[
  {
    "path": ".editorconfig",
    "content": "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",
    "content": "name: Documentation Coverage\n\non: [push, pull_request]\n\npermissions:\n  contents: read\n\nenv:\n  CONSOLE_OUTPUT: XTerm\n  COVERAGE: PartialSummary\n\njobs:\n  validate:\n    runs-on: ubuntu-latest\n    \n    steps:\n    - uses: actions/checkout@v4\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: \"3.4\"\n        bundler-cache: true\n    \n    - name: Validate coverage\n      timeout-minutes: 5\n      run: bundle exec bake decode:index:coverage lib\n"
  },
  {
    "path": ".github/workflows/documentation.yaml",
    "content": "name: Documentation\n\non:\n  push:\n    branches:\n      - main\n\n# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages:\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\n# Allow one concurrent deployment:\nconcurrency:\n  group: \"pages\"\n  cancel-in-progress: true\n\nenv:\n  CONSOLE_OUTPUT: XTerm\n  BUNDLE_WITH: maintenance\n\njobs:\n  generate:\n    runs-on: ubuntu-latest\n    \n    steps:\n    - uses: actions/checkout@v4\n\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: \"3.4\"\n        bundler-cache: true\n    \n    - name: Installing packages\n      run: sudo apt-get install wget\n    \n    - name: Generate documentation\n      timeout-minutes: 5\n      run: bundle exec bake utopia:project:static --force no\n    \n    - name: Upload documentation artifact\n      uses: actions/upload-pages-artifact@v3\n      with:\n        path: docs\n  \n  deploy:\n    runs-on: ubuntu-latest\n    \n    environment:\n      name: github-pages\n      url: ${{steps.deployment.outputs.page_url}}\n    \n    needs: generate\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".github/workflows/rubocop.yaml",
    "content": "name: RuboCop\n\non: [push, pull_request]\n\npermissions:\n  contents: read\n\nenv:\n  CONSOLE_OUTPUT: XTerm\n\njobs:\n  check:\n    runs-on: ubuntu-latest\n    \n    steps:\n    - uses: actions/checkout@v4\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: ruby\n        bundler-cache: true\n    \n    - name: Run RuboCop\n      timeout-minutes: 10\n      run: bundle exec rubocop\n"
  },
  {
    "path": ".github/workflows/test-coverage.yaml",
    "content": "name: Test Coverage\n\non: [push, pull_request]\n\npermissions:\n  contents: read\n\nenv:\n  CONSOLE_OUTPUT: XTerm\n  COVERAGE: PartialSummary\n\njobs:\n  test:\n    name: ${{matrix.ruby}} on ${{matrix.os}}\n    runs-on: ${{matrix.os}}-latest\n    \n    strategy:\n      matrix:\n        os:\n          - ubuntu\n          - macos\n        \n        ruby:\n          - \"3.4\"\n    \n    steps:\n    - uses: actions/checkout@v4\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: ${{matrix.ruby}}\n        bundler-cache: true\n    \n    - name: Run tests\n      timeout-minutes: 5\n      run: bundle exec bake test\n    \n    - uses: actions/upload-artifact@v4\n      with:\n        include-hidden-files: true\n        if-no-files-found: error\n        name: coverage-${{matrix.os}}-${{matrix.ruby}}\n        path: .covered.db\n  \n  validate:\n    needs: test\n    runs-on: ubuntu-latest\n    \n    steps:\n    - uses: actions/checkout@v4\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: \"3.4\"\n        bundler-cache: true\n    \n    - uses: actions/download-artifact@v4\n    \n    - name: Validate coverage\n      timeout-minutes: 5\n      run: bundle exec bake covered:validate --paths */.covered.db \\;\n"
  },
  {
    "path": ".github/workflows/test-external.yaml",
    "content": "name: Test External\n\non: [push, pull_request]\n\npermissions:\n  contents: read\n\nenv:\n  CONSOLE_OUTPUT: XTerm\n\njobs:\n  test:\n    name: ${{matrix.ruby}} on ${{matrix.os}}\n    runs-on: ${{matrix.os}}-latest\n    \n    strategy:\n      matrix:\n        os:\n          - ubuntu\n          - macos\n        \n        ruby:\n          - \"3.1\"\n          - \"3.2\"\n          - \"3.3\"\n          - \"3.4\"\n    \n    steps:\n    - uses: actions/checkout@v4\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: ${{matrix.ruby}}\n        bundler-cache: true\n    \n    - name: Run tests\n      timeout-minutes: 10\n      run: bundle exec bake test:external\n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: Test\n\non: [push, pull_request]\n\npermissions:\n  contents: read\n\nenv:\n  CONSOLE_OUTPUT: XTerm\n\njobs:\n  test:\n    name: ${{matrix.ruby}} on ${{matrix.os}}\n    runs-on: ${{matrix.os}}-latest\n    continue-on-error: ${{matrix.experimental}}\n    \n    strategy:\n      matrix:\n        os:\n          - ubuntu\n          - macos\n        \n        ruby:\n          - \"3.1\"\n          - \"3.2\"\n          - \"3.3\"\n          - \"3.4\"\n        \n        experimental: [false]\n        \n        include:\n          - os: ubuntu\n            ruby: truffleruby\n            experimental: true\n          - os: ubuntu\n            ruby: jruby\n            experimental: true\n          - os: ubuntu\n            ruby: head\n            experimental: true\n    \n    steps:\n    - uses: actions/checkout@v4\n    - uses: ruby/setup-ruby@v1\n      with:\n        ruby-version: ${{matrix.ruby}}\n        bundler-cache: true\n    \n    - name: Run tests\n      timeout-minutes: 10\n      run: bundle exec bake test\n"
  },
  {
    "path": ".gitignore",
    "content": "/.bundle/\n/pkg/\n/gems.locked\n/.covered.db\n/external\n\n/tmp\n"
  },
  {
    "path": ".rubocop.yml",
    "content": "AllCops:\n  DisabledByDefault: true\n\nLayout/IndentationStyle:\n  Enabled: true\n  EnforcedStyle: tabs\n\nLayout/InitialIndentation:\n  Enabled: true\n\nLayout/IndentationWidth:\n  Enabled: true\n  Width: 1\n\nLayout/IndentationConsistency:\n  Enabled: true\n  EnforcedStyle: normal\n\nLayout/BlockAlignment:\n  Enabled: true\n\nLayout/EndAlignment:\n  Enabled: true\n  EnforcedStyleAlignWith: start_of_line\n\nLayout/BeginEndAlignment:\n  Enabled: true\n  EnforcedStyleAlignWith: start_of_line\n\nLayout/ElseAlignment:\n  Enabled: true\n\nLayout/DefEndAlignment:\n  Enabled: true\n\nLayout/CaseIndentation:\n  Enabled: true\n\nLayout/CommentIndentation:\n  Enabled: true\n\nLayout/EmptyLinesAroundClassBody:\n  Enabled: true\n\nLayout/EmptyLinesAroundModuleBody:\n  Enabled: true\n\nStyle/FrozenStringLiteralComment:\n  Enabled: true\n\nStyle/StringLiterals:\n  Enabled: true\n  EnforcedStyle: double_quotes\n"
  },
  {
    "path": "benchmarks/performance.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"benchmark/ips\" if ENV[\"BENCHMARK\"]\nrequire \"ruby-prof\" if ENV[\"PROFILE\"]\nrequire \"flamegraph\" if ENV[\"FLAMEGRAPH\"]\n\ndescribe \"Relaxo Performance\" do\n\tlet(:database_path) {File.join(__dir__, \"test\")}\n\tlet(:database) {Relaxo.connect(database_path)}\n\t\n\tif defined? Benchmark\n\t\tdef benchmark(name = nil)\n\t\t\tBenchmark.ips do |benchmark|\n\t\t\t\t# Collect more data for benchmark:\n\t\t\t\tbenchmark.time = 20\n\t\t\t\tbenchmark.warmup = 10\n\t\t\t\t\n\t\t\t\tbenchmark.report(name) do |i|\n\t\t\t\t\tyield i\n\t\t\t\tend\n\t\t\t\t\n\t\t\t\tbenchmark.compare!\n\t\t\tend\n\t\tend\n\telsif defined? RubyProf\n\t\tdef benchmark(name)\n\t\t\tresult = RubyProf.profile do\n\t\t\t\tyield 1000\n\t\t\tend\n\t\t\t\n\t\t\t#result.eliminate_methods!([/^((?!Utopia).)*$/])\n\t\t\tprinter = RubyProf::FlatPrinter.new(result)\n\t\t\tprinter.print($stderr, min_percent: 1.0)\n\t\t\t\n\t\t\tprinter = RubyProf::GraphHtmlPrinter.new(result)\n\t\t\tfilename = name.gsub(\"/\", \"_\") + \".html\"\n\t\t\tFile.open(filename, \"w\") do |file|\n\t\t\t\tprinter.print(file)\n\t\t\tend\n\t\tend\n\telsif defined? Flamegraph\n\t\tdef benchmark(name)\n\t\t\tfilename = name.gsub(\"/\", \"_\") + \".html\"\n\t\t\tFlamegraph.generate(filename) do\n\t\t\t\tyield 1\n\t\t\tend\n\t\tend\n\telse\n\t\tdef benchmark(name)\n\t\t\tyield 1\n\t\tend\n\tend\n\t\n\tbefore(:each) do\n\t\tFileUtils.rm_rf(database_path)\n\tend\n\t\n\tit \"single transaction should be fast\" do\n\t\tbenchmark(\"single\") do |iterations|\n\t\t\tdatabase.commit(message: \"Some Documents\") do |dataset|\n\t\t\t\titerations.times do |i|\n\t\t\t\t\tobject = dataset.append(\"good-#{i}\")\n\t\t\t\t\tdataset.write(\"#{i%100}/#{i}\", object)\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\tend\n\t\n\tit \"multiple transactions should be fast\" do\n\t\tbenchmark(\"multiple\") do |iterations|\n\t\t\titerations.times do |i|\n\t\t\t\tdatabase.commit(message: \"Some Documents\") do |dataset|\n\t\t\t\t\tobject = dataset.append(\"good-#{i}\")\n\t\t\t\t\tdataset.write(\"#{i%100}/#{i}\", object)\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\tend\nend\n"
  },
  {
    "path": "config/sus.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2024-2025, by Samuel Williams.\n\nrequire \"covered/sus\"\ninclude Covered::Sus\n"
  },
  {
    "path": "fixtures/relaxo/test_records.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"relaxo\"\nrequire \"tmpdir\"\n\nmodule Relaxo\n\tTemporaryDatabase = Sus::Shared(\"temporary database\") do\n\t\tdef around\n\t\t\tDir.mktmpdir do |directory|\n\t\t\t\t@root = directory\n\t\t\t\tsuper\n\t\t\tend\n\t\tend\n\t\t\n\t\tlet(:database_path) {@root}\n\t\tlet(:database) {Relaxo.connect(database_path)}\n\tend\n\t\n\tTestRecords = Sus::Shared(\"test records\") do\n\t\tinclude_context Relaxo::TemporaryDatabase\n\t\t\n\t\tlet(:prefix) {\"records\"}\n\t\t\t\t\n\t\tdef before\n\t\t\tsuper\n\t\t\t\n\t\t\tdatabase.commit(message: \"Create Sample Data\") do |dataset|\n\t\t\t\t20.times do |i|\n\t\t\t\t\tobject = dataset.append(\"good-#{i}\")\n\t\t\t\t\tdataset.write(\"#{prefix}/#{i}\", object)\n\t\t\t\tend\n\t\t\t\t\n\t\t\t\t10.times do |i|\n\t\t\t\t\tobject = dataset.append(\"bad-#{i}\")\n\t\t\t\t\tdataset.write(\"#{prefix}/subdirectory/#{i}\", object)\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\tend\nend\n"
  },
  {
    "path": "gems.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n\nsource \"https://rubygems.org\"\n\ngemspec\n\ngroup :maintenance, optional: true do\n\tgem \"bake-gem\"\n\tgem \"bake-modernize\"\n\t\n\tgem \"utopia-project\"\nend\n\ngroup :test do\n\tgem \"sus\"\n\tgem \"covered\"\n\tgem \"decode\"\n\tgem \"rubocop\"\n\t\n\tgem \"bake-test\"\n\tgem \"bake-test-external\"\n\t\n\tgem \"msgpack\"\nend\n"
  },
  {
    "path": "lib/relaxo/changeset.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n\nrequire_relative \"dataset\"\n\nmodule Relaxo\n\tclass Changeset < Dataset\n\t\tdef initialize(repository, tree)\n\t\t\tsuper\n\t\t\t\n\t\t\t@changes = {}\n\t\t\t@directories = {}\n\t\tend\n\t\t\n\t\tattr :ref\n\t\tattr :changes\n\t\t\n\t\tdef changes?\n\t\t\t@changes.any?\n\t\tend\n\t\t\n\t\tdef read(path)\n\t\t\tif update = @changes[path]\n\t\t\t\tif update[:action] != :remove\n\t\t\t\t\t@repository.read(update[:oid])\n\t\t\t\tend\n\t\t\telse\n\t\t\t\tsuper\n\t\t\tend\n\t\tend\n\t\t\n\t\tdef append(data, type = :blob)\n\t\t\toid = @repository.write(data, type)\n\t\t\t\n\t\t\treturn @repository.read(oid)\n\t\tend\n\t\t\n\t\tdef write(path, object, mode = 0100644)\n\t\t\troot, _, name = path.rpartition(\"/\")\n\t\t\t\n\t\t\tentry = @changes[path] = {\n\t\t\t\taction: :upsert,\n\t\t\t\toid: object.oid,\n\t\t\t\tobject: object,\n\t\t\t\tfilemode: mode,\n\t\t\t\tpath: path,\n\t\t\t\troot: root,\n\t\t\t\tname: name,\n\t\t\t}\n\t\t\t\n\t\t\tfetch_directory(root).insert(entry)\n\t\t\t\n\t\t\treturn entry\n\t\tend\n\t\t\n\t\talias []= write\n\t\t\n\t\tdef delete(path)\n\t\t\troot, _, name = path.rpartition(\"/\")\n\t\t\t\n\t\t\tentry = @changes[path] = {\n\t\t\t\taction: :remove,\n\t\t\t\tpath: path,\n\t\t\t\troot: root,\n\t\t\t\tname: name,\n\t\t\t}\n\t\t\t\n\t\t\tfetch_directory(root).delete(entry)\n\t\t\t\n\t\t\treturn entry\n\t\tend\n\t\t\n\t\tdef abort!\n\t\t\tthrow :abort\n\t\tend\n\t\t\n\t\tdef write_tree\n\t\t\t@tree.update(@changes.values)\n\t\tend\n\tend\nend\n"
  },
  {
    "path": "lib/relaxo/database.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n# Copyright, 2017, by Huba Nagy.\n\nrequire \"rugged\"\n\nrequire_relative \"logger\"\nrequire_relative \"dataset\"\nrequire_relative \"changeset\"\n\nmodule Relaxo\n\tHEAD = \"HEAD\".freeze\n\t\n\tclass Database\n\t\tdef initialize(path, branch, metadata = {})\n\t\t\t@path = path\n\t\t\t@metadata = metadata\n\t\t\t\n\t\t\t@repository = Rugged::Repository.new(path)\n\t\t\t# @repository.config['core.fsyncObjectFiles'] = fsync\n\t\t\t\n\t\t\t@branch = branch\n\t\tend\n\t\t\n\t\tdef config\n\t\t\t@repository.config\n\t\tend\n\t\t\n\t\tattr :path\n\t\tattr :metadata\n\t\tattr :repository\n\t\t\n\t\t# @attribute branch [String] The branch that this database is currently working with.\n\t\tattr :branch\n\t\t\n\t\t# Completely clear out the database.\n\t\tdef clear!\n\t\t\tif head = @repository.branches[@branch]\n\t\t\t\t@repository.references.delete(head)\n\t\t\tend\n\t\tend\n\t\t\n\t\tdef empty?\n\t\t\t@repository.empty?\n\t\tend\n\t\t\n\t\tdef head\n\t\t\t@repository.branches[@branch]\n\t\tend\n\t\t\n\t\tdef [] key\n\t\t\t@metadata[key]\n\t\tend\n\t\t\n\t\t# 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.\n\t\t# @return the result of the block.\n\t\tdef commit(**options)\n\t\t\tresult = nil\n\t\t\t\n\t\t\ttrack_time(options[:message]) do\n\t\t\t\tcatch(:abort) do\n\t\t\t\t\tbegin\n\t\t\t\t\t\tparent, tree = latest_commit\n\t\t\t\t\t\t\n\t\t\t\t\t\tchangeset = Changeset.new(@repository, tree)\n\t\t\t\t\t\t\n\t\t\t\t\t\tresult = yield changeset\n\t\t\t\t\tend until apply(parent, changeset, **options)\n\t\t\t\tend\n\t\t\tend\n\t\t\t\n\t\t\treturn result\n\t\tend\n\t\t\n\t\t# Efficient point-in-time read-only access.\n\t\tdef current\n\t\t\t_, tree = latest_commit\n\t\t\t\n\t\t\tdataset = Dataset.new(@repository, tree)\n\t\t\t\n\t\t\tyield dataset if block_given?\n\t\t\t\n\t\t\treturn dataset\n\t\tend\n\t\t\n\t\t# revision history of given object\n\t\tdef history(path)\n\t\t\thead, _ = latest_commit\n\t\t\t\n\t\t\twalker = Rugged::Walker.new(@repository) # Sounds like 'Walker, Texas Ranger'...\n\t\t\twalker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)\n\t\t\twalker.push(head.oid)\n\t\t\t\n\t\t\tcommits = []\n\t\t\t\n\t\t\told_oid = nil\n\t\t\t\n\t\t\twalker.each do |commit|\n\t\t\t\tdataset = Dataset.new(@repository, commit.tree)\n\t\t\t\toid = dataset.read(path).oid\n\t\t\t\t\n\t\t\t\tif oid != old_oid # modified\n\t\t\t\t\tyield commit if block_given?\n\t\t\t\t\tcommits << commit\n\t\t\t\t\told_oid = oid\n\t\t\t\tend\n\t\t\t\t\n\t\t\t\tbreak if oid.nil? && !old_oid.nil? # deleted or moved\n\t\t\tend\n\t\t\t\n\t\t\treturn commits\n\t\tend\n\t\t\n\t\tprivate\n\t\t\n\t\tdef track_time(message)\n\t\t\tstart_time = Time.now\n\t\t\t\n\t\t\tyield\n\t\tensure\n\t\t\tend_time = Time.now\n\t\t\telapsed_time = end_time - start_time\n\t\t\t\n\t\t\tConsole.debug(self) {\"#{message.inspect}: %0.3fs\" % elapsed_time}\n\t\tend\n\t\t\n\t\tdef apply(parent, changeset, **options)\n\t\t\treturn true unless changeset.changes?\n\t\t\t\n\t\t\toptions[:tree] = changeset.write_tree\n\t\t\toptions[:parents] ||= [parent]\n\t\t\toptions[:update_ref] ||= \"refs/heads/#{@branch}\"\n\t\t\t\n\t\t\tbegin\n\t\t\t\tRugged::Commit.create(@repository, options)\n\t\t\trescue Rugged::ObjectError\n\t\t\t\treturn false\n\t\t\tend\n\t\tend\n\t\t\n\t\tdef latest_commit\n\t\t\tif head = self.head\n\t\t\t\treturn head.target, head.target.tree\n\t\t\telse\n\t\t\t\treturn nil, empty_tree\n\t\t\tend\n\t\tend\n\t\t\n\t\tdef empty_tree\n\t\t\t@empty_tree ||= Rugged::Tree.empty(@repository)\n\t\tend\n\tend\nend\n"
  },
  {
    "path": "lib/relaxo/dataset.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n\nrequire \"rugged\"\n\nrequire_relative \"directory\"\n\nmodule Relaxo\n\tclass Dataset\n\t\tdef initialize(repository, tree)\n\t\t\t@repository = repository\n\t\t\t@tree = tree\n\t\t\t\n\t\t\t@directories = {}\n\t\tend\n\t\t\n\t\tdef read(path)\n\t\t\tif entry = @tree.path(path) and entry[:type] == :blob and oid = entry[:oid]\n\t\t\t\t@repository.read(oid)\n\t\t\tend\n\t\trescue Rugged::TreeError\n\t\t\treturn nil\n\t\tend\n\t\t\n\t\talias [] read\n\t\t\n\t\tdef file?\n\t\t\tread(path)\n\t\tend\n\t\t\n\t\tdef exist?(path)\n\t\t\tread(path) or directory?(path)\n\t\tend\n\t\t\n\t\tdef directory?(path)\n\t\t\t@directories.key?(path) or @tree.path(path)[:type] == :tree\n\t\trescue Rugged::TreeError\n\t\t\treturn false\n\t\tend\n\t\t\n\t\tdef each(path = \"\", &block)\n\t\t\treturn to_enum(:each, path) unless block_given?\n\t\t\t\n\t\t\tdirectory = fetch_directory(path)\n\t\t\t\n\t\t\tdirectory.each(&block)\n\t\tend\n\t\t\n\t\tprotected\n\t\t\n\t\tdef fetch_directory(path)\n\t\t\t@directories[path] ||= Directory.new(@repository, @tree, path)\n\t\tend\n\tend\nend\n"
  },
  {
    "path": "lib/relaxo/directory.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"rugged\"\n\nmodule Relaxo\n\tclass Directory\n\t\tdef initialize(repository, root_tree, path)\n\t\t\t@repository = repository\n\t\t\t\n\t\t\t# The root tree, which path is relative to:\n\t\t\t@root_tree = root_tree\n\t\t\t\n\t\t\t# The entry and tree for the directory itself:\n\t\t\t@entry = nil\n\t\t\t@tree = nil\n\t\t\t\n\t\t\t@path = path\n\t\t\t\n\t\t\t@entries = nil\n\t\t\t@changes = {}\n\t\tend\n\t\t\n\t\tdef freeze\n\t\t\t@changes.freeze\n\t\t\t\n\t\t\tsuper\n\t\tend\n\t\t\n\t\tdef entries\n\t\t\t@entries ||= load_entries!\n\t\tend\n\t\t\n\t\tdef each(&block)\n\t\t\treturn to_enum(:each) unless block_given?\n\t\t\t\n\t\t\tentries.each do |entry|\n\t\t\t\tentry[:object] ||= @repository.read(entry[:oid])\n\t\t\t\t\n\t\t\t\tyield entry[:name], entry[:object]\n\t\t\tend\n\t\tend\n\t\t\n\t\tdef each_entry(&block)\n\t\t\treturn to_enum(:each_entry) unless block_given?\n\t\t\t\n\t\t\tentries.each(&block)\n\t\tend\n\t\t\n\t\tdef insert(entry)\n\t\t\t_, _, name = entry[:name].rpartition(\"/\")\n\t\t\t\n\t\t\t@changes[name] = entry\n\t\t\t\n\t\t\t# Blow away the cache:\n\t\t\t@entries = nil\n\t\tend\n\t\t\n\t\tdef delete(entry)\n\t\t\t_, _, name = entry[:name].rpartition(\"/\")\n\t\t\t\n\t\t\t@changes[name] = nil\n\t\t\t\n\t\t\t# Blow away the cache:\n\t\t\t@entries = nil\n\t\tend\n\t\t\n\t\tprivate\n\t\t\n\t\t# Look up the entry for the given directory `@path`:\n\t\tdef fetch_entry\n\t\t\t@entry ||= @root_tree.path(@path)\n\t\tend\n\t\t\n\t\t# Load the directory tree for the given `@path`:\n\t\tdef fetch_tree\n\t\t\t@tree ||= Rugged::Tree.new(@repository, fetch_entry[:oid])\n\t\trescue Rugged::TreeError\n\t\t\treturn nil\n\t\tend\n\t\t\n\t\t# Load the entries from the tree, applying any changes.\n\t\tdef load_entries!\n\t\t\tentries = @changes.dup\n\t\t\t\n\t\t\tif tree = fetch_tree\n\t\t\t\ttree.each_blob do |entry|\n\t\t\t\t\tunless entries.key? entry[:name]\n\t\t\t\t\t\tentries[entry[:name]] = entry\n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\tend\n\t\t\t\n\t\t\treturn entries.values.compact.sort_by{|entry| entry[:name]}\n\t\tend\n\tend\nend\n"
  },
  {
    "path": "lib/relaxo/logger.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2019-2025, by Samuel Williams.\n\nrequire \"console\"\n\nmodule Relaxo\n\textend Console\nend\n"
  },
  {
    "path": "lib/relaxo/version.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n\nmodule Relaxo\n\tVERSION = \"1.8.0\"\nend\n"
  },
  {
    "path": "lib/relaxo.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n\nrequire \"relaxo/database\"\n\nrequire \"etc\"\nrequire \"socket\"\n\nmodule Relaxo\n\tDEFAULT_BRANCH = \"main\".freeze\n\t\n\tdef self.connect(path, branch: nil, sync: nil, create: true, **metadata)\n\t\tif !File.exist?(path) || create\n\t\t\trepository = Rugged::Repository.init_at(path, true)\n\t\t\t\n\t\t\tif branch\n\t\t\t\trepository.head = \"refs/heads/#{branch}\"\n\t\t\tend\n\t\t\t\n\t\t\tif sync || ENV[\"RELAXO_SYNC\"]\n\t\t\t\trepository.config[\"core.fsyncObjectFiles\"] = true\n\t\t\tend\n\t\telse\n\t\t\trepository = Rugged::Repository.new(path)\n\t\tend\n\t\t\n\t\t# Automatically detect the current branch if `branch` is not provided:\n\t\tbranch ||= self.default_branch(repository)\n\t\t\n\t\tdatabase = Database.new(path, branch, metadata)\n\t\t\n\t\tif config = database.config\n\t\t\tunless config[\"user.name\"]\n\t\t\t\tlogin = Etc.getpwuid\n\t\t\t\thostname = Socket.gethostname\n\t\t\t\t\n\t\t\t\tif login\n\t\t\t\t\tconfig[\"user.name\"] = login.name\n\t\t\t\t\tconfig[\"user.email\"] = \"#{login.name}@#{hostname}\"\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\t\t\n\t\treturn database\n\tend\n\t\n\tprivate\n\t\n\t# Detect the default branch of the repository, taking into account unborn branches.\n\tdef self.default_branch(repository)\n\t\tif head = repository.references[\"HEAD\"]\n\t\t\tif target_id = head.target_id\n\t\t\t\treturn target_id.sub(/^refs\\/heads\\//, \"\")\n\t\t\tend\n\t\tend\n\t\t\n\t\treturn DEFAULT_BRANCH\n\tend\nend\n"
  },
  {
    "path": "license.md",
    "content": "# MIT License\n\nCopyright, 2012-2025, by Samuel Williams.  \nCopyright, 2017-2018, by Huba Nagy.  \nCopyright, 2020, by Olle Jonsson.  \n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "readme.md",
    "content": "# ![Relaxo](logo.svg)\n\nRelaxo 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).\n\n[![Development Status](https://github.com/ioquatix/relaxo/workflows/Test/badge.svg)](https://github.com/ioquatix/relaxo/actions?workflow=Test)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n``` ruby\ngem 'relaxo'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install relaxo\n\n## Usage\n\nConnect to a local database and manipulate some documents.\n\n``` ruby\nrequire 'relaxo'\nrequire 'msgpack'\n\nDB = Relaxo.connect(\"test\")\n\nDB.commit(message: \"Create test data\") do |dataset|\n\tobject = dataset.append(MessagePack.dump({bob: 'dole'}))\n\tdataset.write(\"doc1.msgpack\", object)\nend\n\nDB.commit(message: \"Update test data\") do |dataset|\n\tdoc = MessagePack.load dataset.read('doc1.msgpack').data\n\tdoc[:foo] = 'bar'\n\n\tobject = dataset.append(MessagePack.dump(doc))\n\tdataset.write(\"doc2.msgpack\", object)\nend\n\ndoc = MessagePack.load DB.current['doc2.msgpack'].data\nputs doc\n# => {\"bob\"=>\"dole\", \"foo\"=>\"bar\"}\n```\n\n### Document Storage\n\nRelaxo 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.\n\nRelaxo has a transactional model for both reading and writing.\n\n#### Authors\n\nBy 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:\n\n``` ruby\ndatabase.commit(message: \"Testing Enumeration\", author: {user: \"Alice\", email: \"alice@localhost\"}) do |dataset|\n\tobject = dataset.append(\"Hello World!\")\n\tdataset.write(\"hello.txt\", object)\nend\n```\n\n#### Reading Files\n\n``` ruby\npath = \"path/to/document\"\n\nDB.current do |dataset|\n\tobject = dataset.read(path)\n\n\tputs \"The object id: #{object.oid}\"\n\tputs \"The object data size: #{object.size}\"\n\tputs \"The object data: #{object.data.inspect}\"\nend\n```\n\n#### Writing Files\n\n``` ruby\npath = \"path/to/document\"\ndata = MessagePack.dump(document)\n\nDB.commit(message: \"Adding document\") do |changeset|\n\tobject = changeset.append(data)\n\tchangeset.write(path, object)\nend\n```\n\n### Datasets and Transactions\n\n`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.\n\n### Suitability\n\nRelaxo 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.\n\nBecause 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.\n\n#### Performance\n\nRelaxo can do anywhere from 1000-10,000 inserts per second depending on how you structure the workload.\n\n    Relaxo Performance\n    Warming up --------------------------------------\n                  single   129.000  i/100ms\n    Calculating -------------------------------------\n                  single      6.224k (±14.7%) i/s -    114.036k in  20.000025s\n      single transaction should be fast\n    Warming up --------------------------------------\n                multiple   152.000  i/100ms\n    Calculating -------------------------------------\n                multiple      1.452k (±15.2%) i/s -     28.120k in  20.101831s\n      multiple transactions should be fast\n\nReading data is lighting fast as it's loaded directly from disk and cached.\n\n### Loading Data\n\nAs 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.\n\n### Durability\n\nRelaxo is based on `libgit2` and asserts that it is a transactional database. We base this assertion on:\n\n  - All writes into the object store using `libgit2` are atomic and synchronized to disk.\n  - All updates to refs are atomic and synchronized to disk.\n\nProvided these two invariants are maintained, the operation of Relaxo will be safe, even if there are unexpected interruptions to the program.\n\nThe 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.\n\n## Contributing\n\nWe welcome contributions to this project.\n\n1.  Fork it.\n2.  Create your feature branch (`git checkout -b my-new-feature`).\n3.  Commit your changes (`git commit -am 'Add some feature'`).\n4.  Push to the branch (`git push origin my-new-feature`).\n5.  Create new Pull Request.\n\n### Developer Certificate of Origin\n\nIn 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.\n\n### Community Guidelines\n\nThis 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.\n"
  },
  {
    "path": "relaxo.gemspec",
    "content": "# frozen_string_literal: true\n\nrequire_relative \"lib/relaxo/version\"\n\nGem::Specification.new do |spec|\n\tspec.name = \"relaxo\"\n\tspec.version = Relaxo::VERSION\n\t\n\tspec.summary = \"Relaxo is versioned document database built on top of git.\"\n\tspec.authors = [\"Samuel Williams\", \"Huba Nagy\", \"Olle Jonsson\"]\n\tspec.license = \"MIT\"\n\t\n\tspec.cert_chain  = [\"release.cert\"]\n\tspec.signing_key = File.expand_path(\"~/.gem/release.pem\")\n\t\n\tspec.homepage = \"https://github.com/ioquatix/relaxo\"\n\t\n\tspec.metadata = {\n\t\t\"funding_uri\" => \"https://github.com/sponsors/ioquatix/\",\n\t\t\"source_code_uri\" => \"https://github.com/ioquatix/relaxo.git\",\n\t}\n\t\n\tspec.files = Dir.glob([\"{lib}/**/*\", \"*.md\"], File::FNM_DOTMATCH, base: __dir__)\n\t\n\tspec.required_ruby_version = \">= 3.1\"\n\t\n\tspec.add_dependency \"console\"\n\tspec.add_dependency \"rugged\"\nend\n"
  },
  {
    "path": "release.cert",
    "content": "-----BEGIN CERTIFICATE-----\nMIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11\nZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK\nCZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz\nMjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd\nMBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj\nbzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB\nigKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2\n9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW\nsGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE\ne5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN\nXibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss\nRZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn\ntUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM\nzp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW\nxm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O\nBBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs\naWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs\naWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE\ncBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl\nxCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/\nc1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp\n8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws\nJkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP\neX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt\nQ2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8\nvoD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/relaxo/changeset.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"relaxo/test_records\"\n\ndescribe Relaxo::Changeset do\n\tinclude_context Relaxo::TestRecords\n\t\n\tit \"should enumerate all documents including writes\" do\n\t\trecords = []\n\t\t\n\t\tdatabase.commit(message: \"Testing Enumeration\") do |dataset|\n\t\t\t5.times do |i|\n\t\t\t\tobject = dataset.append(\"extra-#{i}\")\n\t\t\t\tdataset.write(\"#{prefix}/extra-#{i}\", object)\n\t\t\tend\n\t\t\t\n\t\t\texpect(dataset.exist?(\"#{prefix}/extra-0\")).to be_truthy\n\t\t\t\n\t\t\trecords = dataset.each(prefix).to_a\n\t\tend\n\t\t\n\t\texpect(records.count).to be == 25\n\tend\n\t\n\tit \"should enumerate all documents excluding deletes\" do\n\t\trecords = database.commit(message: \"Testing Enumeration\") do |dataset|\n\t\t\t5.times do |i|\n\t\t\t\tdataset.delete(\"#{prefix}/#{i}\")\n\t\t\tend\n\t\t\t\n\t\t\texpect(dataset.exist?(\"#{prefix}/0\")).to be_falsey\n\t\t\t\n\t\t\tdataset.each(prefix).to_a\n\t\tend\n\t\t\n\t\texpect(records.count).to be == 15\n\tend\n\t\n\tlet(:author) do\n\t\t{name: \"Testing McTestface\", email: \"testing@testing.com\"}\n\tend\n\t\n\tit \"can use specified author\" do\n\t\tdatabase.commit(message: \"Testing Enumeration\", author: author) do |dataset|\n\t\t\tobject = dataset.append(\"Hello World!\")\n\t\t\tdataset.write(\"hello.txt\", object)\n\t\tend\n\t\t\n\t\tcommit = database.head.target\n\t\texpect(commit.author).to have_keys(\n\t\t\tname: be == \"Testing McTestface\",\n\t\t\temail: be == \"testing@testing.com\",\n\t\t)\n\tend\nend\n"
  },
  {
    "path": "test/relaxo/concurrency.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"relaxo/test_records\"\n\ndescribe Relaxo::Changeset do\n\tinclude_context Relaxo::TestRecords\n\t\n\tit \"should detect conflicts\" do\n\t\tevents = []\n\t\t\n\t\talice = Fiber.new do\n\t\t\tdatabase.commit(message: \"Alice Data\") do |changeset|\n\t\t\t\tevents << :alice\n\t\t\t\t\n\t\t\t\tobject = changeset.append(\"sample-data-1\")\n\t\t\t\tchangeset.write(\"conflict-path\", object)\n\t\t\t\t\n\t\t\t\tFiber.yield\n\t\t\tend\n\t\tend\n\t\t\n\t\tbob = Fiber.new do\n\t\t\tdatabase.commit(message: \"Bob Data\") do |changeset|\n\t\t\t\tevents << :bob\n\t\t\t\t\n\t\t\t\tobject = changeset.append(\"sample-data-1\")\n\t\t\t\tchangeset.write(\"conflict-path\", object)\n\t\t\t\t\n\t\t\t\tFiber.yield\n\t\t\tend\n\t\tend\n\t\t\n\t\talice.resume\n\t\tbob.resume\n\t\talice.resume\n\t\tbob.resume\n\t\t\n\t\texpect(events).to be == [:alice, :bob, :bob]\n\tend\nend\n"
  },
  {
    "path": "test/relaxo/database.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2012-2025, by Samuel Williams.\n# Copyright, 2017, by Huba Nagy.\n\nrequire \"relaxo\"\nrequire \"relaxo/test_records\"\n\ndescribe Relaxo::Database do\n\tinclude_context Relaxo::TemporaryDatabase\n\t\n\tlet(:document_path) {\"test/document.json\"}\n\tlet(:sample_json) {\"[1, 2, 3]\"}\n\t\n\tit \"should be initially empty\" do\n\t\texpect(database).to be(:empty?)\n\tend\n\t\n\tit \"prepares user details in config\" do\n\t\texpect(database.config.to_hash).to have_keys(\n\t\t\t\"user.name\", \"user.email\"\n\t\t)\n\tend\n\t\n\tit \"can clear database\" do\n\t\tdatabase.clear!\n\t\texpect(database).to be(:empty?)\n\tend\n\t\n\tit \"should not be empty with one document\" do\n\t\tdatabase.commit(message: \"Create test document\") do |dataset|\n\t\t\toid = dataset.append(sample_json)\n\t\t\tdataset.write(document_path, oid)\n\t\tend\n\t\t\n\t\texpect(database).not.to be(:empty?)\n\tend\n\t\n\tit \"should be able to clear the database\" do\n\t\tdatabase.commit(message: \"Create test document\") do |dataset|\n\t\t\toid = dataset.append(sample_json)\n\t\t\tdataset.write(document_path, oid)\n\t\tend\n\t\t\n\t\texpect(database).not.to be(:empty?)\n\t\t\n\t\tdatabase.clear!\n\t\t\n\t\texpect(database).to be(:empty?)\n\tend\n\t\n\tit \"should have metadata\" do\n\t\texpect(database.metadata).to be == {}\n\tend\n\t\n\tit \"should create a document\" do\n\t\tdatabase.commit(message: \"Create test document\") do |dataset|\n\t\t\toid = dataset.append(sample_json)\n\t\t\tdataset.write(document_path, oid)\n\t\tend\n\t\t\n\t\tdatabase.current do |dataset|\n\t\t\texpect(dataset[document_path].data).to be == sample_json\n\t\tend\n\tend\n\t\n\tit \"should erase a document\" do\n\t\tdatabase.commit(message: \"Create test document\") do |dataset|\n\t\t\toid = dataset.append(sample_json)\n\t\t\tdataset.write(document_path, oid)\n\t\tend\n\t\t\n\t\tdatabase.commit(message: \"Delete test document\") do |dataset|\n\t\t\tdataset.delete(document_path)\n\t\tend\n\t\t\n\t\tdatabase.current do |dataset|\n\t\t\texpect(dataset[document_path]).to be_nil\n\t\tend\n\tend\n\t\n\tit \"should create multiple documents\" do\n\t\tdatabase.commit(message: \"Create first document\") do |dataset|\n\t\t\toid = dataset.append(sample_json)\n\t\t\tdataset.write(document_path, oid)\n\t\tend\n\t\t\n\t\tdatabase.commit(message: \"Create second document\") do |dataset|\n\t\t\toid = dataset.append(sample_json)\n\t\t\tdataset.write(document_path + \"2\", oid)\n\t\tend\n\t\t\n\t\tdatabase.current do |dataset|\n\t\t\texpect(dataset[document_path].data).to be == sample_json\n\t\t\texpect(dataset[document_path + \"2\"].data).to be == sample_json\n\t\tend\n\tend\n\t\n\tit \"can enumerate documents\" do\n\t\tdatabase.commit(message: \"Create first document\") do |dataset|\n\t\t\toid = dataset.append(sample_json)\n\t\t\t\n\t\t\t10.times do |id|\n\t\t\t\tdataset.write(document_path + \"-#{id}\", oid)\n\t\t\tend\n\t\tend\n\t\t\n\t\tdatabase.current do |dataset|\n\t\t\texpect(dataset.each(\"test\").count).to be == 10\n\t\tend\n\tend\n\t\n\tit \"can enumerate commit history of a document\" do\n\t\t10.times do |id|\n\t\t\tdatabase.commit(message: \"revising the document #{id}\") do |changeset|\n\t\t\t\toid = changeset.append(\"revision \\##{id} of this document\")\n\t\t\t\tchangeset.write(\"test/doot.txt\", oid)\n\t\t\tend\n\t\tend\n\t\t\n\t\tdatabase.commit(message: \"unrelated commit\") do |changeset|\n\t\t\toid = changeset.append(\"unrelated document\")\n\t\t\tchangeset.write(\"test/unrelated.txt\", oid)\n\t\tend\n\t\t\n\t\texpect(database.history(\"test/doot.txt\").count).to be == 10\n\tend\nend\n"
  },
  {
    "path": "test/relaxo/enumeration.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2017-2025, by Samuel Williams.\n\nrequire \"relaxo/test_records\"\n\ndescribe Relaxo::Dataset do\n\tinclude_context Relaxo::TestRecords\n\t\n\tit \"should enumerate all documents\" do\n\t\trecords = []\n\t\t\n\t\tdatabase.current do |dataset|\n\t\t\trecords = dataset.each(prefix).to_a\n\t\tend\n\t\t\n\t\texpect(records.count).to be == 20\n\tend\nend\n\ndescribe Relaxo::Changeset do\n\tinclude_context Relaxo::TestRecords\n\t\n\tit \"should enumerate all documents\" do\n\t\trecords = []\n\t\t\n\t\tdatabase.commit(message: \"Testing Enumeration\") do |dataset|\n\t\t\trecords = dataset.each(prefix).to_a\n\t\tend\n\t\t\n\t\texpect(records.count).to be == 20\n\tend\nend\n"
  },
  {
    "path": "test/relaxo.rb",
    "content": "# frozen_string_literal: true\n\n# Released under the MIT License.\n# Copyright, 2025, by Samuel Williams.\n\nrequire \"relaxo/test_records\"\n\ndescribe Relaxo do\n\twith \".connect\" do\n\t\tinclude Relaxo::TemporaryDatabase\n\t\t\n\t\tit \"can connect to a new database\" do\n\t\t\texpect(database).to be_a Relaxo::Database\n\t\t\texpect(database.branch).to (be == \"main\").or(be == \"master\")\n\t\tend\n\t\t\n\t\tit \"can connect to a new database with an alternative branch name\" do\n\t\t\tRelaxo.connect(database_path, branch: \"development\")\n\t\t\t\n\t\t\texpect(database).to be_a Relaxo::Database\n\t\t\texpect(database.branch).to be == \"development\"\n\t\tend\n\tend\nend\n"
  }
]