├── .circleci └── config.yml ├── .gitignore ├── .hgtags ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── bundle.json ├── docs ├── .gitignore ├── 404.html ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── _config.yml ├── _data │ └── docs.yml ├── _docs │ ├── start.md │ ├── start │ │ ├── clustering.md │ │ ├── compile.md │ │ ├── connect.md │ │ ├── disk-persistence.md │ │ ├── docker.md │ │ └── download.md │ ├── types.md │ └── types │ │ ├── gcount.md │ │ ├── mvreg.md │ │ ├── pncount.md │ │ ├── system.md │ │ ├── tlog.md │ │ ├── treg.md │ │ └── ujson.md ├── _includes │ ├── doc_stub.html │ ├── docs_nav.html │ ├── footer.html │ ├── head.html │ ├── js_files.html │ ├── section_nav.html │ └── topnav.html ├── _layouts │ ├── default.html │ └── docs.html ├── _sass │ ├── _bootstrap.scss │ ├── _syntax-highlighting.scss │ ├── _typeahead.scss │ ├── bootstrap │ │ ├── _alerts.scss │ │ ├── _badges.scss │ │ ├── _breadcrumbs.scss │ │ ├── _button-groups.scss │ │ ├── _buttons.scss │ │ ├── _carousel.scss │ │ ├── _close.scss │ │ ├── _code.scss │ │ ├── _component-animations.scss │ │ ├── _dropdowns.scss │ │ ├── _forms.scss │ │ ├── _glyphicons.scss │ │ ├── _grid.scss │ │ ├── _input-groups.scss │ │ ├── _jumbotron.scss │ │ ├── _labels.scss │ │ ├── _list-group.scss │ │ ├── _media.scss │ │ ├── _mixins.scss │ │ ├── _modals.scss │ │ ├── _navbar.scss │ │ ├── _navs.scss │ │ ├── _normalize.scss │ │ ├── _pager.scss │ │ ├── _pagination.scss │ │ ├── _panels.scss │ │ ├── _popovers.scss │ │ ├── _print.scss │ │ ├── _progress-bars.scss │ │ ├── _responsive-embed.scss │ │ ├── _responsive-utilities.scss │ │ ├── _scaffolding.scss │ │ ├── _tables.scss │ │ ├── _theme.scss │ │ ├── _thumbnails.scss │ │ ├── _tooltip.scss │ │ ├── _type.scss │ │ ├── _utilities.scss │ │ ├── _variables.scss │ │ ├── _wells.scss │ │ └── mixins │ │ │ ├── _alerts.scss │ │ │ ├── _background-variant.scss │ │ │ ├── _border-radius.scss │ │ │ ├── _buttons.scss │ │ │ ├── _center-block.scss │ │ │ ├── _clearfix.scss │ │ │ ├── _forms.scss │ │ │ ├── _gradients.scss │ │ │ ├── _grid-framework.scss │ │ │ ├── _grid.scss │ │ │ ├── _hide-text.scss │ │ │ ├── _image.scss │ │ │ ├── _labels.scss │ │ │ ├── _list-group.scss │ │ │ ├── _nav-divider.scss │ │ │ ├── _nav-vertical-align.scss │ │ │ ├── _opacity.scss │ │ │ ├── _pagination.scss │ │ │ ├── _panels.scss │ │ │ ├── _progress-bar.scss │ │ │ ├── _reset-filter.scss │ │ │ ├── _reset-text.scss │ │ │ ├── _resize.scss │ │ │ ├── _responsive-visibility.scss │ │ │ ├── _size.scss │ │ │ ├── _tab-focus.scss │ │ │ ├── _table-row.scss │ │ │ ├── _text-emphasis.scss │ │ │ ├── _text-overflow.scss │ │ │ └── _vendor-prefixes.scss │ └── bootswatch │ │ ├── LICENSE │ │ └── custom │ │ ├── _bootswatch.scss │ │ └── _variables.scss ├── css │ ├── font-awesome.min.css │ └── main.scss ├── favicon.ico ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── img │ └── bg.jpg ├── index.html ├── js │ ├── bootstrap.min.js │ ├── main.js │ └── typeahead.bundle.min.js └── search.json ├── jylis ├── _conn.pony ├── _listen.pony ├── _name_fn.pony ├── _name_tokens_fn.pony ├── address.pony ├── cluster.pony ├── cluster_listen_notify.pony ├── cluster_notify.pony ├── config.pony ├── database.pony ├── database_codec.pony ├── disk.pony ├── dispose.pony ├── framed_notify.pony ├── framing.pony ├── heart.pony ├── help.pony ├── log.pony ├── logo.pony ├── main.pony ├── msg.pony ├── name_generator.pony ├── repo_gcount.pony ├── repo_manager.pony ├── repo_mvreg.pony ├── repo_pncount.pony ├── repo_system.pony ├── repo_tlog.pony ├── repo_treg.pony ├── repo_ujson.pony ├── server.pony ├── server_listen_notify.pony ├── server_notify.pony ├── system.pony └── test │ ├── _expect_respond.pony │ ├── _wait.pony │ ├── main.pony │ ├── test_address.pony │ ├── test_cluster.pony │ ├── test_disk.pony │ ├── test_framing.pony │ ├── test_msg.pony │ └── test_name_generator.pony └── spec ├── cluster_spec.rb ├── disk_spec.rb ├── doc_spec.rb ├── spec_helper.rb ├── support ├── jylis.rb ├── ujson.rb └── util.rb └── system_spec.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | vs-ponyc-master: 4 | docker: 5 | - image: ponylang/ponyc:latest 6 | steps: 7 | - checkout 8 | - run: make ci-setup 9 | - run: make ci 10 | vs-ponyc-release: 11 | docker: 12 | - image: ponylang/ponyc:release 13 | steps: 14 | - checkout 15 | - run: make ci-setup 16 | - run: make ci 17 | nightly-release: 18 | machine: true 19 | steps: 20 | - checkout 21 | - run: make release 22 | 23 | workflows: 24 | version: 2 25 | commit: 26 | jobs: 27 | - vs-ponyc-release 28 | nightly: 29 | triggers: 30 | - schedule: 31 | cron: "0 0 * * *" 32 | filters: 33 | branches: 34 | only: master 35 | jobs: 36 | - vs-ponyc-master 37 | - nightly-release 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | bin 3 | compat 4 | -------------------------------------------------------------------------------- /.hgtags: -------------------------------------------------------------------------------- 1 | 0bb5e5f025a975e8d4bad023c780d906f0148c94 gh-pages 2 | 0bb5e5f025a975e8d4bad023c780d906f0148c94 gh-pages 3 | 0000000000000000000000000000000000000000 gh-pages 4 | 0000000000000000000000000000000000000000 gh-pages 5 | 0000000000000000000000000000000000000000 gh-pages 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.7 2 | 3 | # Install Pony dependencies and other build tools. 4 | ENV LLVM_VERSION=3.9 5 | RUN apk add --update \ 6 | alpine-sdk clang-dev linux-headers libexecinfo-dev binutils-gold \ 7 | libressl-dev pcre2-dev coreutils llvm${LLVM_VERSION}-dev 8 | ENV LLVM_CONFIG=llvm-config-3.9 9 | 10 | # Install Pony compiler and Pony runtime. 11 | ENV PONYC_GIT_URL https://github.com/ponylang/ponyc 12 | RUN git clone --depth 1 ${PONYC_GIT_URL} /tmp/ponyc && \ 13 | cd /tmp/ponyc && \ 14 | env CC=clang make default_pic=true install && \ 15 | rm -rf /tmp/ponyc 16 | 17 | # Install Pony dependency manager (stable). 18 | # TODO: use master branch when this branch has been merged 19 | ENV STABLE_GIT_URL https://github.com/ponylang/pony-stable 20 | RUN git clone --depth 1 ${STABLE_GIT_URL} /tmp/pony-stable && \ 21 | cd /tmp/pony-stable && \ 22 | make config=release install && \ 23 | rm -rf /tmp/pony-stable 24 | 25 | # Build the application as a static binary. 26 | # TODO: use --runtimebc (available only when clang version matches LLVM version) 27 | ENV CC=clang 28 | RUN mkdir /src 29 | WORKDIR /src 30 | COPY Makefile bundle.json /src/ 31 | COPY jylis /src/jylis 32 | RUN stable fetch && stable env ponyc --static -o release jylis 33 | 34 | # Transfer the static binary to a new empty image. 35 | FROM scratch 36 | COPY --from=0 /src/release/jylis . 37 | ENTRYPOINT ["./jylis"] 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: bin/jylis 2 | .PHONY: all test spec clean lldb lldb-test ci ci-setup release 3 | 4 | PKG=jylis 5 | REPO_URL=https://github.com/jemc/jylis 6 | COMPAT_BRANCH=master 7 | 8 | bin/${PKG}: bundle.json $(shell find ${PKG} -name *.pony) 9 | mkdir -p bin 10 | stable env ponyc --debug -o bin ${PKG} 11 | 12 | bin/test: bundle.json $(shell find ${PKG} -name *.pony) 13 | mkdir -p bin 14 | stable env ponyc --debug -o bin ${PKG}/test 15 | 16 | compat/bin/${PKG}: $(shell find compat -name bundle.json) $(shell find compat/${PKG} -name *.pony) 17 | git clone ${REPO_URL} --depth 1 --branch ${COMPAT_BRANCH} compat || \ 18 | git --work-tree compat --git-dir compat/.git pull 19 | mkdir -p compat/bin 20 | stable env ponyc --debug -o compat/bin compat/${PKG} 21 | 22 | test: bin/test 23 | $^ 24 | 25 | spec: bin/${PKG} compat/bin/${PKG} 26 | rspec 27 | 28 | clean: 29 | rm -rf bin 30 | 31 | lldb: 32 | stable env lldb -o run -- $(shell which ponyc) --debug -o /tmp ${PKG}/test 33 | 34 | lldb-test: bin/test 35 | stable env lldb -o run -- bin/test 36 | 37 | ci: test spec 38 | 39 | ci-setup: 40 | apt-get update 41 | apt-get install -y libpcre2-dev ruby 42 | gem install rspec:3.7.0 redis:4.0.1 43 | stable fetch 44 | 45 | bin/${PKG}-release: bundle.json $(shell find ${PKG} -name *.pony) 46 | mkdir -p bin 47 | docker build -t ${PKG}-release . 48 | docker create --name ${PKG}-release ${PKG}-release 49 | docker cp ${PKG}-release:/${PKG} bin/${PKG}-release 50 | docker rm -v ${PKG}-release 51 | 52 | # The `make release` target will update the "nightly" release binary on GitHub. 53 | # It will create a new tag named `nightly-{unix-time}` at current master. 54 | # Remove the binary previously uploaded there, and upload the new one. 55 | GITHUB_RELEASE_ID=10442813 56 | GITHUB_AUTH=-H "Authorization: token ${GITHUB_API_TOKEN}" 57 | GITHUB_API_URL=https://api.github.com/repos/jemc/${PKG} 58 | GITHUB_UPLOADS_URL=https://uploads.github.com/repos/jemc/${PKG} 59 | release: bin/${PKG}-release 60 | @curl -X PATCH ${GITHUB_AUTH} ${GITHUB_API_URL}/releases/${GITHUB_RELEASE_ID} --data '{"tag_name": "nightly-$(shell date +%s)", "target_commitish": "master" }' 61 | @curl -X GET ${GITHUB_AUTH} ${GITHUB_API_URL}/releases/${GITHUB_RELEASE_ID}/assets | jq .[0].id | xargs -I '{id}' \ 62 | curl -X DELETE ${GITHUB_AUTH} ${GITHUB_API_URL}/releases/assets/{id} 63 | @curl -X POST ${GITHUB_AUTH} ${GITHUB_UPLOADS_URL}/releases/${GITHUB_RELEASE_ID}/assets?name=jylis --data-binary @"bin/${PKG}-release" -H "Content-Type: application/octet-stream" 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `>jylis` [![CircleCI](https://circleci.com/gh/jemc/jylis.svg?style=shield)](https://circleci.com/gh/jemc/jylis) [![Docker](https://img.shields.io/docker/automated/jemc/jylis.svg)](https://hub.docker.com/r/jemc/jylis) [![Image Size](https://img.shields.io/microbadger/image-size/jemc/jylis/latest.svg)](https://microbadger.com/images/jemc/jylis) 2 | 3 | Jylis is a distributed in-memory database for 4 | Conflict-free Replicated Data Types (CRDTs), 5 | built for speed, scalability, availability, and ease of use. 6 | 7 | Visit [the website](https://jemc.github.io/jylis/) for more information. 8 | -------------------------------------------------------------------------------- /bundle.json: -------------------------------------------------------------------------------- 1 | { 2 | "deps": [ 3 | { 4 | "type": "github", 5 | "repo": "ponylang/regex" 6 | }, 7 | { 8 | "type": "github", 9 | "repo": "ponylang/glob" 10 | }, 11 | { 12 | "type": "github", 13 | "repo": "jemc/pony-crdt" 14 | }, 15 | { 16 | "type": "github", 17 | "repo": "jemc/pony-inspect" 18 | }, 19 | { 20 | "type": "github", 21 | "repo": "jemc/pony-resp" 22 | }, 23 | { 24 | "type": "github", 25 | "repo": "jemc/pony-jason" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | _site 3 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 |

