diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..3d959be --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +keyv.js.org diff --git a/README.md b/README.md index a70dcc9..14519c5 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,28 @@

- keyv -
-
+ keyv logo +
+

-> Simple key-value storage with support for multiple backends. - ![Last version](https://img.shields.io/github/tag/keyvhq/keyv.svg?style=flat-square) [![Coverage Status](https://img.shields.io/coveralls/keyvhq/keyv.svg?style=flat-square)](https://coveralls.io/github/keyvhq/keyv) [![NPM Status](https://img.shields.io/npm/dm/@keyvhq/keyv.svg?style=flat-square)](https://www.npmjs.org/package/@keyvhq/keyv) -Keyv provides a consistent interface for key-value storage across multiple backends via storage adapters. It supports TTL based expiry, making it suitable as a cache or a persistent key-value store. +> **Keyv** is a simple key-value storage with support for multiple backend adapters (MySQL, PostgreSQL, SQLite, Redis, Mongo, DynamoDB, Firestore, Memcached, and more). ## Features -There are a few existing modules similar to Keyv, however Keyv is different because it: - -- Isn't bloated. -- Has a simple Promise based API. -- Suitable as a TTL based cache or persistent key-value store. -- [Easily embeddable](#add-cache-support-to-your-module) inside another module. -- Works with any storage that implements the [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) API. -- Handles all JSON types plus `Buffer`. -- Supports namespaces. -- Wide range of [**efficient, well tested**](#official-storage-adapters) storage adapters. -- Connection errors are passed through (db failures won't kill your app). -- Supports the current active LTS version of Node.js or higher. +- It isn't bloated. +- It supports namespaces. +- It supports TTL based expiry. +- It has a simple Promise based API. +- It handles all JSON types plus `Buffer`. +- It's support a [vary of storages](#official-storage-adapters) adapters. +- It can be [easily embed](#add-cache-support-to-your-module) inside another module. +- It works with any storage that implements the [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) API. +- it handles database errors (db failures won't kill your app). +- It supports the current active LTS version of Node.js or higher. +- It's suitable as a TTL based cache or persistent key-value store. ## Installation @@ -45,7 +42,7 @@ npm install @keyvhq/keyv-mysql --save If you don't provide a specific storage adapter, a in-memory storage adapter is used by default. -## Usage +## Getting Started Just create a new Keyv instance, passing your storage adapter: @@ -94,13 +91,15 @@ You can optionally provide your own serialization functions to support extra dat const keyv = new Keyv({ serialize: JSON.stringify, deserialize: JSON.parse }); ``` -**Warning:** Using custom serializers means you lose any guarantee of data consistency. You should do extensive testing with your serialisation functions and chosen storage engine. +!> Using custom serializers means you lose any guarantee of data consistency. You should do extensive testing with your serialisation functions and chosen storage engine. + +## Storage Adapters -## Official Storage Adapters +### Official The official storage adapters are covered by [over 150 integration tests](https://github.com/microlinkhq/keyv/actions/runs/949262324) to guarantee consistent behaviour. They are lightweight, efficient wrappers over the DB clients making use of indexes and native TTLs where available. -## Third-party Storage Adapters +### Community You can also use third-party storage adapters or build your own. Keyv will wrap these storage adapters in TTL functionality and handle complex types internally. @@ -187,42 +186,42 @@ The options object is also passed through to the storage adapter. Check your sto #### options.namespace -Type: `String`
+Type: `String`
Default: `'keyv'` Namespace for the current instance. #### options.ttl -Type: `Number`
+Type: `Number`
Default: `undefined` Default TTL. Can be overridden by specififying a TTL on `.set()`. #### options.serialize -Type: `Function`
+Type: `Function`
Default: `JSONB.stringify` A custom serialization function. #### options.deserialize -Type: `Function`
+Type: `Function`
Default: `JSONB.parse` A custom deserialization function. #### options.store -Type: `Storage adapter instance`
+Type: `Storage adapter instance`
Default: `new Map()` The storage adapter instance to be used by Keyv. #### options.adapter -Type: `String`
+Type: `String`
Default: `undefined` Specify an adapter to use. e.g `'redis'` or `'mongodb'`. @@ -245,7 +244,7 @@ Returns a promise which resolves to the retrieved value. ##### options.raw -Type: `Boolean`
+Type: `Boolean`
Default: `false` If set to true the raw DB object Keyv stores internally will be returned instead of just the value. @@ -278,7 +277,7 @@ Returns an [Async Iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScr ## License -**keyv** © [Microlink](https://microlink.io), Released under the [MIT](https://github.com/microlinkhq/keyv/blob/master/LICENSE.md) License.
+**keyv** © [Microlink](https://microlink.io), Released under the [MIT](https://github.com/microlinkhq/keyv/blob/master/LICENSE.md) License.
Authored and maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyv/contributors). > [microlink.io](https://microlink.io) · GitHub [@MicrolinkHQ](https://github.com/microlinkhq) · Twitter [@microlinkhq](https://twitter.com/microlinkhq) diff --git a/docs/css/style.css b/docs/css/style.css new file mode 100644 index 0000000..cec2832 --- /dev/null +++ b/docs/css/style.css @@ -0,0 +1,562 @@ +@import url("https://rsms.me/inter/inter-ui.css"); + +:root { + /* colors */ + --base: #f5f4f4; + --black: rgba(18, 16, 12, 0.70196); + --gray0: #f9f9f9; + --gray1: #ededee; + --gray2: #e0e1e1; + --gray3: #d2d3d4; + --gray4: #c3c4c5; + --gray5: #b2b3b5; + --gray6: #9fa0a2; + --gray7: #88898c; + --gray8: #6b6c70; + --gray9: #3d3f44; + --gray10: #000; + + /* theme */ + + --primary-color: var(--gray9); + --secondary-color: var(--gray10); + --accent-color: #fc6568; + + --text-color: #121102; + --text-bold-color: var(--gray10); + + --sidebar-color: var(--primary-color); + --sidebar-active-color: var(--secondary-color); + --sidebar-border-color: #5f6368; + --bg-color: var(--base); + + --serif-font: "IBM Plex Sans", sans-serif; + --sans-serif-font: "IBM Plex Sans", sans-serif; + --code-font: Nitti, "Microsoft YaHei", "Roboto Mono", 微软雅黑, monospace; + + --codebox-border-color: var(--text-color); + --codebox-color: var(--text-color); + --codebox-token-var-color: var(--text-color); + --codebox-bg: var(--bg-color); +} + +::selection { + background: #f9e4ac; +} + +::-moz-selection { + background: #f9e4ac; +} + +* { + -webkit-font-smoothing: antialiased; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-text-size-adjust: none; + -webkit-touch-callout: none; + box-sizing: border-box; +} + +.progress { + background-color: var(--primary-color); + height: 2px; + left: 0; + position: fixed; + right: 0; + top: 0; + transition: width 0.2s, opacity 0.4s; + width: 0; + z-index: 5; +} + +body, +html { + height: 100%; +} + +body { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + color: var(--text-color); + font-family: var(--sans-serif-font); + font-size: 16px; + letter-spacing: 0; + margin: 0; + overflow-x: hidden; +} + +img { + max-width: 100%; +} + +.github-corner { + border-bottom: 0; + position: fixed; + right: 0; + text-decoration: none; + top: 0; + z-index: 1; +} + +.github-corner svg { + color: #fff; + fill: var(--accent-color); + height: 80px; + width: 80px; +} + +.github-corner:hover .octo-arm { + animation: a 0.56s ease-in-out; +} + +main { + display: block; + position: relative; + width: 100vw; + height: 100%; + z-index: 0; +} + +.anchor { + display: inline-block; + text-decoration: none; + transition: all 0.3s; +} + +.anchor span { + color: var(--secondary-color); +} + +.anchor:hover { + text-decoration: underline; +} + +.sidebar { + font-family: var(--serif-font); + overflow-y: auto; + padding: 40px 0 0; + top: 0; + bottom: 0; + left: 0; + position: absolute; + transition: transform 0.25s ease-out; + width: 300px; + z-index: 3; +} + +.sidebar .sidebar-nav { + line-height: 2em; + padding-bottom: 40px; +} + +.sidebar ul { + margin: 0; + padding: 0; +} + +.sidebar ul, +.sidebar ul li { + list-style: none; +} + +.sidebar ul li a { + border-bottom: none; + display: block; +} + +.sidebar::-webkit-scrollbar { + width: 4px; +} + +.sidebar::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 4px; +} + +.sidebar:hover::-webkit-scrollbar-thumb { + background: hsla(0, 0%, 53%, 0.4); +} + +.sidebar:hover::-webkit-scrollbar-track { + background: hsla(0, 0%, 53%, 0.1); +} + +.sidebar-toggle { + background-color: transparent; + background-color: var(--bg-color); + border: 0; + outline: none; + padding: 10px; + bottom: 0; + left: 0; + position: absolute; + text-align: center; + transition: opacity 0.3s; + width: 30px; + width: 284px; + z-index: 4; +} + +.sidebar-toggle .sidebar-toggle-button:hover { + opacity: 0.4; +} + +.sidebar-toggle span { + background-color: var(--primary-color); + display: block; + margin-bottom: 4px; + width: 16px; + height: 2px; +} + +body.sticky .sidebar, +body.sticky .sidebar-toggle { + position: fixed; +} + +.content { + padding-top: 60px; + top: 0; + right: 0; + bottom: 0; + left: 300px; + position: absolute; + transition: left 0.25s ease; +} + +.markdown-section { + margin: 0 auto; + max-width: 800px; + padding: 30px 15px 40px; + position: relative; +} + +.markdown-section>* { + box-sizing: border-box; + font-size: inherit; +} + +.markdown-section> :first-child { + margin-top: 0 !important; +} + +.markdown-section p.tip { + background-color: #f8f8f8; + border-bottom-right-radius: 2px; + border-left: 4px solid var(--accent-color); + border-top-right-radius: 2px; + margin: 2em 0; + padding: 12px 24px 12px 30px; + position: relative; +} + +.markdown-section p.tip:before { + background-color: var(--accent-color); + border-radius: 100%; + color: #fff; + content: "!"; + font-family: Dosis, Source Sans Pro, Helvetica Neue, Arial, sans-serif; + font-size: 14px; + font-weight: 700; + left: -12px; + line-height: 20px; + position: absolute; + width: 20px; + height: 20px; + text-align: center; + top: 14px; +} + +@media print { + + .github-corner, + .sidebar, + .sidebar-toggle { + display: none; + } +} + +@media screen and (max-width: 768px) { + + .github-corner, + .sidebar, + .sidebar-toggle { + position: fixed; + } + + main { + height: auto; + overflow-x: hidden; + } + + .sidebar { + left: -300px; + transition: transform 0.25s ease-out; + } + + .content { + left: 0; + max-width: 100vw; + position: static; + padding-top: 20px; + transition: transform 0.25s ease; + } + + .github-corner { + transition: transform 0.25s ease-out; + } + + .sidebar-toggle { + background-color: transparent; + width: auto; + } + + .github-corner .octo-arm { + animation: a 0.56s ease-in-out; + } + + .github-corner:hover .octo-arm { + animation: none; + } +} + +@keyframes a { + + 0%, + to { + transform: rotate(0); + } + + 20%, + 60% { + transform: rotate(-25deg); + } + + 40%, + 80% { + transform: rotate(10deg); + } +} + +.sidebar, +body { + background-color: var(--bg-color); + color: var(--primary-color); +} + +.sidebar { + color: #364149; +} + +.sidebar li { + margin: 6px 0 6px 15px; +} + +.sidebar ul li a { + color: var(--sidebar-color); + font-size: 14px; + font-weight: 400; + overflow: hidden; + text-decoration: none; + text-overflow: ellipsis; + white-space: nowrap; +} + +.sidebar ul li a:hover { + text-decoration: underline; +} + +.sidebar ul li.active>a { + border-right: 2px solid; + color: var(--sidebar-active-color); + font-weight: 700; +} + +.markdown-section h1, +.markdown-section h2, +.markdown-section h3, +.markdown-section h4, +.markdown-section h5 { + font-family: var(--serif-font); +} + +.markdown-section strong { + font-family: var(--sans-serif-font); + color: var(--text-bold-color); +} + +.markdown-section a { + color: var(--primary-color); + text-decoration: none; + font-weight: 600; +} + +.markdown-section li a, +.markdown-section p a { + color: var(--accent-color); + text-decoration: underline; + text-underline-offset: 1px; +} + +.markdown-section li a:hover, +.markdown-section p a:hover { + opacity: 0.8; +} + +.markdown-section h1 { + font-size: 2rem; + margin: 4rem 0 1rem; +} + +.markdown-section h2 { + font-size: 1.75rem; + margin: 3.5rem 0 1rem; +} + +.markdown-section h3 { + font-size: 1.5rem; + margin: 3rem 0 1rem; +} + +.markdown-section h4 { + font-size: 1.25rem; + margin: 2.5rem 0 1rem; +} + +.markdown-section h5 { + font-size: 1rem; +} + +.markdown-section p { + margin: 1.2em 0; +} + +.markdown-section p, +.markdown-section ul { + line-height: 1.8rem; + word-spacing: 0.05rem; +} + +.markdown-section ul li { + margin-bottom: 10px; +} + +.markdown-section ul { + padding-left: 2rem; +} + +.markdown-section blockquote { + border-left: 4px solid var(--primary-color); + margin: 2em 0; + padding-left: 20px; +} + +.markdown-section blockquote p { + font-weight: 400; + margin-left: 0; + padding: 12px 0; +} + +.markdown-section li code, +.markdown-section p code { + color: var(--codebox-token-var-color); + border: 1px solid var(--codebox-border-color); + background: var(--codebox-bg); + font-size: 0.75rem; + padding: 3px 10px; + border-radius: 3px; + white-space: nowrap; +} + +.markdown-section code { + border-radius: 2px; + color: var(--codebox-token-var-color); + font-size: 0.8rem; + margin: 0 2px; + padding: 3px 5px; + white-space: pre-wrap; +} + +.markdown-section pre { + border-radius: 5px; + background-color: var(--codebox-bg); + border: 1px solid var(--codebox-border-color); +} + +.markdown-section code, +.markdown-section pre { + font-family: var(--code-font); +} + +.markdown-section pre { + line-height: 1.5rem; + margin: 1.2em 0; + overflow: auto; + padding: 0 0.7rem; + position: relative; + word-wrap: normal; +} + +.markdown-section pre, +.markdown-section pre>code { + -moz-osx-font-smoothing: initial; + -webkit-font-smoothing: initial; +} + +.markdown-section pre>code { + background-color: var(--codebox-bg); + border-radius: 2px; + color: var(--codebox-token-var-color); + display: block; + font-family: var(--code-font); + font-size: 0.8rem; + line-height: inherit; + margin: 0 2px; + max-width: inherit; + overflow: inherit; + padding: 1.25em 5px; + white-space: inherit; +} + +.markdown-section code:after, +.markdown-section code:before { + letter-spacing: 0.8px; + letter-spacing: 0.05rem; +} + +.token.class-name, +.token.function, +.token.number { + color: #d46e00; +} + +.token.string { + color: #d40066; +} + +.token.constant, +.token.keyword { + color: #e00400; +} + +.token.comment { + color: var(--gray7); +} + +pre:after { + color: #ccc; + content: attr(data-lang); + font-size: 0.6rem; + font-weight: 600; + height: 15px; + line-height: 15px; + padding: 5px 10px 0; + position: absolute; + right: 0; + text-align: right; + top: 0; +} + +.sidebar ul ul { + margin-left: 15px; +} diff --git a/docs/js/main.js b/docs/js/main.js new file mode 100644 index 0000000..bce08c7 --- /dev/null +++ b/docs/js/main.js @@ -0,0 +1,16 @@ +/* global codecopy */ + +window.$docsify = { + repo: 'microlinkhq/keyv', + maxLevel: 3, + executeScript: true, + auto2top: true, + noEmoji: true, + plugins: [ + function (hook, vm) { + hook.ready(function () { + codecopy('pre') + }) + } + ] +} diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..3195099 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,48 @@ +'use strict' + +const strip = require('gulp-strip-css-comments') +const prefix = require('gulp-autoprefixer') +const cssnano = require('gulp-cssnano') +const uglify = require('gulp-uglify') +const concat = require('gulp-concat') +const gulp = require('gulp') + +const src = { + css: ['docs/css/style.css'], + js: ['docs/js/main.js'] +} + +const dist = { + path: 'static', + name: { + css: 'style', + js: 'main' + } +} + +const styles = () => + gulp + .src(src.css) + .pipe(concat(`${dist.name.css}.min.css`)) + .pipe(prefix()) + .pipe(strip({ all: true })) + .pipe(cssnano()) + .pipe(gulp.dest(dist.path)) + +const scripts = () => + gulp + .src(src.js) + .pipe(concat(`${dist.name.js}.min.js`)) + .pipe(uglify()) + .pipe(gulp.dest(dist.path)) + +const build = gulp.parallel(styles, scripts) + +function watch () { + gulp.watch(src.css, styles) + gulp.watch(src.js, scripts) +} + +module.exports.default = gulp.series(build, watch) +module.exports.build = build +module.exports.watch = watch diff --git a/index.html b/index.html new file mode 100644 index 0000000..c31951a --- /dev/null +++ b/index.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + Keyv is a simple key-value storage with support for multiple backend adapters (MySQL, PostgreSQL, SQLite, Redis, Mongo, DynamoDB, Firestore, Memcached, and more). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + diff --git a/media/logo-sunset.svg b/media/logo-sunset.svg new file mode 100644 index 0000000..5839e36 --- /dev/null +++ b/media/logo-sunset.svg @@ -0,0 +1,16 @@ + + + logo + + + + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index c8f87c2..ecb9a38 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,15 @@ "lerna": "latest", "lint-staged": "latest", "simple-git-hooks": "latest", - "standard": "latest" + "standard": "latest", + "gulp": "latest", + "gulp-autoprefixer": "latest", + "gulp-concat": "latest", + "gulp-cssnano": "latest", + "gulp-strip-css-comments": "latest", + "gulp-uglify": "latest", + "browser-sync": "latest", + "concurrently": "latest" }, "engines": { "node": ">= 12" @@ -118,6 +126,9 @@ "packages/**" ], "scripts": { + "build": "gulp build", + "dev": "concurrently \"gulp\" \"npm run dev:server\"", + "dev:server": "browser-sync start --server --files \"index.html, README.md, static/**/*.(css|js)\"", "clean": "lerna clean --yes && rm -rf node_modules", "contributors": "(lerna exec finepack --parallel && git-authors-cli && finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true", "install": "lerna bootstrap --no-ci --force-local", diff --git a/static/banner.jpg b/static/banner.jpg new file mode 100644 index 0000000..80ead47 Binary files /dev/null and b/static/banner.jpg differ diff --git a/static/banner.png b/static/banner.png new file mode 100644 index 0000000..f8c1a23 Binary files /dev/null and b/static/banner.png differ diff --git a/static/favicon-16x16.png b/static/favicon-16x16.png new file mode 100644 index 0000000..52b8a6a Binary files /dev/null and b/static/favicon-16x16.png differ diff --git a/static/favicon-32x32.png b/static/favicon-32x32.png new file mode 100644 index 0000000..a7fdc81 Binary files /dev/null and b/static/favicon-32x32.png differ diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..da5215d Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/logo.jpg b/static/logo.jpg new file mode 100644 index 0000000..f6b7135 Binary files /dev/null and b/static/logo.jpg differ diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000..de76afe Binary files /dev/null and b/static/logo.png differ diff --git a/static/main.min.js b/static/main.min.js new file mode 100644 index 0000000..169a3b8 --- /dev/null +++ b/static/main.min.js @@ -0,0 +1 @@ +window.$docsify={repo:"microlinkhq/keyv",maxLevel:3,executeScript:!0,auto2top:!0,noEmoji:!0,plugins:[function(o,e){o.ready(function(){codecopy("pre")})}]}; \ No newline at end of file diff --git a/static/style.min.css b/static/style.min.css new file mode 100644 index 0000000..e345f7e --- /dev/null +++ b/static/style.min.css @@ -0,0 +1 @@ +@import url("https://rsms.me/inter/inter-ui.css");:root{--base:#f5f4f4;--black:rgba(18,16,12,.70196);--gray0:#f9f9f9;--gray1:#ededee;--gray2:#e0e1e1;--gray3:#d2d3d4;--gray4:#c3c4c5;--gray5:#b2b3b5;--gray6:#9fa0a2;--gray7:#88898c;--gray8:#6b6c70;--gray9:#3d3f44;--gray10:#000;--primary-color:var(--gray9);--secondary-color:var(--gray10);--accent-color:#fc6568;--text-color:#121102;--text-bold-color:var(--gray10);--sidebar-color:var(--primary-color);--sidebar-active-color:var(--secondary-color);--sidebar-border-color:#5f6368;--bg-color:var(--base);--serif-font:"IBM Plex Sans",sans-serif;--sans-serif-font:"IBM Plex Sans",sans-serif;--code-font:Nitti,"Microsoft YaHei","Roboto Mono",微软雅黑,monospace;--codebox-border-color:var(--text-color);--codebox-color:var(--text-color);--codebox-token-var-color:var(--text-color);--codebox-bg:var(--bg-color)}::selection{background:#f9e4ac}::-moz-selection{background:#f9e4ac}*{-webkit-font-smoothing:antialiased;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none;box-sizing:border-box}.progress{background-color:var(--primary-color);height:2px;left:0;position:fixed;right:0;top:0;transition:width .2s,opacity .4s;width:0;z-index:4}body,html{height:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:var(--text-color);font-family:var(--sans-serif-font);font-size:16px;letter-spacing:0;margin:0;overflow-x:hidden}img{max-width:100%}.github-corner{border-bottom:0;position:fixed;right:0;text-decoration:none;top:0;z-index:1}.github-corner svg{color:#fff;fill:var(--accent-color);height:80px;width:80px}.github-corner:hover .octo-arm{animation:a .56s ease-in-out}main{display:block;position:relative;width:100vw;height:100%;z-index:0}.anchor{display:inline-block;text-decoration:none;transition:all .3s}.anchor span{color:var(--secondary-color)}.anchor:hover{text-decoration:underline}.sidebar{font-family:var(--serif-font);overflow-y:auto;padding:40px 0 0;top:0;bottom:0;left:0;position:absolute;transition:transform .25s ease-out;width:300px;z-index:2}.sidebar .sidebar-nav{line-height:2em;padding-bottom:40px}.sidebar ul{margin:0;padding:0}.sidebar ul,.sidebar ul li{list-style:none}.sidebar ul li a{border-bottom:none;display:block}.sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:transparent;border-radius:4px}.sidebar:hover::-webkit-scrollbar-thumb{background:hsla(0,0%,53%,.4)}.sidebar:hover::-webkit-scrollbar-track{background:hsla(0,0%,53%,.1)}.sidebar-toggle{background-color:transparent;background-color:var(--bg-color);border:0;outline:none;padding:10px;bottom:0;left:0;position:absolute;text-align:center;transition:opacity .3s;width:30px;width:284px;z-index:3}.sidebar-toggle .sidebar-toggle-button:hover{opacity:.4}.sidebar-toggle span{background-color:var(--primary-color);display:block;margin-bottom:4px;width:16px;height:2px}body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}.content{padding-top:60px;top:0;right:0;bottom:0;left:300px;position:absolute;transition:left .25s ease}.markdown-section{margin:0 auto;max-width:800px;padding:30px 15px 40px;position:relative}.markdown-section>*{box-sizing:border-box;font-size:inherit}.markdown-section>:first-child{margin-top:0!important}.markdown-section p.tip{background-color:#f8f8f8;border-bottom-right-radius:2px;border-left:4px solid var(--accent-color);border-top-right-radius:2px;margin:2em 0;padding:12px 24px 12px 30px;position:relative}.markdown-section p.tip:before{background-color:var(--accent-color);border-radius:100%;color:#fff;content:"!";font-family:Dosis,Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:14px;font-weight:700;left:-12px;line-height:20px;position:absolute;width:20px;height:20px;text-align:center;top:14px}@media print{.github-corner,.sidebar,.sidebar-toggle{display:none}}@media screen and (max-width:768px){.github-corner,.sidebar,.sidebar-toggle{position:fixed}main{height:auto;overflow-x:hidden}.sidebar{left:-300px;transition:transform .25s ease-out}.content{left:0;max-width:100vw;position:static;padding-top:20px;transition:transform .25s ease}.github-corner{transition:transform .25s ease-out}.sidebar-toggle{background-color:transparent;width:auto}.github-corner .octo-arm{animation:a .56s ease-in-out}.github-corner:hover .octo-arm{animation:none}}@keyframes a{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}.sidebar,body{background-color:var(--bg-color);color:var(--primary-color)}.sidebar{color:#364149}.sidebar li{margin:6px 0 6px 15px}.sidebar ul li a{color:var(--sidebar-color);font-size:14px;font-weight:400;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.sidebar ul li a:hover{text-decoration:underline}.sidebar ul li.active>a{border-right:2px solid;color:var(--sidebar-active-color);font-weight:700}.markdown-section h1,.markdown-section h2,.markdown-section h3,.markdown-section h4,.markdown-section h5{font-family:var(--serif-font)}.markdown-section strong{font-family:var(--sans-serif-font);color:var(--text-bold-color)}.markdown-section a{color:var(--primary-color);text-decoration:none;font-weight:600}.markdown-section li a,.markdown-section p a{color:var(--accent-color);text-decoration:underline;text-underline-offset:1px}.markdown-section li a:hover,.markdown-section p a:hover{opacity:.8}.markdown-section h1{font-size:2rem;margin:4rem 0 1rem}.markdown-section h2{font-size:1.75rem;margin:3.5rem 0 1rem}.markdown-section h3{font-size:1.5rem;margin:3rem 0 1rem}.markdown-section h4{font-size:1.25rem;margin:2.5rem 0 1rem}.markdown-section h5{font-size:1rem}.markdown-section p{margin:1.2em 0}.markdown-section p,.markdown-section ul{line-height:1.8rem;word-spacing:.05rem}.markdown-section ul li{margin-bottom:10px}.markdown-section ul{padding-left:2rem}.markdown-section blockquote{border-left:4px solid var(--primary-color);margin:2em 0;padding-left:20px}.markdown-section blockquote p{font-weight:400;margin-left:0;padding:12px 0}.markdown-section li code,.markdown-section p code{color:var(--codebox-token-var-color);border:1px solid var(--codebox-border-color);background:var(--codebox-bg);font-size:.75rem;padding:3px 10px;border-radius:3px;white-space:nowrap}.markdown-section code{border-radius:2px;color:var(--codebox-token-var-color);font-size:.8rem;margin:0 2px;padding:3px 5px;white-space:pre-wrap}.markdown-section pre{border-radius:5px;background-color:var(--codebox-bg);border:1px solid var(--codebox-border-color)}.markdown-section code,.markdown-section pre{font-family:var(--code-font)}.markdown-section pre{line-height:1.5rem;margin:1.2em 0;overflow:auto;padding:0 .7rem;position:relative;word-wrap:normal}.markdown-section pre,.markdown-section pre>code{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial}.markdown-section pre>code{background-color:var(--codebox-bg);border-radius:2px;color:var(--codebox-token-var-color);display:block;font-family:var(--code-font);font-size:.8rem;line-height:inherit;margin:0 2px;max-width:inherit;overflow:inherit;padding:1.25em 5px;white-space:inherit}.markdown-section code:after,.markdown-section code:before{letter-spacing:.8px;letter-spacing:.05rem}.token.class-name,.token.function,.token.number{color:#d46e00}.token.string{color:#d40066}.token.constant,.token.keyword{color:#e00400}.token.comment{color:var(--gray7)}pre:after{color:#ccc;content:attr(data-lang);font-size:.6rem;font-weight:600;height:15px;line-height:15px;padding:5px 10px 0;position:absolute;right:0;text-align:right;top:0}.sidebar ul ul{margin-left:15px} \ No newline at end of file