Full Code of ioquatix/relaxo for AI

main 60808b666243 cached
29 files
33.7 KB
10.4k tokens
58 symbols
1 requests
Download .txt
Repository: ioquatix/relaxo
Branch: main
Commit: 60808b666243
Files: 29
Total size: 33.7 KB

Directory structure:
gitextract_rc85mw7x/

├── .editorconfig
├── .github/
│   └── workflows/
│       ├── documentation-coverage.yaml
│       ├── documentation.yaml
│       ├── rubocop.yaml
│       ├── test-coverage.yaml
│       ├── test-external.yaml
│       └── test.yaml
├── .gitignore
├── .rubocop.yml
├── benchmarks/
│   └── performance.rb
├── config/
│   └── sus.rb
├── fixtures/
│   └── relaxo/
│       └── test_records.rb
├── gems.rb
├── lib/
│   ├── relaxo/
│   │   ├── changeset.rb
│   │   ├── database.rb
│   │   ├── dataset.rb
│   │   ├── directory.rb
│   │   ├── logger.rb
│   │   └── version.rb
│   └── relaxo.rb
├── license.md
├── readme.md
├── relaxo.gemspec
├── release.cert
└── test/
    ├── relaxo/
    │   ├── changeset.rb
    │   ├── concurrency.rb
    │   ├── database.rb
    │   └── enumeration.rb
    └── relaxo.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
indent_style = tab
indent_size = 2

[*.{yml,yaml}]
indent_style = space
indent_size = 2


================================================
FILE: .github/workflows/documentation-coverage.yaml
================================================
name: Documentation Coverage

on: [push, pull_request]

permissions:
  contents: read

env:
  CONSOLE_OUTPUT: XTerm
  COVERAGE: PartialSummary

jobs:
  validate:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    - uses: ruby/setup-ruby@v1
      with:
        ruby-version: "3.4"
        bundler-cache: true
    
    - name: Validate coverage
      timeout-minutes: 5
      run: bundle exec bake decode:index:coverage lib


================================================
FILE: .github/workflows/documentation.yaml
================================================
name: Documentation

on:
  push:
    branches:
      - main

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages:
permissions:
  contents: read
  pages: write
  id-token: write

# Allow one concurrent deployment:
concurrency:
  group: "pages"
  cancel-in-progress: true

env:
  CONSOLE_OUTPUT: XTerm
  BUNDLE_WITH: maintenance

jobs:
  generate:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4

    - uses: ruby/setup-ruby@v1
      with:
        ruby-version: "3.4"
        bundler-cache: true
    
    - name: Installing packages
      run: sudo apt-get install wget
    
    - name: Generate documentation
      timeout-minutes: 5
      run: bundle exec bake utopia:project:static --force no
    
    - name: Upload documentation artifact
      uses: actions/upload-pages-artifact@v3
      with:
        path: docs
  
  deploy:
    runs-on: ubuntu-latest
    
    environment:
      name: github-pages
      url: ${{steps.deployment.outputs.page_url}}
    
    needs: generate
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4


================================================
FILE: .github/workflows/rubocop.yaml
================================================
name: RuboCop

on: [push, pull_request]

permissions:
  contents: read

env:
  CONSOLE_OUTPUT: XTerm

jobs:
  check:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    - uses: ruby/setup-ruby@v1
      with:
        ruby-version: ruby
        bundler-cache: true
    
    - name: Run RuboCop
      timeout-minutes: 10
      run: bundle exec rubocop


================================================
FILE: .github/workflows/test-coverage.yaml
================================================
name: Test Coverage

on: [push, pull_request]

permissions:
  contents: read

env:
  CONSOLE_OUTPUT: XTerm
  COVERAGE: PartialSummary

jobs:
  test:
    name: ${{matrix.ruby}} on ${{matrix.os}}
    runs-on: ${{matrix.os}}-latest
    
    strategy:
      matrix:
        os:
          - ubuntu
          - macos
        
        ruby:
          - "3.4"
    
    steps:
    - uses: actions/checkout@v4
    - uses: ruby/setup-ruby@v1
      with:
        ruby-version: ${{matrix.ruby}}
        bundler-cache: true
    
    - name: Run tests
      timeout-minutes: 5
      run: bundle exec bake test
    
    - uses: actions/upload-artifact@v4
      with:
        include-hidden-files: true
        if-no-files-found: error
        name: coverage-${{matrix.os}}-${{matrix.ruby}}
        path: .covered.db
  
  validate:
    needs: test
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    - uses: ruby/setup-ruby@v1
      with:
        ruby-version: "3.4"
        bundler-cache: true
    
    - uses: actions/download-artifact@v4
    
    - name: Validate coverage
      timeout-minutes: 5
      run: bundle exec bake covered:validate --paths */.covered.db \;


================================================
FILE: .github/workflows/test-external.yaml
================================================
name: Test External

on: [push, pull_request]

permissions:
  contents: read

env:
  CONSOLE_OUTPUT: XTerm

jobs:
  test:
    name: ${{matrix.ruby}} on ${{matrix.os}}
    runs-on: ${{matrix.os}}-latest
    
    strategy:
      matrix:
        os:
          - ubuntu
          - macos
        
        ruby:
          - "3.1"
          - "3.2"
          - "3.3"
          - "3.4"
    
    steps:
    - uses: actions/checkout@v4
    - uses: ruby/setup-ruby@v1
      with:
        ruby-version: ${{matrix.ruby}}
        bundler-cache: true
    
    - name: Run tests
      timeout-minutes: 10
      run: bundle exec bake test:external