The page you are looking for cannot be found.

7 |

404

8 |
9 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | ruby RUBY_VERSION 3 | 4 | gem "jekyll", "3.6.3" 5 | 6 | # to use GitHub Pages 7 | # gem "github-pages", group: :jekyll_plugins 8 | 9 | # If you have any plugins, put them here! 10 | group :jekyll_plugins do 11 | gem "jekyll-feed" 12 | gem "jekyll-sitemap" 13 | gem "jekyll-redirect-from" 14 | gem "jekyll-seo-tag" 15 | end 16 | 17 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 18 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 19 | -------------------------------------------------------------------------------- /docs/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | addressable (2.8.0) 5 | public_suffix (>= 2.0.2, < 5.0) 6 | colorator (1.1.0) 7 | ffi (1.15.3) 8 | forwardable-extended (2.6.0) 9 | jekyll (3.6.3) 10 | addressable (~> 2.4) 11 | colorator (~> 1.0) 12 | jekyll-sass-converter (~> 1.0) 13 | jekyll-watch (~> 1.1) 14 | kramdown (~> 1.14) 15 | liquid (~> 4.0) 16 | mercenary (~> 0.3.3) 17 | pathutil (~> 0.9) 18 | rouge (>= 1.7, < 3) 19 | safe_yaml (~> 1.0) 20 | jekyll-feed (0.9.2) 21 | jekyll (~> 3.3) 22 | jekyll-redirect-from (0.12.1) 23 | jekyll (~> 3.3) 24 | jekyll-sass-converter (1.5.2) 25 | sass (~> 3.4) 26 | jekyll-seo-tag (2.2.2) 27 | jekyll (~> 3.3) 28 | jekyll-sitemap (1.1.1) 29 | jekyll (~> 3.3) 30 | jekyll-watch (1.5.1) 31 | listen (~> 3.0) 32 | kramdown (1.17.0) 33 | liquid (4.0.3) 34 | listen (3.5.1) 35 | rb-fsevent (~> 0.10, >= 0.10.3) 36 | rb-inotify (~> 0.9, >= 0.9.10) 37 | mercenary (0.3.6) 38 | pathutil (0.16.2) 39 | forwardable-extended (~> 2.6) 40 | public_suffix (4.0.6) 41 | rb-fsevent (0.11.0) 42 | rb-inotify (0.10.1) 43 | ffi (~> 1.0) 44 | rouge (2.2.1) 45 | safe_yaml (1.0.5) 46 | sass (3.7.4) 47 | sass-listen (~> 4.0.0) 48 | sass-listen (4.0.0) 49 | rb-fsevent (~> 0.9, >= 0.9.4) 50 | rb-inotify (~> 0.9, >= 0.9.7) 51 | 52 | PLATFORMS 53 | ruby 54 | 55 | DEPENDENCIES 56 | jekyll (= 3.6.3) 57 | jekyll-feed 58 | jekyll-redirect-from 59 | jekyll-seo-tag 60 | jekyll-sitemap 61 | tzinfo-data 62 | 63 | RUBY VERSION 64 | ruby 2.3.1p112 65 | 66 | BUNDLED WITH 67 | 1.14.6 68 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Can Güney Aksakalli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Jekyll Doc Theme 2 | 3 | Go to [the website](https://aksakalli.github.io/jekyll-doc-theme/) for detailed information and demo. 4 | 5 | ## Running locally 6 | 7 | You need Ruby and gem before starting, then: 8 | 9 | ```bash 10 | # install bundler 11 | gem install bundler 12 | 13 | # clone the project 14 | git clone https://github.com/aksakalli/jekyll-doc-theme.git 15 | cd jekyll-doc-theme 16 | 17 | # run jekyll with dependencies 18 | bundle exec jekyll serve 19 | ``` 20 | 21 | ## License 22 | 23 | Released under [the MIT license](LICENSE). 24 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Site settings 2 | title: Jylis 3 | email: joe.eli.mac@gmail.com 4 | description: > 5 | A distributed in-memory database for Conflict-free Replicated Data Types (CRDTs). 6 | 7 | baseurl: "/jylis" # the subpath of your site, e.g. /blog/ 8 | url: https://jemc.github.io/jylis # the base hostname & protocol for your site 9 | git_address: https://github.com/jemc/jylis 10 | git_edit_address: https://github.com/jemc/jylis/blob/master/docs 11 | 12 | # theme options from https://bootswatch.com/ 13 | # comment out this to use default Bootstrap 14 | bootwatch: custom 15 | 16 | # Build settings 17 | markdown: kramdown 18 | highlighter: rouge 19 | gems: 20 | - jekyll-feed 21 | - jekyll-redirect-from 22 | - jekyll-seo-tag 23 | - jekyll-sitemap 24 | 25 | exclude: 26 | - Gemfile 27 | - Gemfile.lock 28 | - .idea/ 29 | - .gitignore 30 | - README.md 31 | timezone: Europe/Berlin 32 | defaults: 33 | 34 | - scope: 35 | path: _docs 36 | type: docs 37 | values: 38 | layout: docs 39 | sectionid: docs 40 | seo: 41 | type: "WebPage" 42 | 43 | collections: 44 | docs: 45 | permalink: /:collection/:path/ 46 | output: true 47 | -------------------------------------------------------------------------------- /docs/_data/docs.yml: -------------------------------------------------------------------------------- 1 | - title: Getting Started 2 | index: start 3 | docs: 4 | - download 5 | - docker 6 | - compile 7 | - connect 8 | - clustering 9 | - disk-persistence 10 | 11 | - title: Data Types 12 | index: types 13 | docs: 14 | - treg 15 | - tlog 16 | - gcount 17 | - pncount 18 | - mvreg 19 | - ujson 20 | - system 21 | -------------------------------------------------------------------------------- /docs/_docs/start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | permalink: /docs/start/ 4 | redirect_from: /docs/index.html 5 | --- 6 | 7 | # Getting Started 8 | 9 | If you're here, you're likely interested in trying Jylis for yourself. There are a few good ways do that: 10 | 11 | - For 64-bit Linux machines, you can [download a static binary](download). 12 | 13 | - For other plaforms, the easiest option is to [use the docker image](docker). 14 | 15 | - If you want to hack in your own changes, you'll need to [compile it from source](compile). 16 | 17 | After that, you'll likely want to [connect and try sending some commands](connect). 18 | 19 | ## Next Steps 20 | 21 | Once you've got the basics down, you can read in-depth about [the data types](../types) that Jylis supports, and start thinking about how they might be used in your application. 22 | 23 |
24 |
25 |
26 |

27 |   28 | Work in progress! 29 |

30 |
31 |
32 | Jylis is still unfinished software, and hasn't yet been formally released. Similarly, this guide is also unfinished. The intent is to accurately document the features that currently exist, though at times documentation for some features may be either out of date or not yet written. 33 |
34 |
35 | 36 |
37 |
38 |

39 |   40 | Help is welcome! 41 |

42 |
43 |
44 | We invite collaboration on GitHub to help improve this documentation. Please feel free to file an issue ticket or pull request for documentation you believe is out of date, missing, broken, or could otherwise be improved. In this spirit, you'll find a button like the one below at the bottom of every documentation page to guide you to where that page lives on GitHub, so you can propose changes as a pull request. 45 |
46 |
47 | -------------------------------------------------------------------------------- /docs/_docs/start/clustering.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Clustering 3 | permalink: /docs/start/clustering/ 4 | --- 5 | 6 | # Clustering 7 | 8 | Jylis nodes can be clustered together to increase system availability. Data will automatically replicate between all nodes in the cluster that can connect to each other. Since Jylis is eventually consistent and favors availability, a node that is separated from the cluster will retain the data it was able to replicate before the partition and will continue to respond to reads with the last known value. Likewise, a separated node will also continue to accept writes. Due to the nature of CRDTs, these changes will automatically be reconciled when the node rejoins the cluster. The application may need to decide which value "wins" when fetching this reconciled data, but unlike some other databases no manual repair step is required before the data is available. 9 | 10 | ## Command Line Options 11 | 12 | The following command line options pertain to clustering: 13 | 14 | * `--addr` (`-a`) - The `host:port:name` to be advertised to other clustering nodes. 15 | * `--seed-addrs` (`-s`) - A space-separated list of the `host:port:name` for other known nodes. 16 | * `--heartbeat-time` (`-H`) - The number of seconds between heartbeats in the clustering protocol. 17 | 18 | ## Example Config 19 | 20 | The following is an example `docker-compose.yml` file showing three Jylis nodes clustered together. Each node is exposed to the host on ports `6379`, `6380`, and `6381`, respectively. 21 | 22 | ```yaml 23 | version: "3" 24 | services: 25 | db1: 26 | image: jemc/jylis 27 | ports: 28 | - "6379:6379" 29 | command: 30 | - "--addr=db1:9999:db1" 31 | - "--seed-addrs=db2:9999:db2 db3:9999:db3" 32 | 33 | db2: 34 | image: jemc/jylis 35 | ports: 36 | - "6380:6379" 37 | command: 38 | - "--addr=db2:9999:db2" 39 | - "--seed-addrs=db1:9999:db1 db3:9999:db3" 40 | links: 41 | - db1 42 | 43 | db3: 44 | image: jemc/jylis 45 | ports: 46 | - "6381:6379" 47 | command: 48 | - "--addr=db3:9999:db3" 49 | - "--seed-addrs=db1:9999:db1 db2:9999:db2" 50 | links: 51 | - db1 52 | - db2 53 | ``` 54 | 55 | ### Replication In Action 56 | 57 | * Prerequisites: 58 | * Ensure a Redis client is installed. `redis-cli` will be used in this example, but any Redis-compatible client will work. 59 | * Ensure [docker](https://www.docker.com/community-edition#/download) is installed. 60 | * Ensure [docker-compose](https://docs.docker.com/compose/install/) is installed. 61 | 62 | * Save the `docker-compose.yml` file above to a directory. 63 | 64 | * Bring up the cluster with `docker-compose up -d`. 65 | 66 | * Connect to `db1` with the command `redis-cli -p 6379`. The client will display the port number on the CLI prompt, which shows you are in fact connected to `db1`: `127.0.0.1:6379>` 67 | 68 | * Enter the query: `MVREG SET test 14`. The server will respond `OK`. 69 | 70 | * Exit the database CLI with `Ctrl + D`. 71 | 72 | * Connect to `db2` with the command `redis-cli -p 6380`. 73 | 74 | * Enter the query: `MVREG GET test`. The server will respond `1) "14"`. 75 | -------------------------------------------------------------------------------- /docs/_docs/start/compile.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Compiling from Source 3 | permalink: /docs/start/compile/ 4 | --- 5 | 6 | # Compiling from Source 7 | 8 | ## Get Pony 9 | 10 | Jylis is written in the [Pony programming language](https://www.ponylang.org/), so you'll need to install a working Pony compiler [using a method appropriate to your platform](https://github.com/ponylang/ponyc/blob/master/README.md#installation). It's generally best to use the latest "bleeding edge" master branch of the Pony compiler, or at least the latest stable release. 11 | 12 | ## Get Stable 13 | 14 | Jylis uses a dependency manager for Pony called [Stable](https://github.com/ponylang/pony-stable) to fetch and manage Pony libraries, so you'll also need to [install](https://github.com/ponylang/pony-stable#installation) it as well. 15 | 16 | ## Clone & Build 17 | 18 | Once you've got access to `ponyc` and `stable` in your development environment, you're ready to clone the Jylis source code to your machine and compile it. 19 | 20 | ```bash 21 | git clone https://github.com/jemc/jylis 22 | cd jylis 23 | stable fetch 24 | make 25 | ``` 26 | 27 | After doing so, you should be the proud new owner of a compiled Jylis binary, sitting at `bin/jylis` in the project directory. 28 | 29 | ## Run Tests 30 | 31 | If you want to work on hacking some changes into Jylis, you'll want to be able to run the test suite. You can do so by running `make test` in the project directory where you cloned the source code. Note that you'll need to have run `stable fetch` first to fetch any missing or outdated libraries. 32 | -------------------------------------------------------------------------------- /docs/_docs/start/connect.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Connecting with a Client 3 | permalink: /docs/start/connect/ 4 | --- 5 | 6 | # Connecting with a Client 7 | 8 | Once you've got a working `jylis` binary (or docker container), you'll want to set up a client that can connect to the running server and issue commands. 9 | 10 | Jylis uses the [RESP protocol](https://redis.io/topics/protocol) created and popularized by [Redis](https://redis.io) for client/server communication. There are [many clients](https://redis.io/clients) available for Redis, and any of them should be compatible with Jylis, provided that they let you issue arbitrary commands - the data types and commands supported by Jylis are different from those of Redis, but the protocol is the same. 11 | 12 | By default, the Jylis server will listen on the same default port as Redis (`6379`), so Redis clients should connect easily, even with default configuration. 13 | 14 | One particularly convenient client for demonstration purposes is [`redis-cli`](https://redis.io/topics/rediscli), a small CLI program that ships with Redis. Running both `jylis` and `redis-cli` with the default options should set up an interactive session between the client and server on port `6379`. 15 | 16 | ## Sending Commands 17 | 18 | Once you've established a connection to the Jylis server, you can try to send your first command. We know that [`GCOUNT`](../../types/gcount) is one of the [supported data types](../../types), so let's just type that and see what happens. 19 | 20 | ```sh 21 | 127.0.0.1:6379> GCOUNT 22 | (error) BADCOMMAND (could not parse command) 23 | The following are valid operations for this data type 24 | GCOUNT INC key value 25 | GCOUNT GET key 26 | ``` 27 | 28 | We got an error telling us that the command was invalid, but it was useful enough to tell us what kind of commands are expected for this data type. Now we can try typing one of those, using an arbitrary key (`mykey`): 29 | 30 | ```sh 31 | 127.0.0.1:6379> GCOUNT GET mykey 32 | (integer) 0 33 | ``` 34 | 35 | This is a counter we've never increased, and it didn't even exist before this invocation, but we can see that it's treated as having a base value of zero. Now, let's try increasing it, then reading back the increased value: 36 | 37 | ```sh 38 | 127.0.0.1:6379> GCOUNT INC mykey 10 39 | OK 40 | 127.0.0.1:6379> GCOUNT GET mykey 41 | (integer) 10 42 | ``` 43 | 44 | And, just to confirm everything's working as expected, we can try increasing and reading it again: 45 | 46 | ```sh 47 | 127.0.0.1:6379> GCOUNT INC mykey 15 48 | OK 49 | 127.0.0.1:6379> GCOUNT GET mykey 50 | (integer) 25 51 | ``` 52 | 53 | The example we worked through here was taken from [the documentation for the `GCOUNT`](../../types/gcount#examples) data type. Every data type documentation page includes such examples, and working through them interactively in this way is a great way to get acquainted with those data types. 54 | -------------------------------------------------------------------------------- /docs/_docs/start/disk-persistence.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Disk Persistence 3 | permalink: /docs/start/disk-persistence/ 4 | --- 5 | 6 | # Data Persistence 7 | 8 | Although Jylis is an in-memory database, disk persistence is available as an opt-in feature. Enabling persistence writes data to disk, which is read back into memory when the server restarts. To enable disk persistence, start Jylis with the `--disk-dir=` CLI option. 9 | 10 | ```text 11 | jylis --disk-dir= 12 | ``` 13 | 14 | Even without disk persistence, a Jylis cluster can still retain data beyond a node failure. As long as other nodes in the cluster have a combined view of the data in their collective memory, they will refill the failed node with data when it restarts. Adding disk persistence to one or more nodes adds another dimension of durability, at the cost of increased overhead for operations on those nodes. 15 | -------------------------------------------------------------------------------- /docs/_docs/start/docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Running in Docker 3 | permalink: /docs/start/docker/ 4 | --- 5 | 6 | # Running in Docker 7 | 8 | A [docker](https://www.docker.com/) image of Jylis is available on Docker Hub at [jemc/jylis](https://hub.docker.com/r/jemc/jylis/). 9 | 10 | Start by pulling the latest image: 11 | 12 | ```bash 13 | docker pull jemc/jylis 14 | ``` 15 | 16 | Now start a Jylis container: 17 | 18 | ```bash 19 | docker run -p 6379:6379 jemc/jylis 20 | ``` 21 | 22 | The container is now ready to accept connections on port `6379`. Pressing `Ctrl + C` will shut down the container. Adding the `-d` flag before the image name will start the container in the background. 23 | 24 | ## docker-compose 25 | 26 | This section demonstrates a minimal [docker-compose](https://docs.docker.com/compose/) file that will start Jylis and use a Redis CLI to connect to the database. Create a new directory and copy the following code to `docker-compose.yml`: 27 | 28 | ```yaml 29 | version: "3" 30 | services: 31 | db: 32 | image: jemc/jylis 33 | ports: 34 | - 6379:6379 35 | 36 | cli: 37 | image: redis:alpine 38 | restart: "no" 39 | command: redis-cli -h db 40 | links: 41 | - db 42 | ``` 43 | 44 | Start Jylis in the background: 45 | 46 | ```bash 47 | docker-compose up -d 48 | ``` 49 | 50 | Run the Redis CLI: 51 | 52 | ```bash 53 | docker-compose run cli 54 | ``` 55 | 56 | Jylis is exposed to the system on port `6379` if you would like to connect additional applications to the database. 57 | -------------------------------------------------------------------------------- /docs/_docs/start/download.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Download (for Linux) 3 | permalink: /docs/start/download/ 4 | --- 5 | 6 | # Download (for Linux) 7 | 8 | If you intend to run Jylis in a 64-bit Linux environment, you can download it as a static binary with no dependencies. 9 | 10 | Jylis is in pre-release, so there are no actual releases available to download, but you can download the latest nightly build of the static binary from [the releases page on GitHub](https://github.com/jemc/jylis/releases). Find the "Nightly" release there and download the `jylis` binary from the Assets list. 11 | 12 | Note that you'll need to mark the downloaded file as executable before you can run it. You can do this with a command like `chmod a+x jylis`. 13 | -------------------------------------------------------------------------------- /docs/_docs/types.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Data Types 3 | permalink: /docs/types/ 4 | --- 5 | 6 | # Data Types 7 | 8 | Jylis is a database for CRDTs (Conflict-free Replicated Data Types). Each data type in Jylis defines certain constraints about how it can be used as well as a strategy for merging data from distributed replicas such that each replica is guaranteed to converge to the same result after sufficient information propagation between replicas. This is known as *eventual consistency*. 9 | 10 | The primary advantage of CRDTs is that updating data in any replica never requires synchronizing the update with other replicas. In other words, consensus between nodes in the cluster is never required. As a result, we need not pay any penalty to availability or request latency even when working with a distributed cluster over a faulty, high latency network. Information will propagate between replicas asynchronously in the background, and they will eventually reach the same result after overcoming the faults and latency between cluster nodes. 11 | 12 | The primary disadvantage of CRDTs is the aforementioned constraints that they impose on the operations that can be performed on them. The merge strategy for each data type must be both commutative and idempotent, and only change operations that are compatible with this strategy may be allowed. Each data type is designed to have constraints that make it as useful as possible for a particular kind of application, but some operations that would potentially be very useful cannot be allowed. 13 | 14 | Choosing a data type for your application is a matter of carefully reviewing the semantics, including the operations that are available and their respective constraints and caveats, and selecting a data type whose semantics match your application needs. 15 | 16 | If you find that none of the available data types provide the semantics you want, it may be that it is not possible to provide the desired behaviour with a CRDT. Alternatively, it may be that such a data type merely has not been crafted yet. If you think you have an idea for a valid and useful CRDT that is not represented in Jylis yet, please feel free to file an issue ticket for discussing the idea. 17 | -------------------------------------------------------------------------------- /docs/_docs/types/gcount.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GCOUNT - Grow-Only Counter 3 | permalink: /docs/types/gcount/ 4 | --- 5 | 6 | # `GCOUNT` - Grow-Only Counter 7 | 8 | A grow-only counter holds an integer value that can only be increased. 9 | 10 | Every `INC` operation increases the value associated with the node that processed the operation, and the value associated with each node is tracked separately. A `GET` operation will return the sum of all such values to determine the total increase for that counter across all nodes. As nodes share information and their view of the others' values is updated, they will eventually converge to the same total value. 11 | 12 | The value of the counter is a 64-bit unsigned integer. 13 | 14 | ## Related Data Types 15 | 16 | - [`PNCOUNT`](../pncount) is a similar data type that allows both increasing and decreasing the value. 17 | 18 | ## Functions 19 | 20 | ### `GET key` 21 | 22 | Get the resulting `value` for the counter at `key`. 23 | 24 | Returns a 64-bit unsigned integer, which will be `0` if this counter has never been increased. 25 | 26 | ### `INC key value` 27 | 28 | Increase the counter at `key` by the amount of `value`. 29 | 30 | Returns a simple string reply of `OK`. 31 | 32 | ## Examples 33 | 34 | ```sh 35 | jylis> GCOUNT GET mykey 36 | (integer) 0 37 | jylis> GCOUNT INC mykey 10 38 | OK 39 | jylis> GCOUNT GET mykey 40 | (integer) 10 41 | jylis> GCOUNT INC mykey 15 42 | OK 43 | jylis> GCOUNT GET mykey 44 | (integer) 25 45 | ``` 46 | 47 | ## Detailed Semantics 48 | 49 | - A map of values is held in the counter, where each value in the map is associated with a particular node identity. 50 | 51 | - Two maps `A` and `B` are merged by selecting the higher value for every unique node identity present in either or both maps. 52 | 53 | ## Caveats & Pitfalls 54 | 55 | - As with all eventually-consistent data types, you can't guarantee that the value read from any two nodes will be the same. Changes take time to propagate through the distributed system, and your application should be written with the expectation of seeing inconsistent values. In general, you should assume that nothing is immediate, nothing is atomic, and nothing is ordered. 56 | 57 | - Because the value is an unsigned 64-bit integer, the maximum value that can be represented is limited. If any one node is increased more than that maximum value, or if the sum of all node-local values is greater than that maximum, the value of the counter will be saturated at that maximum value, and any further increases will not result in an increase of the total value. That is, integer overflows are caught, and any additional increase over the maximum is ignored. 58 | 59 | - When designing a solution, an average "worst case" rate of increase for the counter should be calculated in order to ensure that it isn't possible for the local value of the counter to reach the 64-bit integer maximum during the life of the application. 60 | -------------------------------------------------------------------------------- /docs/_docs/types/mvreg.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: MVREG - Multi-Value Register 3 | permalink: /docs/types/mvreg/ 4 | --- 5 | 6 | # `MVREG` - Multi-Value Register
(Observed-Remove Set) 7 | 8 | A multi-value register holds a latest value in causal history. This will often be just one value, but may be multiple values if the register was updated concurrently. 9 | 10 | Every `SET` operation provides a new value to be assigned to the register. Any locally observable values in the register are removed, so that the only local value is the new desired value. However, if a different value was concurrently written to another replica, there is no clear causal relation between the two new values, and both will be retained. In other words, a multi-value register will usually only have one value, but in the case of conflict a `GET` operation will return multiple values and it will be the job of the application to handle them appropriately. 11 | 12 | ## Related Data Types 13 | 14 | - [`TREG`](../treg) is a register that is guaranteed to always hold a single value, but logical timestamps must be provided to resolve conflicts instead of using causal history, and the conflict resolution is only as valid as those timestamps are. 15 | 16 | - [`UJSON`](../ujson) provides broadly expanded functionality with similar semantics to a collection of deeply nested multi-value registers, but requires a JSON parser to use and adds additional complexity to the solution. 17 | 18 | ## Functions 19 | 20 | ### `GET key` 21 | 22 | Get the latest value(s) for the register at `key`. 23 | 24 | Returns an array of strings, where each string is a latest value that was assigned to the register. The order of the array is arbitrary, and applications should not rely upon it for correctness. 25 | 26 | If the register has no value, an empty array will be returned. 27 | 28 | ### `SET key value` 29 | 30 | Set the latest `value` for the register at `key`. 31 | 32 | Any other value(s) that are locally visible (in the replica where this operation is performed) will be removed, leaving the new desired value as the only locally visible value in that replica. 33 | 34 | Values that are not yet locally visible (because they have yet to propagate from other replicas) will be retained upon eventual propagation, leaving the possibility that multiple values will eventually be present, even though each `SET` operation left only one local value. 35 | 36 | Returns a simple string reply of `OK`. 37 | 38 | ## Examples 39 | 40 | ```sh 41 | jylis> MVREG GET mykey 42 | (empty list) 43 | jylis> MVREG SET mykey "apple" 44 | OK 45 | jylis> MVREG GET mykey 46 | 1) "apple" 47 | jylis> MVREG SET mykey "banana" 48 | OK 49 | jylis> MVREG GET mykey 50 | 1) "banana" 51 | ``` 52 | 53 | ## Detailed Semantics 54 | 55 | - Each change operation is recorded as having happend at a relative point in causal history. 56 | 57 | - A change `A` *precedes* another change `B` if the effects of change `A` were already locally visible on the same replica when change `B` occurred; the two changes are said to have a *causal relationship*. 58 | 59 | - If change `A` and change `B` each happened on a replica where the effects of the other change were not yet locally visible, the two changes have happened concurrently; there is no causal relationship between them. 60 | 61 | - `SET` is a change operation that updates the value of the register and shadows only those values whose change operation preceded it in causal history. 62 | 63 | ## Caveats & Pitfalls 64 | 65 | - As with all eventually-consistent data types, you can't guarantee that the value read from any two nodes will be the same. Changes take time to propagate through the distributed system, and your application should be written with the expectation of seeing inconsistent values. In general, you should assume that nothing is immediate, nothing is atomic, and nothing is ordered. 66 | 67 | - Even though this data type will return an array with exactly one value in the most common case, your application must have a way of handling the case where more than one value is present due to concurrent updates, and the case where no values are present due to the register having not yet received any updates. 68 | -------------------------------------------------------------------------------- /docs/_docs/types/pncount.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: PNCOUNT - Positive/Negative Counter 3 | permalink: /docs/types/pncount/ 4 | --- 5 | 6 | # `PNCOUNT` - Positive/Negative Counter 7 | 8 | A positive/negative counter holds an integer value which can be increased or decreased. 9 | 10 | Every `INC` operation increases the "positive" value associated with the node that processed the operation, and the value associated with each node is tracked separately. Similarly, every `DEC` operation increases the negative value. A `GET` operation will return the sum of all such values to determine the net value for that counter across all nodes. As nodes share information and their view of the others' values is updated, they will eventually converge to the same total value. 11 | 12 | The value of the counter is a 64-bit signed integer. 13 | 14 | ## Related Data Types 15 | 16 | - [`GCOUNT`](../gcount) is a similar data type that allows only increasing the value. 17 | 18 | ## Functions 19 | 20 | ### `GET key` 21 | 22 | Get the resulting `value` for the counter at `key`. 23 | 24 | Returns a 64-bit signed integer, which will be `0` if this counter has never been increased or decreased. 25 | 26 | ### `INC key value` 27 | 28 | Increase the counter at `key` by the amount of `value`. 29 | 30 | Returns a simple string reply of `OK`. 31 | 32 | ### `DEC key value` 33 | 34 | Decrease the counter at `key` by the amount of `value`. 35 | 36 | Returns a simple string reply of `OK`. 37 | 38 | ## Examples 39 | 40 | ```sh 41 | jylis> PNCOUNT GET mykey 42 | (integer) 0 43 | jylis> PNCOUNT INC mykey 10 44 | OK 45 | jylis> PNCOUNT GET mykey 46 | (integer) 10 47 | jylis> PNCOUNT DEC mykey 15 48 | OK 49 | jylis> PNCOUNT GET mykey 50 | (integer) -5 51 | ``` 52 | 53 | ## Detailed Semantics 54 | 55 | - Two map of values are held in the counter, one representing positive growth, and one representing negative growth, where each value in each map is associated with a particular node identity. 56 | 57 | - To converge the data structure, both the positive and negative maps are converged separately. 58 | 59 | - Two positive maps or two negative maps `A` and `B` are merged by selecting the value with the greater magnitude for every unique node identity present in either or both maps. 60 | 61 | ## Caveats & Pitfalls 62 | 63 | - As with all eventually-consistent data types, you can't guarantee that the value read from any two nodes will be the same. Changes take time to propagate through the distributed system, and your application should be written with the expectation of seeing inconsistent values. In general, you should assume that nothing is immediate, nothing is atomic, and nothing is ordered. 64 | 65 | - Because the value is a signed 64-bit integer, the maximum and minimum value that can be represented is limited. If any one node is increased more than that maximum value, or decreased more than that minimum value, the behavior of the data type will become unpredictable as changes propagate from node to node. 66 | 67 | - When designing a solution, an average "worst case" rate of increase and decrease for the counter should be calculated in order to ensure that it isn't possible for the local positive or negative value of the counter to overflow during the life of the application. Remember that increase and decrease are tracked independently, so a counter with equal amounts of increase and decrease may still be at risk to overflow either or both of the opposing positive and negative values. 68 | -------------------------------------------------------------------------------- /docs/_docs/types/system.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SYSTEM - System Functions 3 | permalink: /docs/types/system/ 4 | --- 5 | 6 | # `SYSTEM` - System Functions 7 | 8 | In addition to functions that read and write data to data types, some system-level functions are also provided that may be useful in administering Jylis. 9 | 10 | ## Functions 11 | 12 | ### `GETLOG` 13 | 14 | Read recent entries from the system log, which is an interleaved sequence of log entries from all nodes in the cluster. The system log is collected and represented in the same way as a [`TLOG`](../tlog). 15 | 16 | Local server timestamps (unix time in milliseconds) are used as the logical timestamps that control the total order of the log entries from the cluster, so clock disparity between nodes may impact the final order of entries. 17 | 18 | The system log is kept trimmed to a fixed maximum length to avoid unbounded memory growth. The trim length can be set at startup using the `--system-log-trim` CLI option. 19 | 20 | Returns an array where each element is a two-element array containing the value and timestamp of that entry. 21 | 22 | The values will be returned as string, and the timestamps as integers. 23 | 24 | ### `FORGETALL` 25 | 26 | Clear all data from the local in-memory database for this node. This is intended for use in application testing environments. 27 | 28 | If in a cluster, data will remain in the other nodes, meaning that all of the cleared data will be restored locally over time using the normal anti-entropy repair mechanisms - the local node will still be eventually consistent with the rest of the cluster. 29 | 30 | This command is not meant to be used in conjunction with disk persistence, but if used when disk persistence is enabled, there are no guarantees about how much of the data will remain present on the local disk in the immediate or short term. However, if in a cluster, eventual consistency guarantees with the other nodes in the cluster will still hold as described in the paragraph above. 31 | 32 | Returns a simple string reply of `OK` immediately, while the database is being cleared asynchronously. 33 | -------------------------------------------------------------------------------- /docs/_includes/doc_stub.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 |   6 | Not written yet! 7 |