================================================
FILE: .github/workflows/test.yaml
================================================
name: Test

on: [push, pull_request]

permissions:
  contents: read

env:
  CONSOLE_OUTPUT: XTerm

jobs:
  test:
    name: ${{matrix.ruby}} on ${{matrix.os}}
    runs-on: ${{matrix.os}}-latest
    continue-on-error: ${{matrix.experimental}}
    
    strategy:
      matrix:
        os:
          - ubuntu
          - macos
        
        ruby:
          - "3.1"
          - "3.2"
          - "3.3"
          - "3.4"
        
        experimental: [false]
        
        include:
          - os: ubuntu
            ruby: truffleruby
            experimental: true
          - os: ubuntu
            ruby: jruby
            experimental: true
          - os: ubuntu
            ruby: head
            experimental: true
    
    steps:
    - uses: actions/checkout@v4
    - uses: ruby/setup-ruby@v1
      with:
        ruby-version: ${{matrix.ruby}}
        bundler-cache: true
    
    - name: Run tests
      timeout-minutes: 10
      run: bundle exec bake test


================================================
FILE: .gitignore
================================================
/.bundle/
/pkg/
/gems.locked
/.covered.db
/external

/tmp


================================================
FILE: .rubocop.yml
================================================
AllCops:
  DisabledByDefault: true

Layout/IndentationStyle:
  Enabled: true
  EnforcedStyle: tabs

Layout/InitialIndentation:
  Enabled: true

Layout/IndentationWidth:
  Enabled: true
  Width: 1

Layout/IndentationConsistency:
  Enabled: true
  EnforcedStyle: normal

Layout/BlockAlignment:
  Enabled: true

Layout/EndAlignment:
  Enabled: true
  EnforcedStyleAlignWith: start_of_line

Layout/BeginEndAlignment:
  Enabled: true
  EnforcedStyleAlignWith: start_of_line

Layout/ElseAlignment:
  Enabled: true

Layout/DefEndAlignment:
  Enabled: true

Layout/CaseIndentation:
  Enabled: true

Layout/CommentIndentation:
  Enabled: true

Layout/EmptyLinesAroundClassBody:
  Enabled: true

Layout/EmptyLinesAroundModuleBody:
  Enabled: true

Style/FrozenStringLiteralComment:
  Enabled: true

Style/StringLiterals:
  Enabled: true
  EnforcedStyle: double_quotes


================================================
FILE: benchmarks/performance.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2017-2025, by Samuel Williams.

require "benchmark/ips" if ENV["BENCHMARK"]
require "ruby-prof" if ENV["PROFILE"]
require "flamegraph" if ENV["FLAMEGRAPH"]

describe "Relaxo Performance" do
	let(:database_path) {File.join(__dir__, "test")}
	let(:database) {Relaxo.connect(database_path)}
	
	if defined? Benchmark
		def benchmark(name = nil)
			Benchmark.ips do |benchmark|
				# Collect more data for benchmark:
				benchmark.time = 20
				benchmark.warmup = 10
				
				benchmark.report(name) do |i|
					yield i
				end
				
				benchmark.compare!
			end
		end
	elsif defined? RubyProf
		def benchmark(name)
			result = RubyProf.profile do
				yield 1000
			end
			
			#result.eliminate_methods!([/^((?!Utopia).)*$/])
			printer = RubyProf::FlatPrinter.new(result)
			printer.print($stderr, min_percent: 1.0)
			
			printer = RubyProf::GraphHtmlPrinter.new(result)
			filename = name.gsub("/", "_") + ".html"
			File.open(filename, "w") do |file|
				printer.print(file)
			end
		end
	elsif defined? Flamegraph
		def benchmark(name)
			filename = name.gsub("/", "_") + ".html"
			Flamegraph.generate(filename) do
				yield 1
			end
		end
	else
		def benchmark(name)
			yield 1
		end
	end
	
	before(:each) do
		FileUtils.rm_rf(database_path)
	end
	
	it "single transaction should be fast" do
		benchmark("single") do |iterations|
			database.commit(message: "Some Documents") do |dataset|
				iterations.times do |i|
					object = dataset.append("good-#{i}")
					dataset.write("#{i%100}/#{i}", object)
				end
			end
		end
	end
	
	it "multiple transactions should be fast" do
		benchmark("multiple") do |iterations|
			iterations.times do |i|
				database.commit(message: "Some Documents") do |dataset|
					object = dataset.append("good-#{i}")
					dataset.write("#{i%100}/#{i}", object)
				end
			end
		end
	end
end


================================================
FILE: config/sus.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2024-2025, by Samuel Williams.

require "covered/sus"
include Covered::Sus


================================================
FILE: fixtures/relaxo/test_records.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2017-2025, by Samuel Williams.

require "relaxo"
require "tmpdir"

module Relaxo
	TemporaryDatabase = Sus::Shared("temporary database") do
		def around
			Dir.mktmpdir do |directory|
				@root = directory
				super
			end
		end
		
		let(:database_path) {@root}
		let(:database) {Relaxo.connect(database_path)}
	end
	
	TestRecords = Sus::Shared("test records") do
		include_context Relaxo::TemporaryDatabase
		
		let(:prefix) {"records"}
				
		def before
			super
			
			database.commit(message: "Create Sample Data") do |dataset|
				20.times do |i|
					object = dataset.append("good-#{i}")
					dataset.write("#{prefix}/#{i}", object)
				end
				
				10.times do |i|
					object = dataset.append("bad-#{i}")
					dataset.write("#{prefix}/subdirectory/#{i}", object)
				end
			end
		end
	end