8 |
9 |
10 | This page is a stub, meaning the documentation hasn't been written yet. Sit tight and know that this topic is on our radar and we intend to write about it soon. 11 |
12 |
13 | -------------------------------------------------------------------------------- /docs/_includes/docs_nav.html: -------------------------------------------------------------------------------- 1 |
2 | {% for section in site.data.docs %} 3 |
4 | {% assign section_url = section.index | prepend:"/docs/" | append:"/" %} 5 | {% assign p = site.docs | where:"url", section_url | first %} 6 | 11 | 12 | {% if page.url contains section_url %} 13 |
14 |
    15 | {% for item in section.docs %} 16 | {% assign item_url = item | prepend:section_url | append:"/" %} 17 | {% assign p = site.docs | where:"url", item_url | first %} 18 | {{ p.title }} 19 | {% endfor %} 20 |
21 |
22 | {% endif %} 23 |
24 | {% endfor %} 25 |
26 | -------------------------------------------------------------------------------- /docs/_includes/footer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

5 | {{ site.title }} {{ site.time | date: '%Y' }} | 6 | Powered by Jekyll Doc Theme 7 |

8 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /docs/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if page.title %}{{ site.title }} - {{ page.title }}{% else %}{{ site.title }}{% endif %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% seo %} 15 | 16 | 30 | 31 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/_includes/js_files.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/_includes/section_nav.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Map grabs the doc sections, giving us an array of arrays. Join, flattens all 3 | the items to a comma delimited string. Split turns it into an array again. 4 | {% endcomment %} 5 | {% assign docs = site.data.docs | map: 'docs' | join: ',' | split: ',' %} 6 | 7 | {% comment %} 8 | Because this is built for every page, lets find where we are in the ordered 9 | document list by comparing url strings. Then if there's something previous or 10 | next, lets build a link to it. 11 | {% endcomment %} 12 | 13 | {% for document in docs %} 14 | {% assign document_url = document | prepend:"/docs/" | append:"/" %} 15 | {% if document_url == page.url %} 16 |