end


================================================
FILE: gems.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2012-2025, by Samuel Williams.

source "https://rubygems.org"

gemspec

group :maintenance, optional: true do
	gem "bake-gem"
	gem "bake-modernize"
	
	gem "utopia-project"
end

group :test do
	gem "sus"
	gem "covered"
	gem "decode"
	gem "rubocop"
	
	gem "bake-test"
	gem "bake-test-external"
	
	gem "msgpack"
end


================================================
FILE: lib/relaxo/changeset.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2012-2025, by Samuel Williams.

require_relative "dataset"

module Relaxo
	class Changeset < Dataset
		def initialize(repository, tree)
			super
			
			@changes = {}
			@directories = {}
		end
		
		attr :ref
		attr :changes
		
		def changes?
			@changes.any?
		end
		
		def read(path)
			if update = @changes[path]
				if update[:action] != :remove
					@repository.read(update[:oid])
				end
			else
				super
			end
		end
		
		def append(data, type = :blob)
			oid = @repository.write(data, type)
			
			return @repository.read(oid)
		end
		
		def write(path, object, mode = 0100644)
			root, _, name = path.rpartition("/")
			
			entry = @changes[path] = {
				action: :upsert,
				oid: object.oid,
				object: object,
				filemode: mode,
				path: path,
				root: root,
				name: name,
			}
			
			fetch_directory(root).insert(entry)
			
			return entry
		end
		
		alias []= write
		
		def delete(path)
			root, _, name = path.rpartition("/")
			
			entry = @changes[path] = {
				action: :remove,
				path: path,
				root: root,
				name: name,
			}
			
			fetch_directory(root).delete(entry)
			
			return entry
		end
		
		def abort!
			throw :abort
		end
		
		def write_tree
			@tree.update(@changes.values)
		end
	end
end


================================================
FILE: lib/relaxo/database.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2012-2025, by Samuel Williams.
# Copyright, 2017, by Huba Nagy.

require "rugged"

require_relative "logger"
require_relative "dataset"
require_relative "changeset"

module Relaxo
	HEAD = "HEAD".freeze
	
	class Database
		def initialize(path, branch, metadata = {})
			@path = path
			@metadata = metadata
			
			@repository = Rugged::Repository.new(path)
			# @repository.config['core.fsyncObjectFiles'] = fsync
			
			@branch = branch
		end
		
		def config
			@repository.config
		end
		
		attr :path
		attr :metadata
		attr :repository
		
		# @attribute branch [String] The branch that this database is currently working with.
		attr :branch
		
		# Completely clear out the database.
		def clear!
			if head = @repository.branches[@branch]
				@repository.references.delete(head)
			end
		end
		
		def empty?
			@repository.empty?
		end
		
		def head
			@repository.branches[@branch]
		end
		
		def [] key
			@metadata[key]
		end
		
		# During the execution of the block, changes don't get stored immediately, so reading from the dataset (from outside the block) will continue to return the values that were stored in the configuration when the transaction was started.
		# @return the result of the block.
		def commit(**options)
			result = nil
			
			track_time(options[:message]) do
				catch(:abort) do
					begin
						parent, tree = latest_commit
						
						changeset = Changeset.new(@repository, tree)
						
						result = yield changeset
					end until apply(parent, changeset, **options)
				end
			end
			
			return result
		end
		
		# Efficient point-in-time read-only access.
		def current
			_, tree = latest_commit
			
			dataset = Dataset.new(@repository, tree)
			
			yield dataset if block_given?
			
			return dataset
		end
		
		# revision history of given object
		def history(path)
			head, _ = latest_commit
			
			walker = Rugged::Walker.new(@repository) # Sounds like 'Walker, Texas Ranger'...
			walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
			walker.push(head.oid)
			
			commits = []
			
			old_oid = nil
			
			walker.each do |commit|
				dataset = Dataset.new(@repository, commit.tree)
				oid = dataset.read(path).oid
				
				if oid != old_oid # modified
					yield commit if block_given?
					commits << commit
					old_oid = oid
				end
				
				break if oid.nil? && !old_oid.nil? # deleted or moved
			end
			
			return commits
		end
		
		private
		
		def track_time(message)
			start_time = Time.now
			
			yield
		ensure
			end_time = Time.now
			elapsed_time = end_time - start_time
			
			Console.debug(self) {"#{message.inspect}: %0.3fs" % elapsed_time}
		end
		
		def apply(parent, changeset, **options)
			return true unless changeset.changes?
			
			options[:tree] = changeset.write_tree
			options[:parents] ||= [parent]
			options[:update_ref] ||= "refs/heads/#{@branch}"
			
			begin
				Rugged::Commit.create(@repository, options)
			rescue Rugged::ObjectError
				return false
			end
		end
		
		def latest_commit
			if head = self.head
				return head.target, head.target.tree
			else
				return nil, empty_tree
			end
		end
		
		def empty_tree
			@empty_tree ||= Rugged::Tree.empty(@repository)
		end
	end
end


================================================
FILE: lib/relaxo/dataset.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2012-2025, by Samuel Williams.

require "rugged"

require_relative "directory"

module Relaxo
	class Dataset
		def initialize(repository, tree)
			@repository = repository
			@tree = tree
			
			@directories = {}
		end
		
		def read(path)
			if entry = @tree.path(path) and entry[:type] == :blob and oid = entry[:oid]
				@repository.read(oid)
			end
		rescue Rugged::TreeError
			return nil
		end
		
		alias [] read
		
		def file?
			read(path)
		end
		
		def exist?(path)
			read(path) or directory?(path)
		end
		
		def directory?(path)
			@directories.key?(path) or @tree.path(path)[:type] == :tree
		rescue Rugged::TreeError
			return false
		end
		
		def each(path = "", &block)
			return to_enum(:each, path) unless block_given?
			
			directory = fetch_directory(path)
			
			directory.each(&block)
		end
		
		protected
		
		def fetch_directory(path)
			@directories[path] ||= Directory.new(@repository, @tree, path)
		end
	end
end


================================================
FILE: lib/relaxo/directory.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2017-2025, by Samuel Williams.

require "rugged"

module Relaxo
	class Directory
		def initialize(repository, root_tree, path)
			@repository = repository
			
			# The root tree, which path is relative to:
			@root_tree = root_tree
			
			# The entry and tree for the directory itself:
			@entry = nil
			@tree = nil
			
			@path = path
			
			@entries = nil
			@changes = {}
		end
		
		def freeze
			@changes.freeze
			
			super
		end
		
		def entries
			@entries ||= load_entries!
		end
		
		def each(&block)
			return to_enum(:each) unless block_given?
			
			entries.each do |entry|
				entry[:object] ||= @repository.read(entry[:oid])
				
				yield entry[:name], entry[:object]
			end
		end
		
		def each_entry(&block)
			return to_enum(:each_entry) unless block_given?
			
			entries.each(&block)
		end
		
		def insert(entry)
			_, _, name = entry[:name].rpartition("/")
			
			@changes[name] = entry
			
			# Blow away the cache:
			@entries = nil
		end
		
		def delete(entry)
			_, _, name = entry[:name].rpartition("/")
			
			@changes[name] = nil
			
			# Blow away the cache:
			@entries = nil
		end
		
		private
		
		# Look up the entry for the given directory `@path`:
		def fetch_entry
			@entry ||= @root_tree.path(@path)
		end
		
		# Load the directory tree for the given `@path`:
		def fetch_tree
			@tree ||= Rugged::Tree.new(@repository, fetch_entry[:oid])
		rescue Rugged::TreeError
			return nil
		end
		
		# Load the entries from the tree, applying any changes.
		def load_entries!
			entries = @changes.dup
			
			if tree = fetch_tree
				tree.each_blob do |entry|
					unless entries.key? entry[:name]
						entries[entry[:name]] = entry
					end
				end
			end
			
			return entries.values.compact.sort_by{|entry| entry[:name]}
		end
	end
end


================================================
FILE: lib/relaxo/logger.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2019-2025, by Samuel Williams.

require "console"

module Relaxo
	extend Console
end


================================================
FILE: lib/relaxo/version.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2012-2025, by Samuel Williams.

module Relaxo
	VERSION = "1.8.0"
end


================================================
FILE: lib/relaxo.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2012-2025, by Samuel Williams.

require "relaxo/database"

require "etc"
require "socket"

module Relaxo
	DEFAULT_BRANCH = "main".freeze
	
	def self.connect(path, branch: nil, sync: nil, create: true, **metadata)
		if !File.exist?(path) || create
			repository = Rugged::Repository.init_at(path, true)
			
			if branch
				repository.head = "refs/heads/#{branch}"
			end
			
			if sync || ENV["RELAXO_SYNC"]
				repository.config["core.fsyncObjectFiles"] = true
			end
		else
			repository = Rugged::Repository.new(path)
		end
		
		# Automatically detect the current branch if `branch` is not provided:
		branch ||= self.default_branch(repository)
		
		database = Database.new(path, branch, metadata)
		
		if config = database.config
			unless config["user.name"]
				login = Etc.getpwuid
				hostname = Socket.gethostname
				
				if login
					config["user.name"] = login.name
					config["user.email"] = "#{login.name}@#{hostname}"
				end
			end
		end
		
		return database
	end
	
	private
	
	# Detect the default branch of the repository, taking into account unborn branches.
	def self.default_branch(repository)
		if head = repository.references["HEAD"]
			if target_id = head.target_id
				return target_id.sub(/^refs\/heads\//, "")
			end
		end
		
		return DEFAULT_BRANCH
	end
end


================================================
FILE: license.md
================================================
# MIT License

Copyright, 2012-2025, by Samuel Williams.  
Copyright, 2017-2018, by Huba Nagy.  
Copyright, 2020, by Olle Jonsson.  

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: readme.md
================================================
# ![Relaxo](logo.svg)

Relaxo is a transactional database built on top of git. It's aim is to provide a robust interface for document storage and sorted indexes. If you prefer a higher level interface, you can try [relaxo-model](https://github.com/ioquatix/relaxo-model).

[![Development Status](https://github.com/ioquatix/relaxo/workflows/Test/badge.svg)](https://github.com/ioquatix/relaxo/actions?workflow=Test)

## Installation

Add this line to your application's Gemfile:

``` ruby
gem 'relaxo'
```

And then execute:

    $ bundle

Or install it yourself as:

    $ gem install relaxo

## Usage

Connect to a local database and manipulate some documents.

``` ruby
require 'relaxo'
require 'msgpack'

DB = Relaxo.connect("test")

DB.commit(message: "Create test data") do |dataset|
	object = dataset.append(MessagePack.dump({bob: 'dole'}))
	dataset.write("doc1.msgpack", object)
end

DB.commit(message: "Update test data") do |dataset|
	doc = MessagePack.load dataset.read('doc1.msgpack').data
	doc[:foo] = 'bar'

	object = dataset.append(MessagePack.dump(doc))
	dataset.write("doc2.msgpack", object)
end

doc = MessagePack.load DB.current['doc2.msgpack'].data
puts doc
# => {"bob"=>"dole", "foo"=>"bar"}
```

### Document Storage

Relaxo uses the git persistent data structure for storing documents. This data structure exposes a file-system like interface, which stores any kind of data. This means that you are free to use JSON, or BSON, or MessagePack, or JPEG, or XML, or any combination of those.

Relaxo has a transactional model for both reading and writing.

#### Authors

By default, Relaxo sets up the repository author using the login name and hostname of the current session. You can explicitly change this by modifying `database.config`. Additionally, you can set this per-commit:

``` ruby
database.commit(message: "Testing Enumeration", author: {user: "Alice", email: "alice@localhost"}) do |dataset|
	object = dataset.append("Hello World!")
	dataset.write("hello.txt", object)
end
```

#### Reading Files

``` ruby
path = "path/to/document"

DB.current do |dataset|
	object = dataset.read(path)

	puts "The object id: #{object.oid}"
	puts "The object data size: #{object.size}"
	puts "The object data: #{object.data.inspect}"
end
```

#### Writing Files

``` ruby
path = "path/to/document"
data = MessagePack.dump(document)

DB.commit(message: "Adding document") do |changeset|
	object = changeset.append(data)
	changeset.write(path, object)
end
```

### Datasets and Transactions

`Dataset`s and `Changeset`s are important concepts. Relaxo doesn't allow arbitrary access to data, but instead exposes the git persistent model for both reading and writing. The implications of this are that when reading or writing, you always see a consistent snapshot of the data store.

### Suitability

Relaxo is designed to scale to the hundreds of thousands of documents. It's designed around the git persistent data store, and therefore has some performance and concurrency limitations due to the underlying implementation.

Because it maintains a full history of all changes, the repository would continue to grow over time by default, but there are mechanisms to deal with that.

#### Performance

Relaxo can do anywhere from 1000-10,000 inserts per second depending on how you structure the workload.

    Relaxo Performance
    Warming up --------------------------------------
                  single   129.000  i/100ms
    Calculating -------------------------------------
                  single      6.224k (±14.7%) i/s -    114.036k in  20.000025s
      single transaction should be fast
    Warming up --------------------------------------
                multiple   152.000  i/100ms
    Calculating -------------------------------------
                multiple      1.452k (±15.2%) i/s -     28.120k in  20.101831s
      multiple transactions should be fast

Reading data is lighting fast as it's loaded directly from disk and cached.

### Loading Data

As Relaxo is unapologetically based on git, you can use git directly with a non-bare working directory to add any files you like. You can even point Relaxo at an existing git repository.

### Durability

Relaxo is based on `libgit2` and asserts that it is a transactional database. We base this assertion on:

  - All writes into the object store using `libgit2` are atomic and synchronized to disk.
  - All updates to refs are atomic and synchronized to disk.

Provided these two invariants are maintained, the operation of Relaxo will be safe, even if there are unexpected interruptions to the program.

The durability guarantees of Relaxo depend on [`libgit2` calling `fsync`](https://github.com/libgit2/libgit2/pull/4030), and [this being respected by the underlying hardware](http://www.evanjones.ca/intel-ssd-durability.html). Otherwise, durability cannot be guaranteed.

## Contributing

We welcome contributions to this project.

1.  Fork it.
2.  Create your feature branch (`git checkout -b my-new-feature`).
3.  Commit your changes (`git commit -am 'Add some feature'`).
4.  Push to the branch (`git push origin my-new-feature`).
5.  Create new Pull Request.

### Developer Certificate of Origin

In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.

### Community Guidelines

This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.


================================================
FILE: relaxo.gemspec
================================================
# frozen_string_literal: true

require_relative "lib/relaxo/version"

Gem::Specification.new do |spec|
	spec.name = "relaxo"
	spec.version = Relaxo::VERSION
	
	spec.summary = "Relaxo is versioned document database built on top of git."
	spec.authors = ["Samuel Williams", "Huba Nagy", "Olle Jonsson"]
	spec.license = "MIT"
	
	spec.cert_chain  = ["release.cert"]
	spec.signing_key = File.expand_path("~/.gem/release.pem")
	
	spec.homepage = "https://github.com/ioquatix/relaxo"
	
	spec.metadata = {
		"funding_uri" => "https://github.com/sponsors/ioquatix/",
		"source_code_uri" => "https://github.com/ioquatix/relaxo.git",
	}
	
	spec.files = Dir.glob(["{lib}/**/*", "*.md"], File::FNM_DOTMATCH, base: __dir__)
	
	spec.required_ruby_version = ">= 3.1"
	
	spec.add_dependency "console"
	spec.add_dependency "rugged"
end


================================================
FILE: release.cert
================================================
-----BEGIN CERTIFICATE-----
MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
-----END CERTIFICATE-----


================================================
FILE: test/relaxo/changeset.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2017-2025, by Samuel Williams.

require "relaxo/test_records"

describe Relaxo::Changeset do
	include_context Relaxo::TestRecords
	
	it "should enumerate all documents including writes" do
		records = []
		
		database.commit(message: "Testing Enumeration") do |dataset|
			5.times do |i|
				object = dataset.append("extra-#{i}")
				dataset.write("#{prefix}/extra-#{i}", object)
			end
			
			expect(dataset.exist?("#{prefix}/extra-0")).to be_truthy
			
			records = dataset.each(prefix).to_a
		end
		
		expect(records.count).to be == 25
	end
	
	it "should enumerate all documents excluding deletes" do
		records = database.commit(message: "Testing Enumeration") do |dataset|
			5.times do |i|
				dataset.delete("#{prefix}/#{i}")
			end
			
			expect(dataset.exist?("#{prefix}/0")).to be_falsey
			
			dataset.each(prefix).to_a
		end
		
		expect(records.count).to be == 15
	end
	
	let(:author) do
		{name: "Testing McTestface", email: "testing@testing.com"}
	end
	
	it "can use specified author" do
		database.commit(message: "Testing Enumeration", author: author) do |dataset|
			object = dataset.append("Hello World!")
			dataset.write("hello.txt", object)
		end
		
		commit = database.head.target
		expect(commit.author).to have_keys(
			name: be == "Testing McTestface",
			email: be == "testing@testing.com",
		)
	end
end


================================================
FILE: test/relaxo/concurrency.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2017-2025, by Samuel Williams.

require "relaxo/test_records"

describe Relaxo::Changeset do
	include_context Relaxo::TestRecords
	
	it "should detect conflicts" do
		events = []
		
		alice = Fiber.new do
			database.commit(message: "Alice Data") do |changeset|
				events << :alice
				
				object = changeset.append("sample-data-1")
				changeset.write("conflict-path", object)
				
				Fiber.yield
			end
		end
		
		bob = Fiber.new do
			database.commit(message: "Bob Data") do |changeset|
				events << :bob
				
				object = changeset.append("sample-data-1")
				changeset.write("conflict-path", object)
				
				Fiber.yield
			end
		end
		
		alice.resume
		bob.resume
		alice.resume
		bob.resume
		
		expect(events).to be == [:alice, :bob, :bob]
	end
end


================================================
FILE: test/relaxo/database.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2012-2025, by Samuel Williams.
# Copyright, 2017, by Huba Nagy.

require "relaxo"
require "relaxo/test_records"

describe Relaxo::Database do
	include_context Relaxo::TemporaryDatabase
	
	let(:document_path) {"test/document.json"}
	let(:sample_json) {"[1, 2, 3]"}
	
	it "should be initially empty" do
		expect(database).to be(:empty?)
	end
	
	it "prepares user details in config" do
		expect(database.config.to_hash).to have_keys(
			"user.name", "user.email"
		)
	end
	
	it "can clear database" do
		database.clear!
		expect(database).to be(:empty?)
	end
	
	it "should not be empty with one document" do
		database.commit(message: "Create test document") do |dataset|
			oid = dataset.append(sample_json)
			dataset.write(document_path, oid)
		end
		
		expect(database).not.to be(:empty?)
	end
	
	it "should be able to clear the database" do
		database.commit(message: "Create test document") do |dataset|
			oid = dataset.append(sample_json)
			dataset.write(document_path, oid)
		end
		
		expect(database).not.to be(:empty?)
		
		database.clear!
		
		expect(database).to be(:empty?)
	end
	
	it "should have metadata" do
		expect(database.metadata).to be == {}
	end
	
	it "should create a document" do
		database.commit(message: "Create test document") do |dataset|
			oid = dataset.append(sample_json)
			dataset.write(document_path, oid)
		end
		
		database.current do |dataset|
			expect(dataset[document_path].data).to be == sample_json
		end
	end
	
	it "should erase a document" do
		database.commit(message: "Create test document") do |dataset|
			oid = dataset.append(sample_json)
			dataset.write(document_path, oid)
		end
		
		database.commit(message: "Delete test document") do |dataset|
			dataset.delete(document_path)
		end
		
		database.current do |dataset|
			expect(dataset[document_path]).to be_nil
		end
	end
	
	it "should create multiple documents" do
		database.commit(message: "Create first document") do |dataset|
			oid = dataset.append(sample_json)
			dataset.write(document_path, oid)
		end
		
		database.commit(message: "Create second document") do |dataset|
			oid = dataset.append(sample_json)
			dataset.write(document_path + "2", oid)
		end
		
		database.current do |dataset|
			expect(dataset[document_path].data).to be == sample_json
			expect(dataset[document_path + "2"].data).to be == sample_json
		end
	end
	
	it "can enumerate documents" do
		database.commit(message: "Create first document") do |dataset|
			oid = dataset.append(sample_json)
			
			10.times do |id|
				dataset.write(document_path + "-#{id}", oid)
			end
		end
		
		database.current do |dataset|
			expect(dataset.each("test").count).to be == 10
		end
	end
	
	it "can enumerate commit history of a document" do
		10.times do |id|
			database.commit(message: "revising the document #{id}") do |changeset|
				oid = changeset.append("revision \##{id} of this document")
				changeset.write("test/doot.txt", oid)
			end
		end
		
		database.commit(message: "unrelated commit") do |changeset|
			oid = changeset.append("unrelated document")
			changeset.write("test/unrelated.txt", oid)
		end
		
		expect(database.history("test/doot.txt").count).to be == 10
	end
end


================================================
FILE: test/relaxo/enumeration.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2017-2025, by Samuel Williams.

require "relaxo/test_records"

describe Relaxo::Dataset do
	include_context Relaxo::TestRecords
	
	it "should enumerate all documents" do
		records = []
		
		database.current do |dataset|
			records = dataset.each(prefix).to_a
		end
		
		expect(records.count).to be == 20
	end
end

describe Relaxo::Changeset do
	include_context Relaxo::TestRecords
	
	it "should enumerate all documents" do
		records = []
		
		database.commit(message: "Testing Enumeration") do |dataset|
			records = dataset.each(prefix).to_a
		end
		
		expect(records.count).to be == 20
	end
end


================================================
FILE: test/relaxo.rb
================================================
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2025, by Samuel Williams.

require "relaxo/test_records"

describe Relaxo do
	with ".connect" do
		include Relaxo::TemporaryDatabase
		
		it "can connect to a new database" do
			expect(database).to be_a Relaxo::Database
			expect(database.branch).to (be == "main").or(be == "master")
		end
		
		it "can connect to a new database with an alternative branch name" do
			Relaxo.connect(database_path, branch: "development")
			
			expect(database).to be_a Relaxo::Database
			expect(database.branch).to be == "development"
		end
	end
end
Download .txt
gitextract_rc85mw7x/

├── .editorconfig
├── .github/
│   └── workflows/
│       ├── documentation-coverage.yaml
│       ├── documentation.yaml
│       ├── rubocop.yaml
│       ├── test-coverage.yaml
│       ├── test-external.yaml
│       └── test.yaml
├── .gitignore
├── .rubocop.yml
├── benchmarks/
│   └── performance.rb
├── config/
│   └── sus.rb
├── fixtures/
│   └── relaxo/
│       └── test_records.rb
├── gems.rb
├── lib/
│   ├── relaxo/
│   │   ├── changeset.rb
│   │   ├── database.rb
│   │   ├── dataset.rb
│   │   ├── directory.rb
│   │   ├── logger.rb
│   │   └── version.rb
│   └── relaxo.rb
├── license.md
├── readme.md
├── relaxo.gemspec
├── release.cert
└── test/
    ├── relaxo/
    │   ├── changeset.rb
    │   ├── concurrency.rb
    │   ├── database.rb
    │   └── enumeration.rb
    └── relaxo.rb
Download .txt
SYMBOL INDEX (58 symbols across 9 files)

FILE: benchmarks/performance.rb
  function benchmark (line 15) | def benchmark(name = nil)
  function benchmark (line 29) | def benchmark(name)
  function benchmark (line 45) | def benchmark(name)
  function benchmark (line 52) | def benchmark(name)

FILE: fixtures/relaxo/test_records.rb
  type Relaxo (line 9) | module Relaxo
    function around (line 11) | def around
    function before (line 27) | def before

FILE: lib/relaxo.rb
  type Relaxo (line 11) | module Relaxo
    function connect (line 14) | def self.connect(path, branch: nil, sync: nil, create: true, **metadata)
    function default_branch (line 52) | def self.default_branch(repository)

FILE: lib/relaxo/changeset.rb
  type Relaxo (line 8) | module Relaxo
    class Changeset (line 9) | class Changeset < Dataset
      method initialize (line 10) | def initialize(repository, tree)
      method changes? (line 20) | def changes?
      method read (line 24) | def read(path)
      method append (line 34) | def append(data, type = :blob)
      method write (line 40) | def write(path, object, mode = 0100644)
      method delete (line 60) | def delete(path)
      method abort! (line 75) | def abort!
      method write_tree (line 79) | def write_tree

FILE: lib/relaxo/database.rb
  type Relaxo (line 13) | module Relaxo
    class Database (line 16) | class Database
      method initialize (line 17) | def initialize(path, branch, metadata = {})
      method config (line 27) | def config
      method clear! (line 39) | def clear!
      method empty? (line 45) | def empty?
      method head (line 49) | def head
      method [] (line 53) | def [] key
      method commit (line 59) | def commit(**options)
      method current (line 78) | def current
      method history (line 89) | def history(path)
      method track_time (line 118) | def track_time(message)
      method apply (line 129) | def apply(parent, changeset, **options)
      method latest_commit (line 143) | def latest_commit
      method empty_tree (line 151) | def empty_tree

FILE: lib/relaxo/dataset.rb
  type Relaxo (line 10) | module Relaxo
    class Dataset (line 11) | class Dataset
      method initialize (line 12) | def initialize(repository, tree)
      method read (line 19) | def read(path)
      method file? (line 29) | def file?
      method exist? (line 33) | def exist?(path)
      method directory? (line 37) | def directory?(path)
      method each (line 43) | def each(path = "", &block)
      method fetch_directory (line 53) | def fetch_directory(path)

FILE: lib/relaxo/directory.rb
  type Relaxo (line 8) | module Relaxo
    class Directory (line 9) | class Directory
      method initialize (line 10) | def initialize(repository, root_tree, path)
      method freeze (line 26) | def freeze
      method entries (line 32) | def entries
      method each (line 36) | def each(&block)
      method each_entry (line 46) | def each_entry(&block)
      method insert (line 52) | def insert(entry)
      method delete (line 61) | def delete(entry)
      method fetch_entry (line 73) | def fetch_entry
      method fetch_tree (line 78) | def fetch_tree
      method load_entries! (line 85) | def load_entries!

FILE: lib/relaxo/logger.rb
  type Relaxo (line 8) | module Relaxo

FILE: lib/relaxo/version.rb
  type Relaxo (line 6) | module Relaxo
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (40K chars).
[
  {
    "path": ".editorconfig",
    "chars": 105,
    "preview": "root = true\n\n[*]\nindent_style = tab\nindent_size = 2\n\n[*.{yml,yaml}]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".github/workflows/documentation-coverage.yaml",
    "chars": 450,
    "preview": "name: Documentation Coverage\n\non: [push, pull_request]\n\npermissions:\n  contents: read\n\nenv:\n  CONSOLE_OUTPUT: XTerm\n  CO"
  },
  {
    "path": ".github/workflows/documentation.yaml",
    "chars": 1138,
    "preview": "name: Documentation\n\non:\n  push:\n    branches:\n      - main\n\n# Sets permissions of the GITHUB_TOKEN to allow deployment "
  },
  {
    "path": ".github/workflows/rubocop.yaml",
    "chars": 376,
    "preview": "name: RuboCop\n\non: [push, pull_request]\n\npermissions:\n  contents: read\n\nenv:\n  CONSOLE_OUTPUT: XTerm\n\njobs:\n  check:\n   "
  },
  {
    "path": ".github/workflows/test-coverage.yaml",
    "chars": 1181,
    "preview": "name: Test Coverage\n\non: [push, pull_request]\n\npermissions:\n  contents: read\n\nenv:\n  CONSOLE_OUTPUT: XTerm\n  COVERAGE: P"
  },
  {
    "path": ".github/workflows/test-external.yaml",
    "chars": 632,
    "preview": "name: Test External\n\non: [push, pull_request]\n\npermissions:\n  contents: read\n\nenv:\n  CONSOLE_OUTPUT: XTerm\n\njobs:\n  test"
  },
  {
    "path": ".github/workflows/test.yaml",
    "chars": 966,
    "preview": "name: Test\n\non: [push, pull_request]\n\npermissions:\n  contents: read\n\nenv:\n  CONSOLE_OUTPUT: XTerm\n\njobs:\n  test:\n    nam"
  },
  {
    "path": ".gitignore",
    "chars": 58,
    "preview": "/.bundle/\n/pkg/\n/gems.locked\n/.covered.db\n/external\n\n/tmp\n"
  },
  {
    "path": ".rubocop.yml",
    "chars": 858,
    "preview": "AllCops:\n  DisabledByDefault: true\n\nLayout/IndentationStyle:\n  Enabled: true\n  EnforcedStyle: tabs\n\nLayout/InitialIndent"
  },
  {
    "path": "benchmarks/performance.rb",
    "chars": 1899,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"b"
  },
  {
    "path": "config/sus.rb",
    "chars": 153,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2024-2025, by Samuel Williams.\n\nrequire \"c"
  },
  {
    "path": "fixtures/relaxo/test_records.rb",
    "chars": 878,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"r"
  },
  {
    "path": "gems.rb",
    "chars": 391,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n\nsource \"ht"
  },
  {
    "path": "lib/relaxo/changeset.rb",
    "chars": 1308,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n\nrequire_re"
  },
  {
    "path": "lib/relaxo/database.rb",
    "chars": 3254,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n# Copyright"
  },
  {
    "path": "lib/relaxo/dataset.rb",
    "chars": 1018,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n\nrequire \"r"
  },
  {
    "path": "lib/relaxo/directory.rb",
    "chars": 1847,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"r"
  },
  {
    "path": "lib/relaxo/logger.rb",
    "chars": 163,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2019-2025, by Samuel Williams.\n\nrequire \"c"
  },
  {
    "path": "lib/relaxo/version.rb",
    "chars": 147,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n\nmodule Rel"
  },
  {
    "path": "lib/relaxo.rb",
    "chars": 1366,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n\nrequire \"r"
  },
  {
    "path": "license.md",
    "chars": 1157,
    "preview": "# MIT License\n\nCopyright, 2012-2025, by Samuel Williams.  \nCopyright, 2017-2018, by Huba Nagy.  \nCopyright, 2020, by Oll"
  },
  {
    "path": "readme.md",
    "chars": 5805,
    "preview": "# ![Relaxo](logo.svg)\n\nRelaxo is a transactional database built on top of git. It's aim is to provide a robust interface"
  },
  {
    "path": "relaxo.gemspec",
    "chars": 818,
    "preview": "# frozen_string_literal: true\n\nrequire_relative \"lib/relaxo/version\"\n\nGem::Specification.new do |spec|\n\tspec.name = \"rel"
  },
  {
    "path": "release.cert",
    "chars": 1740,
    "preview": "-----BEGIN CERTIFICATE-----\nMIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11\nZWwud2lsbGlhbXMxHTAbBgoJkia"
  },
  {
    "path": "test/relaxo/changeset.rb",
    "chars": 1407,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"r"
  },
  {
    "path": "test/relaxo/concurrency.rb",
    "chars": 839,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"r"
  },
  {
    "path": "test/relaxo/database.rb",
    "chars": 3247,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n# Copyright"
  },
  {
    "path": "test/relaxo/enumeration.rb",
    "chars": 675,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"r"
  },
  {
    "path": "test/relaxo.rb",
    "chars": 614,
    "preview": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2025, by Samuel Williams.\n\nrequire \"relaxo"
  }
]

About this extraction

This page contains the full source code of the ioquatix/relaxo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (33.7 KB), approximately 10.4k tokens, and a symbol index with 58 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!