├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── get-matrix.mjs │ ├── main.yml │ └── pull_request.yml ├── .gitignore ├── .nojekyll ├── .npmrc ├── CHANGELOG.md ├── CNAME ├── LICENSE.md ├── README.md ├── docs ├── css │ └── style.css └── js │ └── main.js ├── gulpfile.js ├── index.html ├── lerna.json ├── media ├── logo-sunset.svg └── logo.svg ├── package.json ├── packages ├── compress │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ └── test │ │ └── index.js ├── core │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.d.ts │ │ └── index.js │ └── test │ │ ├── index.test-d.ts │ │ ├── key.js │ │ └── keyv.js ├── file │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.d.ts │ │ └── index.js │ └── test │ │ ├── fixture.json │ │ ├── index.js │ │ ├── index.test-d.ts │ │ └── store.js ├── memoize │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.d.ts │ │ └── index.js │ └── test │ │ └── index.js ├── mongo │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.d.ts │ │ └── index.js │ └── test │ │ ├── index.js │ │ └── index.test-d.ts ├── multi │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.d.ts │ │ └── index.js │ └── test │ │ ├── index.js │ │ └── index.test-d.ts ├── mysql │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.d.ts │ │ └── index.js │ └── test │ │ ├── index.js │ │ └── index.test-d.ts ├── offline │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ └── test │ │ ├── index.js │ │ ├── keyv-redis.js │ │ └── keyv-s3.js ├── postgres │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.d.ts │ │ └── index.js │ └── test │ │ ├── index.js │ │ └── index.test-d.ts ├── redis │ ├── CHANGELOG.md │ ├── Dockerfile │ ├── README.md │ ├── docker-compose.yml │ ├── package.json │ ├── src │ │ ├── index.d.ts │ │ └── index.js │ └── test │ │ ├── index.js │ │ └── index.test-d.ts ├── sql │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ └── index.js │ └── test │ │ └── index.js ├── sqlite │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.d.ts │ │ └── index.js │ └── test │ │ ├── index.js │ │ └── index.test-d.ts ├── stats │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.d.ts │ │ └── index.js │ └── test │ │ ├── fixture.json │ │ └── index.js └── test-suite │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ └── src │ ├── api.js │ ├── index.js │ ├── iteration.js │ ├── namespace.js │ └── values.js ├── pnpm-workspace.yaml ├── static ├── banner.jpg ├── banner.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── logo.jpg ├── logo.png ├── main.min.js └── style.min.css └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | max_line_length = 80 13 | indent_brace_style = 1TBS 14 | spaces_around_operators = true 15 | quote_type = auto 16 | 17 | [package.json] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: '/' 5 | schedule: 6 | interval: daily 7 | - package-ecosystem: 'github-actions' 8 | directory: '/' 9 | schedule: 10 | # Check for updates to GitHub Actions every weekday 11 | interval: 'daily' 12 | -------------------------------------------------------------------------------- /.github/workflows/get-matrix.mjs: -------------------------------------------------------------------------------- 1 | import { readdir, readFile } from 'fs/promises' 2 | import { dirname, join } from 'path' 3 | import { fileURLToPath } from 'url' 4 | 5 | const __dirname = dirname(fileURLToPath(import.meta.url)) 6 | 7 | const packages = [] 8 | 9 | const packagesPath = join(__dirname, '../../packages') 10 | 11 | for (const packagePath of await readdir(packagesPath)) { 12 | const pkg = join(packagesPath, packagePath, 'package.json') 13 | const { name, scripts } = JSON.parse(await readFile(pkg)) 14 | if (scripts && scripts.test && scripts.test !== 'exit 0') { 15 | packages.push(name) 16 | } 17 | } 18 | 19 | console.log(`{"package":${JSON.stringify(packages)}}`) 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | env: 9 | MYSQL_DB_DATABASE: keyv_test 10 | MYSQL_DB_USER: root 11 | MYSQL_DB_PASSWORD: root 12 | 13 | jobs: 14 | contributors: 15 | if: "${{ github.event.head_commit.message != 'build: contributors' }}" 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | - name: Setup Node.js 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: lts/* 26 | - name: Setup PNPM 27 | uses: pnpm/action-setup@v4 28 | with: 29 | version: latest 30 | run_install: true 31 | - name: Contributors 32 | run: | 33 | git config --global user.email ${{ secrets.GIT_EMAIL }} 34 | git config --global user.name ${{ secrets.GIT_USERNAME }} 35 | pnpm run contributors 36 | - name: Push changes 37 | run: | 38 | git push origin ${{ github.head_ref }} 39 | matrix: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v4 43 | - uses: actions/setup-node@v4 44 | - id: matrix 45 | run: echo "matrix=$(node .github/workflows/get-matrix.mjs)" >> $GITHUB_OUTPUT 46 | - run: echo ${{ steps.matrix.outputs.matrix }} 47 | outputs: 48 | matrix: ${{ steps.matrix.outputs.matrix }} 49 | test: 50 | if: | 51 | !startsWith(github.event.head_commit.message, 'chore(release):') && 52 | !startsWith(github.event.head_commit.message, 'docs:') && 53 | !startsWith(github.event.head_commit.message, 'ci:') 54 | needs: [contributors, matrix] 55 | runs-on: ubuntu-latest 56 | strategy: 57 | fail-fast: false 58 | matrix: ${{fromJson(needs.matrix.outputs.matrix)}} 59 | steps: 60 | - name: Checkout 61 | uses: actions/checkout@v4 62 | - name: Setup Node.js 63 | uses: actions/setup-node@v4 64 | with: 65 | node-version: lts/* 66 | - name: Setup PNPM 67 | uses: pnpm/action-setup@v4 68 | with: 69 | version: latest 70 | run_install: true 71 | - name: Test 72 | run: pnpm --filter "${{ matrix.package }}" exec c8 pnpm test 73 | - name: Coverage 74 | run: pnpm --filter "${{ matrix.package }}" exec c8 report --reporter=lcov --report-dir=coverage 75 | - name: Upload 76 | uses: coverallsapp/github-action@main 77 | with: 78 | flag-name: ${{ matrix.package }} 79 | parallel: true 80 | file: $(pnpm --filter "${{ matrix.package }}" exec pwd)/coverage/lcov.info 81 | 82 | finish: 83 | needs: test 84 | runs-on: ubuntu-latest 85 | steps: 86 | - name: Coveralls Finished 87 | uses: coverallsapp/github-action@main 88 | with: 89 | parallel-finished: true 90 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: pull_request 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | MYSQL_DB_DATABASE: keyv_test 13 | MYSQL_DB_USER: root 14 | MYSQL_DB_PASSWORD: root 15 | 16 | jobs: 17 | matrix: 18 | if: github.ref != 'refs/heads/master' 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions/setup-node@v4 23 | - id: matrix 24 | run: echo "matrix=$(node .github/workflows/get-matrix.mjs)" >> $GITHUB_OUTPUT 25 | - run: echo ${{ steps.matrix.outputs.matrix }} 26 | outputs: 27 | matrix: ${{ steps.matrix.outputs.matrix }} 28 | 29 | test: 30 | needs: matrix 31 | runs-on: ubuntu-latest 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | node-version: [18, 20, 22] 36 | package: ${{ fromJson(needs.matrix.outputs.matrix).package }} 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | - name: Setup Node.js 41 | uses: actions/setup-node@v4 42 | with: 43 | node-version: ${{ matrix.node-version }} 44 | - name: Setup PNPM 45 | uses: pnpm/action-setup@v4 46 | with: 47 | version: latest 48 | run_install: true 49 | - name: Start Redis 50 | if: contains(matrix.package, 'redis') 51 | uses: supercharge/redis-github-action@1.8.0 52 | - name: Start MongoDB 53 | if: contains(matrix.package, 'mongo') 54 | uses: supercharge/mongodb-github-action@1.12.0 55 | - name: Start MySQL 56 | if: contains(matrix.package, 'mysql') 57 | run: | 58 | sudo systemctl start mysql.service 59 | mysql -e 'CREATE DATABASE ${{ env.MYSQL_DB_DATABASE }}' -u${{ env.MYSQL_DB_USER }} -p${{ env.MYSQL_DB_PASSWORD }} 60 | mysql -e 'SET GLOBAL max_connections = 300;' -u${{ env.MYSQL_DB_USER }} -p${{ env.MYSQL_DB_PASSWORD }} 61 | - name: Start PostgreSQL 62 | if: contains(matrix.package, 'postgres') 63 | run: | 64 | sudo systemctl start postgresql.service 65 | pg_isready 66 | - name: Setup PostgreSQL for ava tests 67 | if: contains(matrix.package, 'postgres') 68 | run: | 69 | sudo -u postgres psql --command="alter system set max_connections = 300;" 70 | sudo -u postgres psql --command="CREATE DATABASE keyv_test;" 71 | sudo -u postgres psql --command="ALTER ROLE postgres WITH PASSWORD 'postgres';" --command="\du" 72 | sudo systemctl restart postgresql.service 73 | pg_isready 74 | - name: Test 75 | run: pnpm --filter "${{ matrix.package }}" exec c8 pnpm test 76 | - name: Coverage 77 | run: pnpm --filter "${{ matrix.package }}" exec c8 report --reporter=lcov --report-dir=coverage 78 | - name: Upload 79 | uses: coverallsapp/github-action@main 80 | with: 81 | flag-name: ${{ matrix.package }} 82 | parallel: true 83 | file: $(pnpm --filter "${{ matrix.package }}" exec pwd)/coverage/lcov.info 84 | 85 | finish: 86 | needs: test 87 | runs-on: ubuntu-latest 88 | steps: 89 | - name: Coveralls Finished 90 | uses: coverallsapp/github-action@main 91 | with: 92 | parallel-finished: true 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lerna-debug.log 2 | test/testdb.sqlite 3 | 4 | ## Node 5 | 6 | #npm5 7 | package-lock.json 8 | 9 | #yarn 10 | yarn.lock 11 | 12 | #pnpm 13 | pnpm-lock.yaml 14 | 15 | 16 | # Logs 17 | logs 18 | *.log 19 | npm-debug.log* 20 | 21 | # Runtime data 22 | pids 23 | *.pid 24 | *.seed 25 | *.pid.lock 26 | 27 | # Directory for instrumented libs generated by jscoverage/JSCover 28 | lib-cov 29 | 30 | # Coverage directory used by tools like istanbul 31 | coverage 32 | 33 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (http://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules 44 | jspm_packages 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | ## OS X 56 | 57 | *.DS_Store 58 | .AppleDouble 59 | .LSOverride 60 | 61 | # Icon must end with two \r 62 | Icon 63 | 64 | 65 | # Thumbnails 66 | ._* 67 | 68 | # Files that might appear in the root of a volume 69 | .DocumentRevisions-V100 70 | .fseventsd 71 | .Spotlight-V100 72 | .TemporaryItems 73 | .Trashes 74 | .VolumeIcon.icns 75 | .com.apple.timemachine.donotpresent 76 | 77 | # Directories potentially created on remote AFP share 78 | .AppleDB 79 | .AppleDesktop 80 | Network Trash Folder 81 | Temporary Items 82 | .apdisk 83 | 84 | **/*.env 85 | 86 | *.sqlite 87 | 88 | .env 89 | .envrc 90 | 91 | # packages/file 92 | packages/file/test/fixtures2 93 | packages/file/test/test.json 94 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microlinkhq/keyvhq/9246bdb25a3b69bc79c525042a58336a8325a9c5/.nojekyll -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | audit=false 2 | fund=false 3 | loglevel=error 4 | package-lock=false 5 | prefer-dedupe=true 6 | prefer-offline=false 7 | resolution-mode=highest 8 | save-prefix=~ 9 | save=false 10 | shamefully-hoist=true 11 | strict-peer-dependencies=false 12 | unsafe-perm=true 13 | link-workspace-packages=true 14 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | keyvhq.js.org 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2017 Luke Childs (https://lukechilds.co) 4 | Copyright © 2021 Microlink (https://microlink.io) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /docs/js/main.js: -------------------------------------------------------------------------------- 1 | /* global codecopy */ 2 | 3 | window.$docsify = { 4 | repo: 'microlinkhq/keyvhq', 5 | maxLevel: 3, 6 | auto2top: true, 7 | externalLinkRel: 'noopener noreferrer', 8 | plugins: [ 9 | function (hook) { 10 | hook.ready(function () { 11 | codecopy('pre') 12 | }) 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const postcss = require('gulp-postcss') 4 | const concat = require('gulp-concat') 5 | const uglify = require('gulp-uglify') 6 | const gulp = require('gulp') 7 | 8 | const src = { 9 | css: ['docs/css/style.css'], 10 | js: ['docs/js/main.js'] 11 | } 12 | 13 | const dist = { 14 | path: 'static', 15 | name: { 16 | css: 'style', 17 | js: 'main' 18 | } 19 | } 20 | 21 | const styles = () => 22 | gulp 23 | .src(src.css) 24 | .pipe(concat(`${dist.name.css}.min.css`)) 25 | .pipe( 26 | postcss([ 27 | require('postcss-focus'), 28 | require('cssnano')({ 29 | preset: [require('cssnano-preset-advanced')] 30 | }) 31 | ]) 32 | ) 33 | .pipe(gulp.dest(dist.path)) 34 | 35 | const scripts = () => 36 | gulp 37 | .src(src.js) 38 | .pipe(concat(`${dist.name.js}.min.js`)) 39 | .pipe(uglify()) 40 | .pipe(gulp.dest(dist.path)) 41 | 42 | const build = gulp.parallel(styles, scripts) 43 | 44 | function watch () { 45 | gulp.watch(src.css, styles) 46 | gulp.watch(src.js, scripts) 47 | } 48 | 49 | module.exports.default = gulp.series(build, watch) 50 | module.exports.build = build 51 | module.exports.watch = watch 52 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Keyv is a simple key-value storage with multi-backend support 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "2.1.9", 6 | "command": { 7 | "bootstrap": { 8 | "npmClientArgs": [ 9 | "--no-package-lock" 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /media/logo-sunset.svg: -------------------------------------------------------------------------------- 1 | logo -------------------------------------------------------------------------------- /media/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/compress/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.7](https://github.com/microlinkhq/keyvhq/compare/v2.1.6...v2.1.7) (2025-02-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/compress 9 | 10 | ## [2.1.5](https://github.com/microlinkhq/keyvhq/compare/v2.1.4...v2.1.5) (2024-07-30) 11 | 12 | **Note:** Version bump only for package @keyvhq/compress 13 | 14 | ## [2.1.1](https://github.com/microlinkhq/keyvhq/compare/v2.1.0...v2.1.1) (2024-04-23) 15 | 16 | **Note:** Version bump only for package @keyvhq/compress 17 | 18 | ## [2.0.3](https://github.com/microlinkhq/keyvhq/compare/v2.0.2...v2.0.3) (2023-06-20) 19 | 20 | **Note:** Version bump only for package @keyvhq/compress 21 | 22 | # [2.0.0](https://github.com/microlinkhq/keyvhq/compare/v1.6.28...v2.0.0) (2023-04-22) 23 | 24 | ### Features 25 | 26 | * shipped v2 ([#169](https://github.com/microlinkhq/keyvhq/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyvhq/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 27 | 28 | ### BREAKING CHANGES 29 | 30 | * The EventEmitter behavior has been completely removed 31 | 32 | ## [1.6.28](https://github.com/microlinkhq/keyvhq/compare/v1.6.27...v1.6.28) (2023-02-27) 33 | 34 | **Note:** Version bump only for package @keyvhq/compress 35 | 36 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 37 | 38 | **Note:** Version bump only for package @keyvhq/compress 39 | 40 | ## [1.6.20](https://github.com/microlinkhq/keyvhq/compare/v1.6.19...v1.6.20) (2022-07-12) 41 | 42 | **Note:** Version bump only for package @keyvhq/compress 43 | 44 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 45 | 46 | **Note:** Version bump only for package @keyvhq/compress 47 | 48 | ## [1.6.6](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.6) (2022-03-09) 49 | 50 | **Note:** Version bump only for package @keyvhq/compress 51 | 52 | ## [1.6.5](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.5) (2022-03-09) 53 | 54 | **Note:** Version bump only for package @keyvhq/compress 55 | 56 | ## [1.6.1](https://github.com/microlinkhq/keyvhq/compare/v1.6.0...v1.6.1) (2021-10-14) 57 | 58 | **Note:** Version bump only for package @keyvhq/compress 59 | 60 | # [1.3.0](https://github.com/microlinkhq/keyvhq/compare/v1.2.7...v1.3.0) (2021-09-01) 61 | 62 | ### Features 63 | 64 | - add compress decorator ([cd089b6](https://github.com/microlinkhq/keyvhq/commit/cd089b65dbed6183df722d618a7deebbed070650)) 65 | 66 | # Changelog 67 | 68 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 69 | 70 | ### 1.0.3 (2021-07-25) 71 | 72 | ### 1.0.2 (2021-07-19) 73 | 74 | ### 1.0.1 (2021-07-19) 75 | 76 | ## [1.0.0](https://github.com/Kikobeats/keyv-offline/compare/v0.0.1...v1.0.0) (2021-07-16) 77 | 78 | ### 0.0.1 (2021-07-16) 79 | -------------------------------------------------------------------------------- /packages/compress/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/compress [keyv](https://github.com/microlinkhq/keyvhq) 2 | 3 | > Adds compression bindings for your Keyv instance. 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ npm install @keyvhq/compress --save 9 | ``` 10 | 11 | ## Usage 12 | 13 | All you need to do is to wrap your [keyv](https://keyv.js.org) instance: 14 | 15 | ```js 16 | const KeyvRedis = require('@keyvhq/redis') 17 | const Keyv = require('@keyvhq/core') 18 | 19 | const store = new KeyvRedis({ 20 | uri: 'redis://user:pass@localhost:6379', 21 | maxRetriesPerRequest: 1, 22 | emitErrors: false 23 | }) 24 | 25 | const keyv = new Keyv({ store }) 26 | ``` 27 | 28 | Using `@keyvhq/compress` at the top level: 29 | 30 | ```js 31 | const KeyvCompress = require('@keyvhq/compress') 32 | const keyv = KeyvCompress(new Keyv({ store })) 33 | ``` 34 | 35 | Additionally, it can accept [compress-brotli#options](https://github.com/Kikobeats/compress-brotli#compressbrotlioptions) as second argument: 36 | 37 | ```js 38 | const keyv = KeyvCompress( 39 | new Keyv({ store }), 40 | { 41 | serialize: v8.serialize, 42 | deserialize: v8.deserialize 43 | } 44 | ) 45 | ``` 46 | 47 | ## License 48 | 49 | **@keyvhq/memoize** © [Kiko Beats](https://kikobeats.com), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
50 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 51 | 52 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 53 | -------------------------------------------------------------------------------- /packages/compress/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/compress", 3 | "description": "Adds compression bindings for your Keyv instance, saving as much space as you can.", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.7", 6 | "main": "src/index.js", 7 | "author": { 8 | "email": "hello@microlink.io", 9 | "name": "microlink.io", 10 | "url": "https://microlink.io" 11 | }, 12 | "repository": { 13 | "directory": "packages/offline", 14 | "type": "git", 15 | "url": "git+https://github.com/microlinkhq/keyvhq.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/microlinkhq/keyvhq/issues" 19 | }, 20 | "keywords": [ 21 | "brotli", 22 | "cache", 23 | "compress", 24 | "compression", 25 | "decompress", 26 | "deserialize", 27 | "key", 28 | "keyv", 29 | "serialize", 30 | "store", 31 | "ttl", 32 | "value" 33 | ], 34 | "dependencies": { 35 | "compress-brotli": "~1.3.12" 36 | }, 37 | "devDependencies": { 38 | "@keyvhq/core": "workspace:*", 39 | "ava": "5" 40 | }, 41 | "engines": { 42 | "node": ">= 18" 43 | }, 44 | "files": [ 45 | "src" 46 | ], 47 | "scripts": { 48 | "test": "ava" 49 | }, 50 | "license": "MIT", 51 | "publishConfig": { 52 | "access": "public" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/compress/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compressBrotli = require('compress-brotli') 4 | 5 | function KeyvCompress (keyv, opts) { 6 | if (!(this instanceof KeyvCompress)) return new KeyvCompress(keyv, opts) 7 | 8 | const brotli = compressBrotli(opts) 9 | 10 | keyv.serialize = async ({ value, expires }) => { 11 | return brotli.serialize({ value: await brotli.compress(value), expires }) 12 | } 13 | 14 | keyv.deserialize = async data => { 15 | const { value, expires } = brotli.deserialize(data) 16 | return { value: await brotli.decompress(value), expires } 17 | } 18 | 19 | return keyv 20 | } 21 | 22 | module.exports = KeyvCompress 23 | -------------------------------------------------------------------------------- /packages/compress/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compressBrotli = require('compress-brotli') 4 | const JSONB = require('json-buffer') 5 | const Keyv = require('@keyvhq/core') 6 | const test = require('ava') 7 | 8 | const KeyvCompress = require('..') 9 | 10 | test('pass compress options', async t => { 11 | const compressOpts = { enable: false } 12 | const brotli = compressBrotli(compressOpts) 13 | const store = new Map() 14 | const keyv = KeyvCompress(new Keyv({ store, namespace: null }), compressOpts) 15 | 16 | await keyv.set('foo', 'bar') 17 | t.is(store.get('foo'), JSONB.stringify({ value: 'bar', expires: null })) 18 | 19 | t.deepEqual( 20 | await keyv.deserialize(JSONB.stringify({ value: 'bar', expires: null })), 21 | await brotli.deserialize( 22 | JSONB.stringify({ value: await brotli.decompress('bar'), expires: null }) 23 | ) 24 | ) 25 | }) 26 | 27 | test('enable compression', async t => { 28 | const brotli = compressBrotli() 29 | const store = new Map() 30 | const keyv = KeyvCompress(new Keyv({ store, namespace: null })) 31 | await keyv.set('foo', 'bar') 32 | const compressed = await brotli.compress('bar') 33 | 34 | t.is( 35 | store.get('foo'), 36 | JSONB.stringify({ 37 | value: compressed, 38 | expires: null 39 | }) 40 | ) 41 | }) 42 | -------------------------------------------------------------------------------- /packages/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.7](https://github.com/microlinkhq/keyvhq/compare/v2.1.6...v2.1.7) (2025-02-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/core 9 | 10 | ## [2.1.1](https://github.com/microlinkhq/keyvhq/compare/v2.1.0...v2.1.1) (2024-04-23) 11 | 12 | **Note:** Version bump only for package @keyvhq/core 13 | 14 | # [2.1.0](https://github.com/microlinkhq/keyvhq/compare/v2.0.3...v2.1.0) (2023-09-29) 15 | 16 | **Note:** Version bump only for package @keyvhq/core 17 | 18 | # [2.0.0](https://github.com/microlinkhq/keyvhq/compare/v1.6.28...v2.0.0) (2023-04-22) 19 | 20 | ### Features 21 | 22 | * shipped v2 ([#169](https://github.com/microlinkhq/keyvhq/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyvhq/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 23 | 24 | ### BREAKING CHANGES 25 | 26 | * The EventEmitter behavior has been completely removed 27 | 28 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 29 | 30 | **Note:** Version bump only for package @keyvhq/core 31 | 32 | ## [1.6.14](https://github.com/microlinkhq/keyvhq/compare/v1.6.13...v1.6.14) (2022-05-29) 33 | 34 | ### Bug Fixes 35 | 36 | * added async deserialize/serialize functions ([703b98b](https://github.com/microlinkhq/keyvhq/commit/703b98b918d235bac76760e20e907e0bc028bfa0)) 37 | 38 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 39 | 40 | ### Bug Fixes 41 | 42 | * associate `emitErros` with keyv instance ([3a88e0a](https://github.com/microlinkhq/keyvhq/commit/3a88e0a4af316769c1845a9f5c82a3b8d25120e2)) 43 | 44 | ## [1.6.9](https://github.com/microlinkhq/keyvhq/compare/v1.6.8...v1.6.9) (2022-03-30) 45 | 46 | ### Bug Fixes 47 | 48 | * **core:** wait deserialize promise ([b5f3f1c](https://github.com/microlinkhq/keyvhq/commit/b5f3f1c4f93e79185807b3831ba37802ab9e10fc)) 49 | 50 | ## [1.6.6](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.6) (2022-03-09) 51 | 52 | ### Bug Fixes 53 | 54 | * linter ([328d51c](https://github.com/microlinkhq/keyvhq/commit/328d51c1a16f753d9341246184eab79203afdda4)) 55 | * linter ([c36c964](https://github.com/microlinkhq/keyvhq/commit/c36c964a16e79d8bd0e4fd62a926293e89ea0472)) 56 | 57 | ## [1.6.5](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.5) (2022-03-09) 58 | 59 | ### Bug Fixes 60 | 61 | * linter ([328d51c](https://github.com/microlinkhq/keyvhq/commit/328d51c1a16f753d9341246184eab79203afdda4)) 62 | * linter ([c36c964](https://github.com/microlinkhq/keyvhq/commit/c36c964a16e79d8bd0e4fd62a926293e89ea0472)) 63 | 64 | ## [1.6.4](https://github.com/microlinkhq/keyvhq/compare/v1.6.3...v1.6.4) (2022-01-24) 65 | 66 | **Note:** Version bump only for package @keyvhq/core 67 | 68 | ## [1.6.3](https://github.com/microlinkhq/keyvhq/compare/v1.6.2...v1.6.3) (2021-12-01) 69 | 70 | ### Bug Fixes 71 | 72 | * removed dupe titles ([273cd62](https://github.com/microlinkhq/keyvhq/commit/273cd6218417b9e131ad5393deb884ab1aaec7fa)) 73 | 74 | ## [1.6.2](https://github.com/microlinkhq/keyvhq/compare/v1.6.1...v1.6.2) (2021-11-08) 75 | 76 | **Note:** Version bump only for package @keyvhq/core 77 | 78 | # [1.6.0](https://github.com/microlinkhq/keyvhq/compare/v1.5.2...v1.6.0) (2021-10-05) 79 | 80 | **Note:** Version bump only for package @keyvhq/core 81 | 82 | # [1.4.0](https://github.com/microlinkhq/keyvhq/compare/v1.3.0...v1.4.0) (2021-09-12) 83 | 84 | ### Bug Fixes 85 | 86 | - convert key to string if possible ([4a232b9](https://github.com/microlinkhq/keyvhq/commit/4a232b95ee8e056eab1abc59c0d5275f7250349c)) 87 | - fixed namespacing bugs ([f14cd4f](https://github.com/microlinkhq/keyvhq/commit/f14cd4f1651fc866e96785dff0f33f807a1b8493)) 88 | - namespace api ([fd07736](https://github.com/microlinkhq/keyvhq/commit/fd07736aee52c9bde9a81f075faa85c39d72cc51)) 89 | - no namespaced iteration of maps ([77b9724](https://github.com/microlinkhq/keyvhq/commit/77b9724fe4169331ed09539236729237d7c7cefa)) 90 | 91 | ### Features 92 | 93 | - use empty namespace by default ([a2872d3](https://github.com/microlinkhq/keyvhq/commit/a2872d3ec5ee3cb6445fc97bd129505d43e90c0e)) 94 | 95 | # [1.3.0](https://github.com/microlinkhq/keyvhq/compare/v1.2.7...v1.3.0) (2021-09-01) 96 | 97 | ### Bug Fixes 98 | 99 | - redis iterator not working when keys are present ([ba7d28c](https://github.com/microlinkhq/keyvhq/commit/ba7d28cde0fe86e305f7db125d12629938fc5a70)) 100 | 101 | ## [1.2.6](https://github.com/microlinkhq/keyvhq/compare/v1.2.5...v1.2.6) (2021-08-24) 102 | 103 | **Note:** Version bump only for package @keyvhq/core 104 | 105 | ## [1.1.1](https://github.com/microlinkhq/keyvhq/compare/v1.1.0...v1.1.1) (2021-07-30) 106 | 107 | **Note:** Version bump only for package @keyvhq/core 108 | 109 | # [1.1.0](https://github.com/microlinkhq/keyvhq/compare/v1.0.2...v1.1.0) (2021-07-30) 110 | 111 | ### Bug Fixes 112 | 113 | - exit early if undefined ([2926ab6](https://github.com/microlinkhq/keyvhq/commit/2926ab697e2f6bf866743e8d31e4a42bc10d2cd2)) 114 | - setup namespace at store ([8ccbb75](https://github.com/microlinkhq/keyvhq/commit/8ccbb7533d011fd11a3ffda96e3fe6854fa3e2b8)) 115 | 116 | ## [1.0.2](https://github.com/microlinkhq/keyvhq/compare/v1.0.1...v1.0.2) (2021-07-16) 117 | 118 | **Note:** Version bump only for package @keyvhq/core 119 | 120 | ## [1.0.1](https://github.com/microlinkhq/keyvhq/compare/v1.0.0...v1.0.1) (2021-07-16) 121 | 122 | **Note:** Version bump only for package @keyvhq/core 123 | 124 | ## [0.2.4](https://github.com/microlinkhq/keyvhq/compare/v0.2.0...v0.2.4) (2021-07-01) 125 | 126 | ### Bug Fixes 127 | 128 | - typo opts -> options ([ce95753](https://github.com/microlinkhq/keyvhq/commit/ce957538f2881a0c31397a4f87b74efa081880b0)) 129 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/core", 3 | "description": "Simple key-value storage with support for multiple backends", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.7", 6 | "types": "./src/index.d.ts", 7 | "main": "src/index.js", 8 | "author": { 9 | "email": "hello@microlink.io", 10 | "name": "microlink.io", 11 | "url": "https://microlink.io" 12 | }, 13 | "repository": { 14 | "directory": "packages/keyv", 15 | "type": "git", 16 | "url": "git+https://github.com/microlinkhq/keyvhq.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/microlinkhq/keyvhq/issues" 20 | }, 21 | "keywords": [ 22 | "cache", 23 | "key", 24 | "keyv", 25 | "store", 26 | "ttl", 27 | "value" 28 | ], 29 | "dependencies": { 30 | "json-buffer": "~3.0.1" 31 | }, 32 | "devDependencies": { 33 | "@keyvhq/test-suite": "workspace:*", 34 | "@types/node": "latest", 35 | "ava": "5", 36 | "timekeeper": "latest", 37 | "tsd": "latest", 38 | "typescript": "latest" 39 | }, 40 | "engines": { 41 | "node": ">= 18" 42 | }, 43 | "files": [ 44 | "src" 45 | ], 46 | "scripts": { 47 | "lint": "tsd", 48 | "pretest": "npm run lint", 49 | "test": "ava" 50 | }, 51 | "license": "MIT", 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "tsd": { 56 | "directory": "test" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/core/src/index.d.ts: -------------------------------------------------------------------------------- 1 | declare class Keyv { 2 | constructor (opts?: Keyv.Options) 3 | 4 | /** Returns the value. */ 5 | get(key: string, options?: { raw?: TRaw }): 6 | Promise<(TRaw extends false 7 | ? TValue 8 | : Keyv.DeserializedData) | undefined> 9 | /** Returns `true` if the key existed, `false` if not. */ 10 | has (key: string): Promise 11 | /** 12 | * Set a value. 13 | * 14 | * By default keys are persistent. You can set an expiry TTL in milliseconds. 15 | */ 16 | set (key: string, value: TValue, ttl?: number): Promise 17 | /** 18 | * Deletes an entry. 19 | * 20 | * Returns `true` if the key existed, `false` if not. 21 | */ 22 | delete (key: string): Promise 23 | /** Delete all entries in the current namespace. */ 24 | clear (options?: Record): Promise 25 | /** 26 | * Yields an iterator with all the key, value entries in the namespace. 27 | */ 28 | iterator (): AsyncIterator<[string, Keyv.DeserializedData]> 29 | } 30 | 31 | declare namespace Keyv { 32 | interface Options { 33 | /** Namespace for the current instance. */ 34 | namespace?: string | undefined 35 | /** A custom serialization function. */ 36 | serialize?: ((data: DeserializedData) => string | Promise) | undefined 37 | /** A custom deserialization function. */ 38 | deserialize?: ((data: string) => DeserializedData | undefined | Promise | undefined>) | undefined 39 | /** The storage adapter instance to be used by Keyv. Defaults to in-memory map */ 40 | store?: Store | Map 41 | /** Default TTL. Can be overridden by specififying a TTL on `.set()`. */ 42 | ttl?: number | undefined 43 | /** Emit Errors on the keyv instance, defaults to true. */ 44 | emitErrors?: boolean | true 45 | } 46 | 47 | interface DeserializedData { 48 | value: TValue 49 | expires: number | null 50 | } 51 | 52 | interface Store { 53 | get: (key: string) => TValue | Promise | undefined 54 | has: (key: string) => Promise 55 | set: (key: string, value: TValue, ttl?: number) => any 56 | delete: (key: string) => boolean | Promise 57 | clear: () => void | Promise 58 | iterator: () => AsyncGenerator 59 | } 60 | } 61 | 62 | export = Keyv 63 | -------------------------------------------------------------------------------- /packages/core/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const JSONB = require('json-buffer') 4 | 5 | class Keyv { 6 | constructor (options = {}) { 7 | Object.entries( 8 | Object.assign( 9 | { 10 | serialize: JSONB.stringify, 11 | deserialize: JSONB.parse, 12 | store: new Map() 13 | }, 14 | options 15 | ) 16 | ).forEach(([key, value]) => (this[key] = value)) 17 | 18 | const generateIterator = iterator => 19 | async function * () { 20 | for await (const [key, raw] of typeof iterator === 'function' 21 | ? iterator(this.namespace) 22 | : iterator) { 23 | const data = 24 | typeof raw === 'string' ? await this.deserialize(raw) : raw 25 | if (this.namespace && !key.includes(this.namespace)) { 26 | continue 27 | } 28 | 29 | if (typeof data.expires === 'number' && Date.now() > data.expires) { 30 | this.delete(key) 31 | continue 32 | } 33 | 34 | yield [this._getKeyUnprefix(key), data.value] 35 | } 36 | } 37 | 38 | // Attach iterators 39 | if ( 40 | typeof this.store[Symbol.iterator] === 'function' && 41 | this.store instanceof Map 42 | ) { 43 | this.iterator = generateIterator(this.store) 44 | } else if (typeof this.store.iterator === 'function') { 45 | this.iterator = generateIterator(this.store.iterator.bind(this.store)) 46 | } 47 | } 48 | 49 | _getKeyPrefix (key) { 50 | return this.namespace 51 | ? `${this.namespace}:${key}` 52 | : (key && key.toString()) || key 53 | } 54 | 55 | _getKeyUnprefix (key) { 56 | return this.namespace ? key.split(':').splice(1).join(':') : key 57 | } 58 | 59 | async get (key, { raw: asRaw = false } = {}) { 60 | const raw = await this.store.get(this._getKeyPrefix(key)) 61 | if (raw === undefined) return undefined 62 | 63 | const data = typeof raw === 'string' ? await this.deserialize(raw) : raw 64 | 65 | if (typeof data.expires === 'number' && Date.now() > data.expires) { 66 | this.delete(key) 67 | return undefined 68 | } 69 | 70 | return asRaw ? data : data.value 71 | } 72 | 73 | async has (key) { 74 | return typeof this.store.has === 'function' 75 | ? this.store.has(this._getKeyPrefix(key)) 76 | : (await this.store.get(this._getKeyPrefix(key))) !== undefined 77 | } 78 | 79 | async set (key, value, ttl) { 80 | if (value === undefined) return false 81 | if (ttl === undefined) ttl = this.ttl 82 | if (ttl === 0) ttl = undefined 83 | const expires = typeof ttl === 'number' ? Date.now() + ttl : null 84 | const valueSerialized = await this.serialize({ value, expires }) 85 | await this.store.set(this._getKeyPrefix(key), valueSerialized, ttl) 86 | return true 87 | } 88 | 89 | async delete (key) { 90 | return this.store.delete(this._getKeyPrefix(key)) 91 | } 92 | 93 | async clear (options) { 94 | return this.store.clear(this.namespace, options) 95 | } 96 | } 97 | module.exports = Keyv 98 | -------------------------------------------------------------------------------- /packages/core/test/index.test-d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-new */ 2 | /* eslint-disable @typescript-eslint/no-floating-promises */ 3 | /* eslint-disable @typescript-eslint/no-invalid-void-type */ 4 | 5 | import { expectType } from 'tsd' 6 | import Keyv, { DeserializedData } from '../src' 7 | 8 | new Keyv({ namespace: 'redis' }) 9 | new Keyv({ ttl: 123 }) 10 | new Keyv({ 11 | serialize: (d) => { 12 | expectType(d.expires) 13 | return JSON.stringify(d) 14 | } 15 | }) 16 | 17 | new Keyv({ deserialize: JSON.parse }) 18 | 19 | // @ts-expect-error 20 | new Keyv({ deserialize: (d: string) => d }) 21 | 22 | new Keyv({ 23 | deserialize: (d) => { 24 | expectType(d) 25 | return { 26 | value: true, 27 | expires: new Date().getTime() 28 | } 29 | } 30 | }) 31 | 32 | new Keyv({ store: new Map() }) 33 | 34 | new Keyv(); 35 | 36 | (async () => { 37 | const keyv = new Keyv() 38 | expectType(await keyv.set('foo', 'expires in 1 second', 1000)) 39 | expectType(await keyv.set('foo', 'never expires')) 40 | expectType(await keyv.get('foo')) 41 | expectType(await keyv.get('foo', { raw: false })) 42 | expectType | undefined>(await keyv.get('foo', { raw: true })) 43 | expectType(await keyv.has('foo')) 44 | expectType(await keyv.delete('foo')) 45 | expectType(await keyv.clear()) 46 | expectType]>>(keyv.iterator()) 47 | })() 48 | -------------------------------------------------------------------------------- /packages/core/test/key.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('ava') 4 | const Keyv = require('../src') 5 | 6 | const createCase = (test, key, value = 'foobar', testTitle) => 7 | test(testTitle || String(key), async t => { 8 | const store = new Map() 9 | const keyv = new Keyv({ store }) 10 | await keyv.set(key, value) 11 | t.is(await keyv.get(key), value) 12 | }) 13 | 14 | createCase(test, 'string') 15 | createCase(test, 123) 16 | createCase(test, null) 17 | createCase(test, undefined) 18 | createCase(test, NaN) 19 | createCase(test, Infinity) 20 | createCase(test, true) 21 | createCase(test, false) 22 | createCase(test, '😍') 23 | createCase(test, '¯\\_(ツ)_/¯') 24 | createCase(test, Buffer.from('string'), 'foobar', 'string-buffer') 25 | -------------------------------------------------------------------------------- /packages/core/test/keyv.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const keyvTestSuite = require('@keyvhq/test-suite') 4 | const tk = require('timekeeper') 5 | const test = require('ava') 6 | 7 | const Keyv = require('..') 8 | 9 | test.serial('Keyv is a class', t => { 10 | t.is(typeof Keyv, 'function') 11 | t.throws(() => Keyv()) // eslint-disable-line new-cap 12 | t.notThrows(() => new Keyv()) 13 | }) 14 | 15 | test.serial('Keyv accepts storage adapters', async t => { 16 | const store = new Map() 17 | const keyv = new Keyv({ store }) 18 | t.is(store.size, 0) 19 | await keyv.set('foo', 'bar') 20 | t.is(await keyv.get('foo'), 'bar') 21 | t.is(store.size, 1) 22 | }) 23 | 24 | test.serial('Keyv passes tll info to stores', async t => { 25 | t.plan(1) 26 | const store = new Map() 27 | const storeSet = store.set 28 | store.set = (key, value, ttl) => { 29 | t.is(ttl, 100) 30 | storeSet.call(store, key, value, ttl) 31 | } 32 | 33 | const keyv = new Keyv({ store }) 34 | await keyv.set('foo', 'bar', 100) 35 | }) 36 | 37 | test.serial('Keyv respects default tll option', async t => { 38 | const store = new Map() 39 | const keyv = new Keyv({ store, ttl: 100 }) 40 | await keyv.set('foo', 'bar') 41 | t.is(await keyv.get('foo'), 'bar') 42 | tk.freeze(Date.now() + 150) 43 | t.is(await keyv.get('foo'), undefined) 44 | t.is(store.size, 0) 45 | tk.reset() 46 | }) 47 | 48 | test.serial('.set(key, value, ttl) overwrites default tll option', async t => { 49 | const startTime = Date.now() 50 | tk.freeze(startTime) 51 | const store = new Map() 52 | const keyv = new Keyv({ store, ttl: 200 }) 53 | await keyv.set('foo', 'bar') 54 | await keyv.set('fizz', 'buzz', 100) 55 | await keyv.set('ping', 'pong', 300) 56 | t.is(await keyv.get('foo'), 'bar') 57 | t.is(await keyv.get('fizz'), 'buzz') 58 | t.is(await keyv.get('ping'), 'pong') 59 | tk.freeze(startTime + 150) 60 | t.is(await keyv.get('foo'), 'bar') 61 | t.is(await keyv.get('fizz'), undefined) 62 | t.is(await keyv.get('ping'), 'pong') 63 | tk.freeze(startTime + 250) 64 | t.is(await keyv.get('foo'), undefined) 65 | t.is(await keyv.get('ping'), 'pong') 66 | tk.freeze(startTime + 350) 67 | t.is(await keyv.get('ping'), undefined) 68 | tk.reset() 69 | }) 70 | 71 | test.serial( 72 | '.set(key, value, ttl) where ttl is "0" overwrites default tll option and sets key to never expire', 73 | async t => { 74 | const startTime = Date.now() 75 | tk.freeze(startTime) 76 | const store = new Map() 77 | const keyv = new Keyv({ store, ttl: 200 }) 78 | await keyv.set('foo', 'bar', 0) 79 | t.is(await keyv.get('foo'), 'bar') 80 | tk.freeze(startTime + 250) 81 | t.is(await keyv.get('foo'), 'bar') 82 | tk.reset() 83 | } 84 | ) 85 | 86 | test.serial( 87 | '.get(key, {raw: true}) returns the raw object instead of the value', 88 | async t => { 89 | const store = new Map() 90 | const keyv = new Keyv({ store }) 91 | await keyv.set('foo', 'bar') 92 | const value = await keyv.get('foo') 93 | const rawObject = await keyv.get('foo', { raw: true }) 94 | t.is(value, 'bar') 95 | t.is(rawObject.value, 'bar') 96 | } 97 | ) 98 | 99 | test.serial( 100 | 'Keyv uses custom serializer when provided instead of json-buffer', 101 | async t => { 102 | t.plan(3) 103 | const store = new Map() 104 | const serialize = data => { 105 | t.pass() 106 | return JSON.stringify(data) 107 | } 108 | 109 | const deserialize = data => { 110 | t.pass() 111 | return JSON.parse(data) 112 | } 113 | 114 | const keyv = new Keyv({ store, serialize, deserialize }) 115 | await keyv.set('foo', 'bar') 116 | t.is(await keyv.get('foo'), 'bar') 117 | } 118 | ) 119 | 120 | test.serial('Keyv supports async serializer/deserializer', async t => { 121 | t.plan(3) 122 | const store = new Map() 123 | 124 | const serialize = async data => { 125 | t.pass() 126 | return JSON.stringify(data) 127 | } 128 | 129 | const deserialize = async data => { 130 | t.pass() 131 | return JSON.parse(data) 132 | } 133 | 134 | const keyv = new Keyv({ store, serialize, deserialize }) 135 | await keyv.set('foo', 'bar') 136 | t.is(await keyv.get('foo'), 'bar') 137 | }) 138 | 139 | test.serial('Keyv uses an empty namespace by default', async t => { 140 | const store = new Map() 141 | const keyv = new Keyv({ store }) 142 | await keyv.set('foo', 'bar') 143 | t.is([...store.keys()][0], 'foo') 144 | }) 145 | 146 | test.serial('Default namespace can be overridden', async t => { 147 | const store = new Map() 148 | const keyv = new Keyv({ store, namespace: 'magic' }) 149 | await keyv.set('foo', 'bar') 150 | t.is([...store.keys()][0], 'magic:foo') 151 | }) 152 | 153 | test.serial('An empty namespace stores the key as a string', async t => { 154 | const store = new Map() 155 | const keyv = new Keyv({ store, namespace: '' }) 156 | await keyv.set(42, 'foo') 157 | t.is([...store.keys()][0], '42') 158 | }) 159 | 160 | const store = () => new Map() 161 | keyvTestSuite(test, Keyv, store) 162 | -------------------------------------------------------------------------------- /packages/file/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.7](https://github.com/microlinkhq/keyvhq/compare/v2.1.6...v2.1.7) (2025-02-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/file 9 | 10 | ## [2.1.5](https://github.com/microlinkhq/keyvhq/compare/v2.1.4...v2.1.5) (2024-07-30) 11 | 12 | **Note:** Version bump only for package @keyvhq/file 13 | 14 | ## [2.1.1](https://github.com/microlinkhq/keyvhq/compare/v2.1.0...v2.1.1) (2024-04-23) 15 | 16 | **Note:** Version bump only for package @keyvhq/file 17 | 18 | # [2.1.0](https://github.com/microlinkhq/keyvhq/compare/v2.0.3...v2.1.0) (2023-09-29) 19 | 20 | **Note:** Version bump only for package @keyvhq/file 21 | 22 | # [2.0.0](https://github.com/microlinkhq/keyvhq/compare/v1.6.28...v2.0.0) (2023-04-22) 23 | 24 | ### Features 25 | 26 | * shipped v2 ([#169](https://github.com/microlinkhq/keyvhq/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyvhq/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 27 | 28 | ### BREAKING CHANGES 29 | 30 | * The EventEmitter behavior has been completely removed 31 | -------------------------------------------------------------------------------- /packages/file/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/file [keyv](https://github.com/microlinkhq/keyv/packages/file) 2 | 3 | > A file storage adapter for [Keyv](https://github.com/microlinkhq/keyv). 4 | 5 | ## Install 6 | 7 | ```shell 8 | npm install --save @keyvhq/core @keyvhq/file 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | const KeyvFile = require('@keyvhq/file') 15 | const Keyv = require('@keyvhq/core') 16 | 17 | const keyv = new Keyv({ 18 | store: new KeyvFile('./myfile') 19 | }) 20 | ``` 21 | 22 | ## License 23 | 24 | **@keyvhq/file** © [Luke Childs](https://lukechilds.co), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
25 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 26 | 27 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 28 | -------------------------------------------------------------------------------- /packages/file/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/file", 3 | "description": "A file storage adapter for Keyv", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.7", 6 | "types": "src/index.d.ts", 7 | "main": "src/index.js", 8 | "author": { 9 | "email": "hello@microlink.io", 10 | "name": "microlink.io", 11 | "url": "https://microlink.io" 12 | }, 13 | "repository": { 14 | "directory": "packages/file", 15 | "type": "git", 16 | "url": "git+https://github.com/microlinkhq/keyvhq.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/microlinkhq/keyvhq/issues" 20 | }, 21 | "keywords": [ 22 | "adapter", 23 | "cache", 24 | "file", 25 | "json", 26 | "key", 27 | "keyv", 28 | "storage", 29 | "store", 30 | "ttl", 31 | "value" 32 | ], 33 | "devDependencies": { 34 | "@keyvhq/core": "workspace:*", 35 | "@keyvhq/test-suite": "workspace:*", 36 | "@types/node": "latest", 37 | "ava": "5", 38 | "tsd": "latest", 39 | "typescript": "latest" 40 | }, 41 | "engines": { 42 | "node": ">= 18" 43 | }, 44 | "files": [ 45 | "src" 46 | ], 47 | "scripts": { 48 | "lint": "tsd", 49 | "pretest": "npm run lint", 50 | "test": "ava" 51 | }, 52 | "license": "MIT", 53 | "publishConfig": { 54 | "access": "public" 55 | }, 56 | "tsd": { 57 | "directory": "test" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/file/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '@keyvhq/core' 2 | 3 | declare class KeyvFile implements Store { 4 | constructor (uri: string) 5 | get (key: string): Promise 6 | has (key: string): Promise 7 | set (key: string, value: TValue, ttl?: number): Promise 8 | delete (key: string): Promise 9 | clear (): Promise 10 | iterator (): AsyncGenerator 11 | } 12 | 13 | export = KeyvFile 14 | -------------------------------------------------------------------------------- /packages/file/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { mkdirSync, readFileSync, writeFileSync } = require('fs') 4 | const { dirname } = require('path') 5 | 6 | const read = path => { 7 | try { 8 | return Object.entries(JSON.parse(readFileSync(path, 'utf8'))) 9 | } catch (error) { 10 | if (error.code !== 'ENOENT') throw error 11 | return undefined 12 | } 13 | } 14 | 15 | const write = async map => { 16 | try { 17 | return writeFileSync(map.uri, JSON.stringify(Object.fromEntries(map))) 18 | } catch (error) { 19 | if (error.code !== 'ENOENT') throw error 20 | await mkdirSync(dirname(map.uri), { recursive: true }) 21 | return write(map) 22 | } 23 | } 24 | 25 | class KeyvFile extends Map { 26 | constructor (uri) { 27 | if (!uri) throw new TypeError('A file uri should be provided.') 28 | super(read(uri)) 29 | this.uri = uri 30 | } 31 | 32 | set = (...args) => { 33 | const result = super.set(...args) 34 | write(this) 35 | return result 36 | } 37 | 38 | clear = (...args) => { 39 | super.clear(...args) 40 | write(this) 41 | } 42 | } 43 | 44 | module.exports = KeyvFile 45 | -------------------------------------------------------------------------------- /packages/file/test/fixture.json: -------------------------------------------------------------------------------- 1 | {"foo":"bar"} 2 | -------------------------------------------------------------------------------- /packages/file/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const keyvTestSuite = require('@keyvhq/test-suite') 4 | const Keyv = require('@keyvhq/core') 5 | const path = require('path') 6 | const test = require('ava') 7 | 8 | const KeyvFile = require('../src') 9 | 10 | const store = () => new KeyvFile(path.join(__dirname, 'test.json')) 11 | keyvTestSuite(test, Keyv, store) 12 | -------------------------------------------------------------------------------- /packages/file/test/index.test-d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-new */ 2 | 3 | import Keyv from '@keyvhq/core' 4 | import KeyvFile from '../src' 5 | 6 | new Keyv({ store: new KeyvFile('./test.json') }) 7 | -------------------------------------------------------------------------------- /packages/file/test/store.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { readFileSync, writeFileSync } = require('fs') 4 | const path = require('path') 5 | const test = require('ava') 6 | 7 | const KeyvFile = require('../src') 8 | 9 | const fixture = path.join(__dirname, 'fixture.json') 10 | const backup = readFileSync(fixture, 'utf-8') 11 | 12 | const load = filepath => JSON.parse(readFileSync(filepath)) 13 | 14 | test.afterEach(() => writeFileSync(fixture, backup)) 15 | 16 | test.serial('clear content file', async t => { 17 | const uri = fixture 18 | const store = new KeyvFile(uri) 19 | await store.set('fooz', 'barz') 20 | await store.clear() 21 | t.is(store.size, 0) 22 | t.is(Object.keys(load(uri)).length, 0) 23 | }) 24 | 25 | test.serial('write into a file', async t => { 26 | const uri = fixture 27 | const store = new KeyvFile(uri) 28 | await store.set('fooz', 'barz') 29 | t.is(await store.get('fooz'), 'barz') 30 | t.is(load(uri).fooz, 'barz') 31 | }) 32 | 33 | test.serial('load a previous declared file', async t => { 34 | const uri = fixture 35 | const store = new KeyvFile(uri) 36 | t.is(store.size, 1) 37 | t.is(await store.get('foo'), 'bar') 38 | }) 39 | 40 | test.serial('write into a non previously declared file', async t => { 41 | const uri = path.join(__dirname, 'fixtures2/fixture.json') 42 | const store = new KeyvFile(uri) 43 | await store.set('fooz', 'barz') 44 | t.is(await store.get('fooz'), 'barz') 45 | t.is(load(uri).fooz, 'barz') 46 | }) 47 | -------------------------------------------------------------------------------- /packages/memoize/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.9](https://github.com/microlinkhq/keyv/compare/v2.1.8...v2.1.9) (2025-05-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/memoize 9 | 10 | ## [2.1.7](https://github.com/microlinkhq/keyv/compare/v2.1.6...v2.1.7) (2025-02-20) 11 | 12 | **Note:** Version bump only for package @keyvhq/memoize 13 | 14 | ## [2.1.5](https://github.com/microlinkhq/keyv/compare/v2.1.4...v2.1.5) (2024-07-30) 15 | 16 | **Note:** Version bump only for package @keyvhq/memoize 17 | 18 | ## [2.1.1](https://github.com/microlinkhq/keyv/compare/v2.1.0...v2.1.1) (2024-04-23) 19 | 20 | **Note:** Version bump only for package @keyvhq/memoize 21 | 22 | # [2.1.0](https://github.com/microlinkhq/keyv/compare/v2.0.3...v2.1.0) (2023-09-29) 23 | 24 | ### Features 25 | 26 | * memoize: add typescript definition ([#195](https://github.com/microlinkhq/keyv/issues/195)) ([ea05353](https://github.com/microlinkhq/keyv/commit/ea05353ce1b724a003ce36745d6d91b41f10ca7a)) 27 | 28 | ## [2.0.3](https://github.com/microlinkhq/keyv/compare/v2.0.2...v2.0.3) (2023-06-20) 29 | 30 | **Note:** Version bump only for package @keyvhq/memoize 31 | 32 | # [2.0.0](https://github.com/microlinkhq/keyv/compare/v1.6.28...v2.0.0) (2023-04-22) 33 | 34 | ### Features 35 | 36 | * shipped v2 ([#169](https://github.com/microlinkhq/keyv/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyv/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 37 | 38 | ### BREAKING CHANGES 39 | 40 | * The EventEmitter behavior has been completely removed 41 | 42 | ## [1.6.28](https://github.com/microlinkhq/keyvhq/compare/v1.6.27...v1.6.28) (2023-02-27) 43 | 44 | **Note:** Version bump only for package @keyvhq/memoize 45 | 46 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 47 | 48 | **Note:** Version bump only for package @keyvhq/memoize 49 | 50 | ## [1.6.14](https://github.com/microlinkhq/keyvhq/compare/v1.6.13...v1.6.14) (2022-05-29) 51 | 52 | **Note:** Version bump only for package @keyvhq/memoize 53 | 54 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 55 | 56 | **Note:** Version bump only for package @keyvhq/memoize 57 | 58 | ## [1.6.9](https://github.com/microlinkhq/keyvhq/compare/v1.6.8...v1.6.9) (2022-03-30) 59 | 60 | **Note:** Version bump only for package @keyvhq/memoize 61 | 62 | ## [1.6.6](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.6) (2022-03-09) 63 | 64 | **Note:** Version bump only for package @keyvhq/memoize 65 | 66 | ## [1.6.5](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.5) (2022-03-09) 67 | 68 | **Note:** Version bump only for package @keyvhq/memoize 69 | 70 | ## [1.6.4](https://github.com/microlinkhq/keyvhq/compare/v1.6.3...v1.6.4) (2022-01-24) 71 | 72 | **Note:** Version bump only for package @keyvhq/memoize 73 | 74 | ## [1.6.3](https://github.com/microlinkhq/keyvhq/compare/v1.6.2...v1.6.3) (2021-12-01) 75 | 76 | **Note:** Version bump only for package @keyvhq/memoize 77 | 78 | ## [1.6.2](https://github.com/microlinkhq/keyvhq/compare/v1.6.1...v1.6.2) (2021-11-08) 79 | 80 | **Note:** Version bump only for package @keyvhq/memoize 81 | 82 | # [1.6.0](https://github.com/microlinkhq/keyvhq/compare/v1.5.2...v1.6.0) (2021-10-05) 83 | 84 | **Note:** Version bump only for package @keyvhq/memoize 85 | 86 | ## [1.5.1](https://github.com/microlinkhq/keyvhq/compare/v1.5.0...v1.5.1) (2021-09-16) 87 | 88 | **Note:** Version bump only for package @keyvhq/memoize 89 | 90 | # [1.4.0](https://github.com/microlinkhq/keyvhq/compare/v1.3.0...v1.4.0) (2021-09-12) 91 | 92 | **Note:** Version bump only for package @keyvhq/memoize 93 | 94 | # [1.3.0](https://github.com/microlinkhq/keyvhq/compare/v1.2.7...v1.3.0) (2021-09-01) 95 | 96 | **Note:** Version bump only for package @keyvhq/memoize 97 | 98 | ## [1.2.7](https://github.com/microlinkhq/keyvhq/compare/v1.2.6...v1.2.7) (2021-08-24) 99 | 100 | **Note:** Version bump only for package @keyvhq/memoize 101 | 102 | ## [1.2.6](https://github.com/microlinkhq/keyvhq/compare/v1.2.5...v1.2.6) (2021-08-24) 103 | 104 | **Note:** Version bump only for package @keyvhq/memoize 105 | 106 | ## [1.2.5](https://github.com/microlinkhq/keyvhq/compare/v1.2.4...v1.2.5) (2021-08-19) 107 | 108 | ### Features 109 | 110 | * **memoize:** add a way to invalidate on demand ([82e713b](https://github.com/microlinkhq/keyvhq/commit/82e713b3e73686598f2a5a815467aa9ba6e7d532)) 111 | 112 | ## [1.2.4](https://github.com/microlinkhq/keyvhq/compare/v1.2.3...v1.2.4) (2021-08-19) 113 | 114 | ### Bug Fixes 115 | 116 | * stale ttl value could be 0 ([694ea16](https://github.com/microlinkhq/keyvhq/commit/694ea1680eac5245c78af296033fa96c845ca9f4)) 117 | 118 | ## [1.2.3](https://github.com/microlinkhq/keyvhq/compare/v1.2.2...v1.2.3) (2021-08-17) 119 | 120 | ### Bug Fixes 121 | 122 | * indent ([0e2c243](https://github.com/microlinkhq/keyvhq/commit/0e2c243d687676ed200ac4390d2c30be79db19f1)) 123 | 124 | ## [1.2.2](https://github.com/microlinkhq/keyvhq/compare/v1.2.1...v1.2.2) (2021-08-16) 125 | 126 | **Note:** Version bump only for package @keyvhq/memoize 127 | 128 | ## [1.2.1](https://github.com/microlinkhq/keyvhq/compare/v1.2.0...v1.2.1) (2021-08-12) 129 | 130 | **Note:** Version bump only for package @keyvhq/memoize 131 | 132 | # [1.2.0](https://github.com/microlinkhq/keyvhq/compare/v1.1.1...v1.2.0) (2021-08-11) 133 | 134 | ### Bug Fixes 135 | 136 | * clear pending after stale is refreshed ([ee49c03](https://github.com/microlinkhq/keyvhq/commit/ee49c03b1e6f88231b385f16b2e1ab0688464333)) 137 | 138 | ### Features 139 | 140 | * expose more memoize methods ([c032e93](https://github.com/microlinkhq/keyvhq/commit/c032e939e787bfadf587cd89fde2a9f225307ac5)) 141 | 142 | ## [1.1.1](https://github.com/microlinkhq/keyvhq/compare/v1.1.0...v1.1.1) (2021-07-30) 143 | 144 | **Note:** Version bump only for package @keyvhq/memoize 145 | 146 | # [1.1.0](https://github.com/microlinkhq/keyvhq/compare/v1.0.2...v1.1.0) (2021-07-30) 147 | 148 | ### Features 149 | 150 | * add memoize package ([25e1c4f](https://github.com/microlinkhq/keyvhq/commit/25e1c4f94115dc99e72d1dca28c3e31211d45b5a)) 151 | -------------------------------------------------------------------------------- /packages/memoize/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/memoize [keyv](https://github.com/microlinkhq/keyv/packages/memoize) 2 | 3 | > Memoize any function using Keyv as storage backend. 4 | 5 | ## Install 6 | 7 | ```shell 8 | npm install --save @keyvhq/memoize 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | const memoize = require('@keyvhq/memoize') 15 | 16 | const memoizedRequest = memoize(request) 17 | 18 | memoizedRequest('http://example.com').then(res => { /* from request */ }) 19 | memoizedRequest('http://example.com').then(res => { /* from cache */ }) 20 | ``` 21 | 22 | You can pass a [keyv](https://github.com/microlinkhq/keyv) instance or options to be used as argument: 23 | 24 | ```js 25 | const memoize = require('@keyvhq/memoize') 26 | const Keyv = require('@keyvhq/core') 27 | 28 | memoize(request, { store: new Keyv({ namespace: 'ssr' }) }) 29 | ``` 30 | 31 | ### Defining the key 32 | 33 | By default the first argument of your function call is used as cache key. 34 | 35 | You can pass a function to define how key will be defined. The key function will be called with the same arguments as the function. 36 | 37 | ```js 38 | const sum = (n1, n2) => n1 + n2 39 | 40 | const memoized = memoize(sum, new Keyv(), { 41 | key: (n1, n2) => `${n1}+${n2}` 42 | }) 43 | 44 | // cached as { '1+2': 3 } 45 | memoized(1, 2) 46 | ``` 47 | 48 | The library uses flood protection internally based on the result of the key. 49 | 50 | This means you can make as many requests as you want simultaneously while being sure you won't flood your async resource. 51 | 52 | ### Setup your TTL 53 | 54 | Set `ttl` to a `number` for a static TTL value. 55 | 56 | ```js 57 | const memoizedRequest = memoize(request, new Keyv(), { ttl: 60000 }) 58 | 59 | // cached for 60 seconds 60 | memoizedRequest('http://example.com') 61 | ``` 62 | 63 | Set `ttl` to a `function` for a dynamic TTL value. 64 | 65 | ```js 66 | const memoizedRequest = memoize(request, new Keyv(), { 67 | ttl: (res) => res.statusCode === 200 ? 60000 : 0 68 | }) 69 | 70 | // cached for 60 seconds only if response was 200 OK 71 | memoizedRequest('http://example.com') 72 | ``` 73 | 74 | ### Stale support 75 | 76 | Set `staleTtl` to any `number` of milliseconds. 77 | 78 | If the `ttl` of a requested resource is below this staleness threshold we will still return the stale value but meanwhile asynchronously refresh the value. 79 | 80 | ```js 81 | const memoizedRequest = memoize(request, new Keyv(), { 82 | ttl: 60000, 83 | staleTtl: 10000 84 | }) 85 | 86 | // cached for 60 seconds 87 | memoizedRequest('http://example.com') 88 | 89 | // … 55 seconds later 90 | // Our cache will expire in 5 seconds. 91 | // This is below the staleness threshold of 10 seconds. 92 | // returns cached result + refresh cache on background 93 | memoizedRequest('http://example.com') 94 | ``` 95 | 96 | When the `staleTtl` option is set we won't delete expired items either. The same logic as above applies. 97 | 98 | ## API 99 | 100 | ### memoize(fn, \[keyvOptions], \[options]) 101 | 102 | #### fn 103 | 104 | Type: `Function`
105 | *Required* 106 | 107 | Promise-returning or async function to be memoized. 108 | 109 | #### keyvOptions 110 | 111 | Type: `Object` 112 | 113 | The [Keyv](https://github.com/microlinkhq/keyv) instance or [keyv#options](https://github.com/microlinkhq/keyv#options) to be used. 114 | 115 | #### options 116 | 117 | ##### key 118 | 119 | Type: `Function`
120 | Default: `identity` 121 | 122 | It defines how the get will be obtained. 123 | 124 | The signature of the function should be a `String` to be used as key associated with the cache copy: 125 | 126 | ```js 127 | key: ({ req }) => req.url 128 | ``` 129 | 130 | Just in case you need a more granular control, you can return an `Array`, where the second value determines the expiration behavior: 131 | 132 | ```js 133 | key: ({ req }) => [req.url, req.query.forceExpiration] 134 | ``` 135 | 136 | ##### objectMode 137 | 138 | Type: `Boolean`
139 | Default: `false` 140 | 141 | When is `true`, the result will be an `Array`, being the second item in the `Array` some information about the item: 142 | 143 | ```js 144 | const fn = () => Promise.reject(new Error('NOPE')) 145 | const keyv = new Keyv() 146 | const memoizedSum = memoize(fn, keyv, { staleTtl: 10, objectMode: true }) 147 | 148 | const [sum, info] = await memoizedSum(1, 2) 149 | 150 | console.log(info) 151 | // { 152 | // hasValue: true, 153 | // key: 1, 154 | // isExpired: false, 155 | // isStale: true, 156 | // staleError: Error: NOPE 157 | // } 158 | ``` 159 | 160 | ##### staleTtl 161 | 162 | Type: `Number` or `Function`
163 | Default: `undefined` 164 | 165 | The staleness threshold we will still return the stale value but meanwhile asynchronously refresh the value. 166 | 167 | When you provide a function, the value will be passed as first argument. 168 | 169 | ##### ttl 170 | 171 | Type: `Number` or `Function`
172 | Default: `undefined` 173 | 174 | The time-to-live quantity of time the value will considered as fresh. 175 | 176 | ##### value 177 | 178 | Type: `Function`
179 | Default: `identity` 180 | 181 | A decorate function to be applied before store the value. 182 | 183 | ## License 184 | 185 | **@keyvhq/memoize** © [Dieter Luypaert](https://moeriki.com), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
186 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 187 | 188 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 189 | -------------------------------------------------------------------------------- /packages/memoize/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/memoize", 3 | "description": "Memoize any function using Keyv as storage backend.", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.9", 6 | "types": "./src/index.d.ts", 7 | "main": "src/index.js", 8 | "author": { 9 | "email": "hello@microlink.io", 10 | "name": "microlink.io", 11 | "url": "https://microlink.io" 12 | }, 13 | "repository": { 14 | "directory": "packages/memo", 15 | "type": "git", 16 | "url": "git+https://github.com/microlinkhq/keyv.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/microlinkhq/keyvhq/issues" 20 | }, 21 | "keywords": [ 22 | "cache", 23 | "key", 24 | "keyv", 25 | "memo", 26 | "memoize", 27 | "store", 28 | "ttl", 29 | "value" 30 | ], 31 | "dependencies": { 32 | "@keyvhq/core": "workspace:*", 33 | "mimic-fn": "~3.0.0", 34 | "null-prototype-object": "~1.0.0" 35 | }, 36 | "devDependencies": { 37 | "ava": "5" 38 | }, 39 | "engines": { 40 | "node": ">= 18" 41 | }, 42 | "files": [ 43 | "src" 44 | ], 45 | "scripts": { 46 | "test": "ava" 47 | }, 48 | "license": "MIT", 49 | "publishConfig": { 50 | "access": "public" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/memoize/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Options, Store } from '@keyvhq/core' 2 | 3 | declare function memoize ( 4 | fn: (...args: Args) => Promise, 5 | keyvOptions: Options | Store, 6 | options: { 7 | key?: (value: RawResult) => any 8 | objectMode?: boolean 9 | staleTtl?: number | boolean | ((value: any) => number | boolean) 10 | ttl?: number | ((value: any) => number) 11 | // Difficult to type this one, so we'll just leave it as any 12 | // When options.value is not provided, the return type of the memoized function is Result 13 | // I can't figure out how to type this, so I'm just going to leave it as any 14 | value?: (value: RawResult) => Result 15 | } 16 | ): (...args: Args) => Promise 17 | 18 | export default memoize 19 | -------------------------------------------------------------------------------- /packages/memoize/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const NullProtoObj = require('null-prototype-object') 4 | const Keyv = require('@keyvhq/core') 5 | const mimicFn = require('mimic-fn') 6 | 7 | const isFunction = input => typeof input === 'function' 8 | const isNumber = input => typeof input === 'number' 9 | const isString = input => typeof input === 'string' 10 | const isUndefined = input => input === undefined 11 | const identity = value => value 12 | 13 | function memoize ( 14 | fn, 15 | keyvOptions, 16 | { 17 | key: getKey = identity, 18 | objectMode = false, 19 | staleTtl: rawStaleTtl, 20 | ttl: rawTtl, 21 | value: getValue = identity 22 | } = {} 23 | ) { 24 | const keyv = keyvOptions instanceof Keyv ? keyvOptions : new Keyv(keyvOptions) 25 | const ttl = isFunction(rawTtl) ? rawTtl : () => rawTtl 26 | const staleTtl = isFunction(rawStaleTtl) 27 | ? rawStaleTtl 28 | : isNumber(rawStaleTtl) 29 | ? () => rawStaleTtl 30 | : rawStaleTtl 31 | 32 | const pending = new NullProtoObj() 33 | 34 | /** 35 | * This can be better. Check: 36 | * - https://github.com/lukechilds/keyv/issues/36 37 | * 38 | * @param {string} key 39 | * @return {Promise} { expires:number, value:* } 40 | */ 41 | async function getRaw (key) { 42 | const raw = await keyv.store.get(keyv._getKeyPrefix(key)) 43 | return isString(raw) ? keyv.deserialize(raw) : raw 44 | } 45 | 46 | /** 47 | * @param {string} key 48 | * @param {*} value 49 | * @return {Promise} resolves when updated 50 | */ 51 | async function updateStoredValue (key, raw) { 52 | const value = await getValue(raw) 53 | await keyv.set(key, value, ttl(value)) 54 | return value 55 | } 56 | 57 | /** 58 | * @return {Promise<*>} 59 | */ 60 | function memoized (...args) { 61 | const rawKey = getKey(...args) 62 | const [key, forceExpiration] = Array.isArray(rawKey) ? rawKey : [rawKey] 63 | 64 | if (!isUndefined(pending[key])) return pending[key] 65 | 66 | pending[key] = getRaw(key).then(async data => { 67 | const hasValue = data ? !isUndefined(data.value) : false 68 | const hasExpires = hasValue && typeof data.expires === 'number' 69 | const ttlValue = hasExpires ? data.expires - Date.now() : undefined 70 | const staleTtlValue = 71 | hasExpires && !isUndefined(staleTtl) ? staleTtl(data.value) : false 72 | const isExpired = 73 | forceExpiration === true 74 | ? forceExpiration 75 | : staleTtlValue === false && hasExpires && ttlValue < 0 76 | const isStale = staleTtlValue !== false && ttlValue < staleTtlValue 77 | const info = { hasValue, key, isExpired, isStale, forceExpiration } 78 | const done = value => (objectMode ? [value, info] : value) 79 | 80 | if (hasValue && !isExpired && !isStale) { 81 | pending[key] = undefined 82 | return done(data.value) 83 | } 84 | 85 | const promise = Promise.resolve(fn(...args)).then(value => 86 | updateStoredValue(key, value) 87 | ) 88 | 89 | if (isStale) { 90 | promise 91 | .then(() => (pending[key] = undefined)) 92 | .catch(error => (info.staleError = error)) 93 | return done(data.value) 94 | } 95 | 96 | try { 97 | const value = await promise 98 | pending[key] = undefined 99 | return done(value) 100 | } catch (error) { 101 | pending[key] = undefined 102 | throw error 103 | } 104 | }) 105 | 106 | return pending[key] 107 | } 108 | 109 | mimicFn(memoized, fn) 110 | 111 | return Object.assign(memoized, { keyv, ttl, staleTtl }) 112 | } 113 | 114 | module.exports = memoize 115 | -------------------------------------------------------------------------------- /packages/mongo/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.7](https://github.com/microlinkhq/keyv/compare/v2.1.6...v2.1.7) (2025-02-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/mongo 9 | 10 | ## [2.1.5](https://github.com/microlinkhq/keyv/compare/v2.1.4...v2.1.5) (2024-07-30) 11 | 12 | **Note:** Version bump only for package @keyvhq/mongo 13 | 14 | ## [2.1.1](https://github.com/microlinkhq/keyv/compare/v2.1.0...v2.1.1) (2024-04-23) 15 | 16 | **Note:** Version bump only for package @keyvhq/mongo 17 | 18 | # [2.1.0](https://github.com/microlinkhq/keyv/compare/v2.0.3...v2.1.0) (2023-09-29) 19 | 20 | **Note:** Version bump only for package @keyvhq/mongo 21 | 22 | # [2.0.0](https://github.com/microlinkhq/keyv/compare/v1.6.28...v2.0.0) (2023-04-22) 23 | 24 | ### Features 25 | 26 | * shipped v2 ([#169](https://github.com/microlinkhq/keyv/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyv/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 27 | 28 | ### BREAKING CHANGES 29 | 30 | * The EventEmitter behavior has been completely removed 31 | 32 | ## [1.6.28](https://github.com/microlinkhq/keyvhq/compare/v1.6.27...v1.6.28) (2023-02-27) 33 | 34 | **Note:** Version bump only for package @keyvhq/mongo 35 | 36 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 37 | 38 | **Note:** Version bump only for package @keyvhq/mongo 39 | 40 | ## [1.6.25](https://github.com/microlinkhq/keyvhq/compare/v1.6.24...v1.6.25) (2022-10-20) 41 | 42 | **Note:** Version bump only for package @keyvhq/mongo 43 | 44 | ## [1.6.24](https://github.com/microlinkhq/keyvhq/compare/v1.6.23...v1.6.24) (2022-09-24) 45 | 46 | **Note:** Version bump only for package @keyvhq/mongo 47 | 48 | ## [1.6.22](https://github.com/microlinkhq/keyvhq/compare/v1.6.21...v1.6.22) (2022-08-19) 49 | 50 | **Note:** Version bump only for package @keyvhq/mongo 51 | 52 | ## [1.6.21](https://github.com/microlinkhq/keyvhq/compare/v1.6.20...v1.6.21) (2022-07-22) 53 | 54 | **Note:** Version bump only for package @keyvhq/mongo 55 | 56 | ## [1.6.15](https://github.com/microlinkhq/keyvhq/compare/v1.6.14...v1.6.15) (2022-06-07) 57 | 58 | **Note:** Version bump only for package @keyvhq/mongo 59 | 60 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 61 | 62 | **Note:** Version bump only for package @keyvhq/mongo 63 | 64 | ## [1.6.12](https://github.com/microlinkhq/keyvhq/compare/v1.6.11...v1.6.12) (2022-05-12) 65 | 66 | **Note:** Version bump only for package @keyvhq/mongo 67 | 68 | ## [1.6.11](https://github.com/microlinkhq/keyvhq/compare/v1.6.10...v1.6.11) (2022-04-05) 69 | 70 | **Note:** Version bump only for package @keyvhq/mongo 71 | 72 | ## [1.6.6](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.6) (2022-03-09) 73 | 74 | ### Bug Fixes 75 | 76 | * changed all adapter apis to support (uri, option) ([f78a5dd](https://github.com/microlinkhq/keyvhq/commit/f78a5dd26ebcb2661d99c61328fbd92d9c462149)) 77 | 78 | ## [1.6.5](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.5) (2022-03-09) 79 | 80 | ### Bug Fixes 81 | 82 | * changed all adapter apis to support (uri, option) ([f78a5dd](https://github.com/microlinkhq/keyvhq/commit/f78a5dd26ebcb2661d99c61328fbd92d9c462149)) 83 | 84 | ## [1.6.4](https://github.com/microlinkhq/keyvhq/compare/v1.6.3...v1.6.4) (2022-01-24) 85 | 86 | **Note:** Version bump only for package @keyvhq/mongo 87 | 88 | ## [1.6.3](https://github.com/microlinkhq/keyvhq/compare/v1.6.2...v1.6.3) (2021-12-01) 89 | 90 | **Note:** Version bump only for package @keyvhq/mongo 91 | 92 | ## [1.6.2](https://github.com/microlinkhq/keyvhq/compare/v1.6.1...v1.6.2) (2021-11-08) 93 | 94 | **Note:** Version bump only for package @keyvhq/mongo 95 | 96 | ## [1.6.1](https://github.com/microlinkhq/keyvhq/compare/v1.6.0...v1.6.1) (2021-10-14) 97 | 98 | **Note:** Version bump only for package @keyvhq/mongo 99 | 100 | # [1.6.0](https://github.com/microlinkhq/keyvhq/compare/v1.5.2...v1.6.0) (2021-10-05) 101 | 102 | **Note:** Version bump only for package @keyvhq/mongo 103 | 104 | ## [1.5.1](https://github.com/microlinkhq/keyvhq/compare/v1.5.0...v1.5.1) (2021-09-16) 105 | 106 | **Note:** Version bump only for package @keyvhq/mongo 107 | 108 | # [1.4.0](https://github.com/microlinkhq/keyvhq/compare/v1.3.0...v1.4.0) (2021-09-12) 109 | 110 | ### Bug Fixes 111 | 112 | * namespace api ([fd07736](https://github.com/microlinkhq/keyvhq/commit/fd07736aee52c9bde9a81f075faa85c39d72cc51)) 113 | * **test:** t.fail instead of t.throw ([5dfd9bb](https://github.com/microlinkhq/keyvhq/commit/5dfd9bbdf2b21473e9f5d9a89ac552cce5e26a69)) 114 | 115 | # [1.3.0](https://github.com/microlinkhq/keyvhq/compare/v1.2.7...v1.3.0) (2021-09-01) 116 | 117 | ### Bug Fixes 118 | 119 | * **mongo:** updated check for nonexistent key ([2f9a153](https://github.com/microlinkhq/keyvhq/commit/2f9a153c1998e5ff2b59c1a1c92a32c58ff95368)) 120 | 121 | ## [1.2.7](https://github.com/microlinkhq/keyvhq/compare/v1.2.6...v1.2.7) (2021-08-24) 122 | 123 | **Note:** Version bump only for package @keyvhq/mongo 124 | 125 | # [1.2.0](https://github.com/microlinkhq/keyvhq/compare/v1.1.1...v1.2.0) (2021-08-11) 126 | 127 | **Note:** Version bump only for package @keyvhq/mongo 128 | 129 | ## [1.1.1](https://github.com/microlinkhq/keyvhq/compare/v1.1.0...v1.1.1) (2021-07-30) 130 | 131 | **Note:** Version bump only for package @keyvhq/mongo 132 | 133 | # [1.1.0](https://github.com/microlinkhq/keyvhq/compare/v1.0.2...v1.1.0) (2021-07-30) 134 | 135 | ### Bug Fixes 136 | 137 | * mongooptions ([10b1f9a](https://github.com/microlinkhq/keyvhq/commit/10b1f9ac01c7fc2bbc0830c5dcdfe80a320a845d)) 138 | * removed tls ([81aafe1](https://github.com/microlinkhq/keyvhq/commit/81aafe192e9612bb496f41053bf4520140e6776a)) 139 | 140 | ## [1.0.2](https://github.com/microlinkhq/keyvhq/compare/v1.0.1...v1.0.2) (2021-07-16) 141 | 142 | **Note:** Version bump only for package @keyvhq/mongo 143 | 144 | ## [1.0.1](https://github.com/microlinkhq/keyvhq/compare/v1.0.0...v1.0.1) (2021-07-16) 145 | 146 | **Note:** Version bump only for package @keyvhq/mongo 147 | 148 | ## [0.2.4](https://github.com/microlinkhq/keyvhq/compare/v0.2.0...v0.2.4) (2021-07-01) 149 | 150 | **Note:** Version bump only for package @keyvhq/mongo 151 | -------------------------------------------------------------------------------- /packages/mongo/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/mongo [keyv](https://github.com/microlinkhq/keyv/packages/mongo) 2 | 3 | > MongoDB storage adapter for [Keyv](https://github.com/microlinkhq/keyv). 4 | 5 | Uses TTL indexes to automatically remove expired documents. However [MongoDB doesn't guarantee data will be deleted immediately upon expiration](https://docs.mongodb.com/manual/core/index-ttl/#timing-of-the-delete-operation), so expiry dates are revalidated in Keyv. 6 | 7 | ## Install 8 | 9 | ```shell 10 | npm install --save @keyvhq/core @keyvhq/mongo 11 | ``` 12 | 13 | ## Usage 14 | 15 | > **NOTE**: The mongo uses `url` instead of `uri` to provide the connection string details. 16 | 17 | ```js 18 | const KeyvMongo = require('@keyvhq/mongo') 19 | const Keyv = require('@keyvhq/core') 20 | 21 | const keyv = new Keyv({ 22 | store: new KeyvMongo('mongodb://user:pass@localhost:27017/dbname') 23 | }) 24 | ``` 25 | 26 | You can specify the collection name, by default `'keyv'` is used: 27 | 28 | ```js 29 | const KeyvMongo = require('@keyvhq/mongo') 30 | const Keyv = require('@keyvhq/core') 31 | 32 | const keyv = new Keyv({ 33 | store: new KeyvMongo('mongodb://user:pass@localhost:27017/dbname', { 34 | collection: 'cache' 35 | }) 36 | }) 37 | ``` 38 | 39 | ## License 40 | 41 | **@keyvhq/mongo** © [Luke Childs](https://lukechilds.co), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
42 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 43 | 44 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 45 | -------------------------------------------------------------------------------- /packages/mongo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/mongo", 3 | "description": "MongoDB storage adapter for Keyv", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.7", 6 | "types": "./src/index.d.ts", 7 | "main": "src/index.js", 8 | "author": { 9 | "email": "hello@microlink.io", 10 | "name": "microlink.io", 11 | "url": "https://microlink.io" 12 | }, 13 | "repository": { 14 | "directory": "packages/mongo", 15 | "type": "git", 16 | "url": "git+https://github.com/microlinkhq/keyv.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/microlinkhq/keyvhq/issues" 20 | }, 21 | "keywords": [ 22 | "adapter", 23 | "cache", 24 | "key", 25 | "keyv", 26 | "mongo", 27 | "mongodb", 28 | "storage", 29 | "store", 30 | "ttl", 31 | "value" 32 | ], 33 | "dependencies": { 34 | "mongodb": "~4.13.0", 35 | "pify": "~5.0.0" 36 | }, 37 | "devDependencies": { 38 | "@keyvhq/core": "workspace:*", 39 | "@keyvhq/test-suite": "workspace:*", 40 | "@types/node": "latest", 41 | "ava": "5", 42 | "tsd": "latest", 43 | "typescript": "latest" 44 | }, 45 | "engines": { 46 | "node": ">= 18" 47 | }, 48 | "files": [ 49 | "src" 50 | ], 51 | "scripts": { 52 | "lint": "tsd", 53 | "pretest": "npm run lint", 54 | "test": "ava" 55 | }, 56 | "license": "MIT", 57 | "publishConfig": { 58 | "access": "public" 59 | }, 60 | "tsd": { 61 | "directory": "test" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/mongo/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '@keyvhq/core' 2 | import { MongoClientOptions } from 'mongodb' 3 | 4 | declare class KeyvMongo implements Store { 5 | constructor (uri?: string) 6 | constructor (options?: KeyvMongo.Options) 7 | constructor (uri: string, options?: KeyvMongo.Options) 8 | 9 | get (key: string): Promise 10 | has (key: string): Promise 11 | set (key: string, value: TValue, ttl?: number): Promise 12 | delete (key: string): Promise 13 | clear (): Promise 14 | iterator (): AsyncGenerator 15 | } 16 | 17 | declare namespace KeyvMongo { 18 | interface Options extends MongoClientOptions { 19 | uri?: string | undefined 20 | collection?: string | undefined 21 | } 22 | } 23 | 24 | export = KeyvMongo 25 | -------------------------------------------------------------------------------- /packages/mongo/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const mongodb = require('mongodb') 4 | const pify = require('pify') 5 | 6 | const keyvMongoKeys = ['url', 'collection', 'emitErrors'] 7 | class KeyvMongo { 8 | constructor (url, options) { 9 | url = url || {} 10 | if (typeof url === 'string') { 11 | url = { url } 12 | } 13 | 14 | if (url.uri) { 15 | url = Object.assign({ url: url.uri }, url) 16 | } 17 | 18 | this.options = Object.assign( 19 | { 20 | url: 'mongodb://127.0.0.1:27017', 21 | collection: 'keyv', 22 | useNewUrlParser: true, 23 | useUnifiedTopology: true 24 | }, 25 | url, 26 | options 27 | ) 28 | 29 | const mongoOptions = Object.fromEntries( 30 | Object.entries(this.options).filter( 31 | ([k, v]) => !keyvMongoKeys.includes(k) 32 | ) 33 | ) 34 | this.client = new mongodb.MongoClient(this.options.url, mongoOptions) 35 | 36 | this.mongo = {} 37 | let listeningEvents = false 38 | // Implementation from sql by lukechilds, 39 | this.connect = new Promise(resolve => { 40 | this.client 41 | .connect() 42 | .then(client => { 43 | this.db = client.db(this.options.db) 44 | this.store = this.db.collection(this.options.collection) 45 | this.store.createIndex( 46 | { key: 1 }, 47 | { 48 | unique: true, 49 | background: true 50 | } 51 | ) 52 | this.store.createIndex( 53 | { expiresAt: 1 }, 54 | { 55 | expireAfterSeconds: 0, 56 | background: true 57 | } 58 | ) 59 | for (const method of [ 60 | 'updateOne', 61 | 'findOne', 62 | 'deleteOne', 63 | 'deleteMany' 64 | ]) { 65 | this.store[method] = pify(this.store[method].bind(this.store)) 66 | } 67 | 68 | if (!listeningEvents) { 69 | if (this.options.emitErrors) { 70 | this.client.on('error', error => this.emit('error', error)) 71 | listeningEvents = true 72 | } 73 | } 74 | 75 | resolve(this.store) 76 | }) 77 | .catch(error => { 78 | if (this.options.emitErrors) this.emit('error', error) 79 | }) 80 | }) 81 | } 82 | 83 | get (key) { 84 | return this.connect.then(store => 85 | store.findOne({ key: { $eq: key } }).then(doc => { 86 | if (!doc) { 87 | return undefined 88 | } 89 | 90 | return doc.value 91 | }) 92 | ) 93 | } 94 | 95 | set (key, value, ttl) { 96 | const expiresAt = 97 | typeof ttl === 'number' ? new Date(Date.now() + ttl) : null 98 | return this.connect.then(store => 99 | store.updateOne( 100 | { key: { $eq: key } }, 101 | { $set: { key, value, expiresAt } }, 102 | { upsert: true } 103 | ) 104 | ) 105 | } 106 | 107 | delete (key) { 108 | if (typeof key !== 'string') { 109 | return Promise.resolve(false) 110 | } 111 | 112 | return this.connect.then(store => 113 | store 114 | .deleteOne({ key: { $eq: key } }) 115 | .then(object => object.deletedCount > 0) 116 | ) 117 | } 118 | 119 | clear (namespace) { 120 | return this.connect.then(store => 121 | store 122 | .deleteMany({ 123 | key: new RegExp(`^${namespace ? namespace + ':' : '.*'}`) 124 | }) 125 | .then(() => undefined) 126 | ) 127 | } 128 | 129 | async * iterator (namespace) { 130 | const iterator = await this.connect.then(store => 131 | store 132 | .find({ 133 | key: new RegExp(`^${namespace ? namespace + ':' : '.*'}`) 134 | }) 135 | .map(x => { 136 | return [x.key, x.value] 137 | }) 138 | ) 139 | yield * iterator 140 | } 141 | } 142 | module.exports = KeyvMongo 143 | -------------------------------------------------------------------------------- /packages/mongo/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const keyvTestSuite = require('@keyvhq/test-suite') 4 | const Keyv = require('@keyvhq/core') 5 | const test = require('ava') 6 | 7 | const KeyvMongo = require('..') 8 | 9 | const mongoURL = process.env.MONGO_URL || 'mongodb://127.0.0.1:27017' 10 | const store = () => new KeyvMongo(mongoURL) 11 | keyvTestSuite(test, Keyv, store) 12 | 13 | test('Collection option merges into default options', t => { 14 | const store = new KeyvMongo({ collection: 'foo' }) 15 | t.deepEqual(store.options, { 16 | url: 'mongodb://127.0.0.1:27017', 17 | useNewUrlParser: true, 18 | useUnifiedTopology: true, 19 | collection: 'foo' 20 | }) 21 | }) 22 | 23 | test('Collection option merges into default options if URL is passed', t => { 24 | const store = new KeyvMongo(mongoURL, { collection: 'foo' }) 25 | t.deepEqual(store.options, { 26 | url: mongoURL, 27 | useNewUrlParser: true, 28 | useUnifiedTopology: true, 29 | collection: 'foo' 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /packages/mongo/test/index.test-d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-new */ 2 | 3 | import Keyv from '@keyvhq/core' 4 | import KeyvMongo from '../src' 5 | 6 | new Keyv({ store: new KeyvMongo('mongodb://user:pass@localhost:27017/dbname', { collection: 'cache' }) }) 7 | 8 | new KeyvMongo({ uri: 'mongodb://user:pass@localhost:27017/dbname' }) 9 | new KeyvMongo({ uri: 'mongodb://user:pass@localhost:27017/dbname', collection: 'cache' }) 10 | new KeyvMongo({ collection: 'cache' }) 11 | new KeyvMongo('mongodb://user:pass@localhost:27017/dbname') 12 | 13 | const mongo = new KeyvMongo('mongodb://user:pass@localhost:27017/dbname') 14 | new Keyv({ store: mongo }) 15 | -------------------------------------------------------------------------------- /packages/multi/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.7](https://github.com/microlinkhq/keyv/compare/v2.1.6...v2.1.7) (2025-02-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/multi 9 | 10 | ## [2.1.5](https://github.com/microlinkhq/keyv/compare/v2.1.4...v2.1.5) (2024-07-30) 11 | 12 | **Note:** Version bump only for package @keyvhq/multi 13 | 14 | ## [2.1.1](https://github.com/microlinkhq/keyv/compare/v2.1.0...v2.1.1) (2024-04-23) 15 | 16 | **Note:** Version bump only for package @keyvhq/multi 17 | 18 | # [2.1.0](https://github.com/microlinkhq/keyv/compare/v2.0.3...v2.1.0) (2023-09-29) 19 | 20 | **Note:** Version bump only for package @keyvhq/multi 21 | 22 | ## [2.0.3](https://github.com/microlinkhq/keyv/compare/v2.0.2...v2.0.3) (2023-06-20) 23 | 24 | **Note:** Version bump only for package @keyvhq/multi 25 | 26 | # [2.0.0](https://github.com/microlinkhq/keyv/compare/v1.6.28...v2.0.0) (2023-04-22) 27 | 28 | ### Features 29 | 30 | * shipped v2 ([#169](https://github.com/microlinkhq/keyv/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyv/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 31 | 32 | ### BREAKING CHANGES 33 | 34 | * The EventEmitter behavior has been completely removed 35 | 36 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 37 | 38 | **Note:** Version bump only for package @keyvhq/multi 39 | 40 | ## [1.6.14](https://github.com/microlinkhq/keyvhq/compare/v1.6.13...v1.6.14) (2022-05-29) 41 | 42 | **Note:** Version bump only for package @keyvhq/multi 43 | 44 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 45 | 46 | **Note:** Version bump only for package @keyvhq/multi 47 | 48 | ## [1.6.9](https://github.com/microlinkhq/keyvhq/compare/v1.6.8...v1.6.9) (2022-03-30) 49 | 50 | **Note:** Version bump only for package @keyvhq/multi 51 | 52 | ## [1.6.8](https://github.com/microlinkhq/keyvhq/compare/v1.6.7...v1.6.8) (2022-03-28) 53 | 54 | ### Bug Fixes 55 | 56 | * **multi:** wait deserialize promise ([898fcbe](https://github.com/microlinkhq/keyvhq/commit/898fcbe10f63dcf933cc7b713118560515afe2fd)) 57 | 58 | ## [1.6.6](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.6) (2022-03-09) 59 | 60 | **Note:** Version bump only for package @keyvhq/multi 61 | 62 | ## [1.6.5](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.5) (2022-03-09) 63 | 64 | **Note:** Version bump only for package @keyvhq/multi 65 | 66 | ## [1.6.4](https://github.com/microlinkhq/keyvhq/compare/v1.6.3...v1.6.4) (2022-01-24) 67 | 68 | **Note:** Version bump only for package @keyvhq/multi 69 | 70 | ## [1.6.3](https://github.com/microlinkhq/keyvhq/compare/v1.6.2...v1.6.3) (2021-12-01) 71 | 72 | **Note:** Version bump only for package @keyvhq/multi 73 | 74 | ## [1.6.2](https://github.com/microlinkhq/keyvhq/compare/v1.6.1...v1.6.2) (2021-11-08) 75 | 76 | **Note:** Version bump only for package @keyvhq/multi 77 | 78 | ## [1.6.1](https://github.com/microlinkhq/keyvhq/compare/v1.6.0...v1.6.1) (2021-10-14) 79 | 80 | **Note:** Version bump only for package @keyvhq/multi 81 | 82 | ## [1.5.2](https://github.com/microlinkhq/keyvhq/compare/v1.5.1...v1.5.2) (2021-09-22) 83 | 84 | **Note:** Version bump only for package @keyvhq/multi 85 | 86 | ## [1.5.1](https://github.com/microlinkhq/keyvhq/compare/v1.5.0...v1.5.1) (2021-09-16) 87 | 88 | **Note:** Version bump only for package @keyvhq/multi 89 | 90 | # [1.5.0](https://github.com/microlinkhq/keyvhq/compare/v1.4.0...v1.5.0) (2021-09-15) 91 | 92 | ### Performance Improvements 93 | 94 | * do parallel when is possible ([fed8ded](https://github.com/microlinkhq/keyvhq/commit/fed8dedf5d022fd51bbe556bff65e2962141a0c0)) 95 | -------------------------------------------------------------------------------- /packages/multi/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/multi [keyv](https://github.com/microlinkhq/keyv/packages/multi) 2 | 3 | > A multi storage adapter to manage local and remote store as one for Keyv. 4 | 5 | ## Install 6 | 7 | ```shell 8 | npm install --save @keyvhq/multi 9 | ``` 10 | 11 | ## Usage 12 | 13 | First, you need to provide your `local` and `remote` stores to be used, being possible to use any [Keyv storage adapter](https://keyv.js.org/#/?id=storage-adapters-1#/?id=storage-adapters-1#/?id=storage-adapters-1): 14 | 15 | ```js 16 | const KeyvMulti = require('@keyvhq/multi') 17 | const KeyvRedis = require('@keyvhq/redis') 18 | const Keyv = require('@keyvhq/core') 19 | 20 | const keyv = new Keyv({ 21 | store: new KeyvMulti({ 22 | local: new Map(), 23 | remote: new KeyvRedis() 24 | }) 25 | }) 26 | ``` 27 | 28 | After that, just interact with the store as a single [keyv](https://keyv.js.org/#/?id=usage#/?id=usage#/?id=usage) instance. 29 | 30 | The actions will be performed in parallel when is possible, and the stores will fallback between them to keep them in synchronized. 31 | 32 | ## API 33 | 34 | ### multi(\[options]) 35 | 36 | #### options 37 | 38 | ##### local 39 | 40 | Type: `Object`
41 | Default: `new Keyv()` 42 | 43 | A keyv instance to be used as local strategy. 44 | 45 | ##### remote 46 | 47 | Type: `Object`
48 | Default: `new Keyv()` 49 | 50 | A keyv instance to be used as remote strategy. 51 | 52 | ##### validator 53 | 54 | Type: `Function`
55 | Default: `() => true` 56 | 57 | The validator function is used as a precondition to determining is remote storage should be checked. 58 | 59 | ## License 60 | 61 | **@keyvhq/multi** © [Jytesh](https://github.com/Jytesh), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
62 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 63 | 64 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 65 | -------------------------------------------------------------------------------- /packages/multi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/multi", 3 | "description": "Layered cache with any backend", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.7", 6 | "types": "./src/index.d.ts", 7 | "main": "src/index.js", 8 | "author": { 9 | "email": "hello@microlink.io", 10 | "name": "microlink.io", 11 | "url": "https://microlink.io" 12 | }, 13 | "repository": { 14 | "directory": "packages/memo", 15 | "type": "git", 16 | "url": "git+https://github.com/microlinkhq/keyv.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/microlinkhq/keyvhq/issues" 20 | }, 21 | "keywords": [ 22 | "cache", 23 | "key", 24 | "keyv", 25 | "multicache", 26 | "multilevel", 27 | "store", 28 | "ttl", 29 | "value" 30 | ], 31 | "dependencies": { 32 | "@keyvhq/core": "workspace:*" 33 | }, 34 | "devDependencies": { 35 | "@keyvhq/compress": "latest", 36 | "@keyvhq/sqlite": "latest", 37 | "ava": "5", 38 | "tsd": "latest", 39 | "typescript": "latest" 40 | }, 41 | "engines": { 42 | "node": ">= 18" 43 | }, 44 | "files": [ 45 | "src" 46 | ], 47 | "scripts": { 48 | "lint": "tsd", 49 | "pretest": "npm run lint", 50 | "test": "ava" 51 | }, 52 | "license": "MIT", 53 | "publishConfig": { 54 | "access": "public" 55 | }, 56 | "tsd": { 57 | "directory": "test" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/multi/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import Keyv, { Store } from '@keyvhq/core' 2 | 3 | declare class KeyvMulti implements Store { 4 | constructor (options: KeyvMulti.Options) 5 | 6 | get (key: string): Promise 7 | has (key: string): Promise 8 | set (key: string, value: TValue): Promise 9 | delete (key: string): Promise 10 | clear (options?: KeyvMulti.ClearOptions): Promise 11 | iterator (): AsyncGenerator 12 | } 13 | 14 | declare namespace KeyvMulti { 15 | interface Options { 16 | local?: Keyv 17 | remote?: Keyv 18 | validator?: () => boolean 19 | } 20 | interface ClearOptions { 21 | localOnly?: boolean 22 | } 23 | } 24 | 25 | export = KeyvMulti 26 | -------------------------------------------------------------------------------- /packages/multi/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Keyv = require('@keyvhq/core') 4 | 5 | class MultiCache { 6 | constructor ({ remote = new Keyv(), local = new Keyv(), ...options }) { 7 | const normalizedOptions = Object.assign( 8 | { 9 | validator: () => true 10 | }, 11 | options 12 | ) 13 | this.remote = remote 14 | this.local = local 15 | 16 | Object.keys(normalizedOptions).forEach( 17 | key => (this[key] = normalizedOptions[key]) 18 | ) 19 | } 20 | 21 | async get (...args) { 22 | let res = await this.local.get(...args) 23 | 24 | if (res === undefined || !this.validator(res, ...args)) { 25 | const key = this.remote._getKeyPrefix(...args) 26 | 27 | const raw = await this.remote.store.get(key) 28 | const data = 29 | typeof raw === 'string' ? await this.remote.deserialize(raw) : raw 30 | 31 | const hasValue = data ? data.value !== undefined : false 32 | const isFresh = 33 | hasValue && typeof data.expires === 'number' 34 | ? Date.now() <= data.expires 35 | : true 36 | 37 | if (hasValue && isFresh) { 38 | res = data.value 39 | this.local.set( 40 | this.remote._getKeyUnprefix(key), 41 | data.value, 42 | data.expires 43 | ) 44 | } 45 | } 46 | 47 | return res 48 | } 49 | 50 | async has (...args) { 51 | let res = await this.local.has(...args) 52 | if (res === false || !this.validator(res, ...args)) { 53 | res = await this.remote.has(...args) 54 | } 55 | return res 56 | } 57 | 58 | async set (...args) { 59 | await Promise.all( 60 | ['local', 'remote'].map(store => this[store].set(...args)) 61 | ) 62 | return true 63 | } 64 | 65 | async delete (key, { localOnly = false } = {}) { 66 | await Promise.all( 67 | ['local', !localOnly && 'remote'] 68 | .filter(Boolean) 69 | .map(store => this[store].delete(key)) 70 | ) 71 | 72 | return true 73 | } 74 | 75 | async clear (namespace, { localOnly = false } = {}) { 76 | await Promise.all( 77 | ['local', !localOnly && 'remote'] 78 | .filter(Boolean) 79 | .map(store => this[store].clear(namespace)) 80 | ) 81 | return true 82 | } 83 | } 84 | 85 | module.exports = MultiCache 86 | -------------------------------------------------------------------------------- /packages/multi/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { setTimeout } = require('timers/promises') 4 | const keyvCompress = require('@keyvhq/compress') 5 | const KeyvSqlite = require('@keyvhq/sqlite') 6 | const Keyv = require('@keyvhq/core') 7 | const test = require('ava') 8 | 9 | const KeyvMulti = require('..') 10 | 11 | const remoteStore = () => 12 | keyvCompress( 13 | new Keyv({ 14 | store: new KeyvSqlite({ 15 | uri: 'sqlite://test/testdb.sqlite', 16 | busyTimeout: 30000 17 | }) 18 | }) 19 | ) 20 | 21 | const localStore = () => keyvCompress(new Keyv()) 22 | 23 | test.beforeEach(async () => { 24 | const remote = remoteStore() 25 | const local = localStore() 26 | const store = new KeyvMulti({ remote, local }) 27 | const keyv = new Keyv({ store }) 28 | 29 | return keyv.clear() 30 | }) 31 | 32 | test.serial('.set() sets to both stores', async t => { 33 | const remote = remoteStore() 34 | const local = localStore() 35 | const store = new KeyvMulti({ remote, local }) 36 | 37 | await store.set('foo', 'bar') 38 | 39 | const [remoteRes, localRes, storeRes] = await Promise.all([ 40 | remote.get('foo'), 41 | store.get('foo'), 42 | local.get('foo') 43 | ]) 44 | const result = remoteRes === localRes && storeRes === localRes // Check equality as 'bar' is just a string 45 | t.is(result, true) 46 | }) 47 | 48 | test.serial('.has() returns boolean', async t => { 49 | const remote = remoteStore() 50 | const local = localStore() 51 | const store = new KeyvMulti({ remote, local }) 52 | 53 | await store.set('foo', 'bar') 54 | 55 | t.is(await store.has('foo'), true) 56 | }) 57 | 58 | test.serial('.has() checks both stores', async t => { 59 | const remote = remoteStore() 60 | const store = new KeyvMulti({ remote }) 61 | 62 | await remote.set('fizz', 'buzz') 63 | 64 | t.is(await store.has('fizz'), true) 65 | }) 66 | 67 | test.serial('.delete() deletes both stores', async t => { 68 | const remote = remoteStore() 69 | const local = localStore() 70 | const store = new KeyvMulti({ remote, local }) 71 | 72 | await store.set('fizz', 'buzz') 73 | await store.delete('fizz') 74 | 75 | t.is(await store.get('fizz'), undefined) 76 | t.is(await local.get('fizz'), undefined) 77 | t.is(await remote.get('fizz'), undefined) 78 | }) 79 | 80 | test.serial( 81 | '.delete({ localOnly: true }) deletes only local store', 82 | async t => { 83 | const remote = remoteStore() 84 | const local = localStore() 85 | const store = new KeyvMulti({ remote, local }) 86 | 87 | await store.set('fizz', 'buzz') 88 | await store.delete('fizz', { localOnly: true }) 89 | 90 | t.is(await local.get('fizz'), undefined) 91 | t.is(await remote.get('fizz'), 'buzz') 92 | } 93 | ) 94 | 95 | test.serial('.clear() clears both stores', async t => { 96 | const remote = remoteStore() 97 | const local = localStore() 98 | const store = new KeyvMulti({ remote, local }) 99 | const keyv = new Keyv({ store }) 100 | 101 | await keyv.set('fizz', 'buzz') 102 | await keyv.clear() 103 | 104 | t.is(await store.get('fizz'), undefined) 105 | }) 106 | 107 | test.serial('.clear({ localOnly: true }) clears local store alone', async t => { 108 | const remote = remoteStore() 109 | const local = localStore() 110 | const store = new KeyvMulti({ remote, local }) 111 | const keyv = new Keyv({ store }) 112 | 113 | await keyv.set('fizz', 'buzz') 114 | await keyv.clear({ localOnly: true }) 115 | 116 | t.is(await local.get('fizz'), undefined) 117 | t.is((await keyv.deserialize(await remote.get('fizz'))).value, 'buzz') 118 | }) 119 | 120 | test.serial('ttl is valid', async t => { 121 | const remote = remoteStore() 122 | const local = new Keyv({ ttl: 100 }) // set local ttl 123 | const store = new KeyvMulti({ remote, local }) 124 | 125 | await store.set('foo', 'bar') 126 | await remote.set('foo', 'notbar') 127 | 128 | await setTimeout(100) 129 | t.is(await store.get('foo'), 'notbar') 130 | }) 131 | 132 | test.serial('copy locally when is possible', async t => { 133 | const remote = remoteStore() 134 | const local = new Keyv() 135 | const store = new KeyvMulti({ remote, local }) 136 | 137 | await remote.set('foo', 'bar') 138 | 139 | t.is(await store.get('foo'), 'bar') 140 | t.is(await local.get('foo'), 'bar') 141 | }) 142 | 143 | test.serial('custom validator', async t => { 144 | const remote = remoteStore() 145 | const local = new Keyv() 146 | const store = new KeyvMulti({ 147 | remote, 148 | local, 149 | validator: val => { 150 | if (val.timeSensitiveData) return false // fetch from remote store only 151 | return true 152 | } 153 | }) 154 | 155 | await store.set('1', { timeSensitiveData: 'bar' }) 156 | await store.set('2', { timeSensitiveData: false }) 157 | 158 | t.deepEqual(await store.get('1'), { timeSensitiveData: 'bar' }) // fetched from remote 159 | t.deepEqual(await store.get('2'), { timeSensitiveData: false }) 160 | 161 | await remote.set('1', { timeSensitiveData: 'foo1' }) 162 | await remote.set('2', { timeSensitiveData: 'foo2' }) // set to remote so local has not been updated 163 | 164 | t.deepEqual(await store.get('1'), { timeSensitiveData: 'foo1' }) 165 | t.deepEqual(await store.get('2'), { timeSensitiveData: false }) 166 | }) 167 | -------------------------------------------------------------------------------- /packages/multi/test/index.test-d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-new, @typescript-eslint/no-floating-promises */ 2 | 3 | import Keyv from '@keyvhq/core' 4 | import KeyvMulti from '../src' 5 | 6 | const store = new KeyvMulti({ 7 | local: new Keyv(), 8 | remote: new Keyv() 9 | }) 10 | 11 | new Keyv({ store }).clear({ localOnly: true }) 12 | -------------------------------------------------------------------------------- /packages/mysql/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.7](https://github.com/microlinkhq/keyv/compare/v2.1.6...v2.1.7) (2025-02-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/mysql 9 | 10 | ## [2.1.6](https://github.com/microlinkhq/keyv/compare/v2.1.5...v2.1.6) (2024-09-24) 11 | 12 | **Note:** Version bump only for package @keyvhq/mysql 13 | 14 | ## [2.1.5](https://github.com/microlinkhq/keyv/compare/v2.1.4...v2.1.5) (2024-07-30) 15 | 16 | **Note:** Version bump only for package @keyvhq/mysql 17 | 18 | ## [2.1.1](https://github.com/microlinkhq/keyv/compare/v2.1.0...v2.1.1) (2024-04-23) 19 | 20 | **Note:** Version bump only for package @keyvhq/mysql 21 | 22 | # [2.1.0](https://github.com/microlinkhq/keyv/compare/v2.0.3...v2.1.0) (2023-09-29) 23 | 24 | **Note:** Version bump only for package @keyvhq/mysql 25 | 26 | ## [2.0.3](https://github.com/microlinkhq/keyv/compare/v2.0.2...v2.0.3) (2023-06-20) 27 | 28 | **Note:** Version bump only for package @keyvhq/mysql 29 | 30 | ## [2.0.1](https://github.com/microlinkhq/keyv/compare/v2.0.0...v2.0.1) (2023-05-08) 31 | 32 | **Note:** Version bump only for package @keyvhq/mysql 33 | 34 | # [2.0.0](https://github.com/microlinkhq/keyv/compare/v1.6.28...v2.0.0) (2023-04-22) 35 | 36 | ### Features 37 | 38 | * shipped v2 ([#169](https://github.com/microlinkhq/keyv/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyv/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 39 | 40 | ### BREAKING CHANGES 41 | 42 | * The EventEmitter behavior has been completely removed 43 | 44 | ## [1.6.28](https://github.com/microlinkhq/keyvhq/compare/v1.6.27...v1.6.28) (2023-02-27) 45 | 46 | **Note:** Version bump only for package @keyvhq/mysql 47 | 48 | ## [1.6.27](https://github.com/microlinkhq/keyvhq/compare/v1.6.26...v1.6.27) (2023-01-30) 49 | 50 | **Note:** Version bump only for package @keyvhq/mysql 51 | 52 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 53 | 54 | **Note:** Version bump only for package @keyvhq/mysql 55 | 56 | ## [1.6.16](https://github.com/microlinkhq/keyvhq/compare/v1.6.15...v1.6.16) (2022-06-22) 57 | 58 | ### Bug Fixes 59 | 60 | * use mysql pool instead of connection ([0f87178](https://github.com/microlinkhq/keyvhq/commit/0f87178bcdb2eacf69d3cc822054f4d5ab876182)) 61 | 62 | ## [1.6.15](https://github.com/microlinkhq/keyvhq/compare/v1.6.14...v1.6.15) (2022-06-07) 63 | 64 | **Note:** Version bump only for package @keyvhq/mysql 65 | 66 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 67 | 68 | **Note:** Version bump only for package @keyvhq/mysql 69 | 70 | ## [1.6.7](https://github.com/microlinkhq/keyvhq/compare/v1.6.6...v1.6.7) (2022-03-13) 71 | 72 | **Note:** Version bump only for package @keyvhq/mysql 73 | 74 | ## [1.6.6](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.6) (2022-03-09) 75 | 76 | ### Bug Fixes 77 | 78 | * changed all adapter apis to support (uri, option) ([f78a5dd](https://github.com/microlinkhq/keyvhq/commit/f78a5dd26ebcb2661d99c61328fbd92d9c462149)) 79 | 80 | ## [1.6.5](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.5) (2022-03-09) 81 | 82 | ### Bug Fixes 83 | 84 | * changed all adapter apis to support (uri, option) ([f78a5dd](https://github.com/microlinkhq/keyvhq/commit/f78a5dd26ebcb2661d99c61328fbd92d9c462149)) 85 | 86 | ## [1.6.3](https://github.com/microlinkhq/keyvhq/compare/v1.6.2...v1.6.3) (2021-12-01) 87 | 88 | **Note:** Version bump only for package @keyvhq/mysql 89 | 90 | ## [1.6.2](https://github.com/microlinkhq/keyvhq/compare/v1.6.1...v1.6.2) (2021-11-08) 91 | 92 | **Note:** Version bump only for package @keyvhq/mysql 93 | 94 | # [1.6.0](https://github.com/microlinkhq/keyvhq/compare/v1.5.2...v1.6.0) (2021-10-05) 95 | 96 | **Note:** Version bump only for package @keyvhq/mysql 97 | 98 | ## [1.5.1](https://github.com/microlinkhq/keyvhq/compare/v1.5.0...v1.5.1) (2021-09-16) 99 | 100 | **Note:** Version bump only for package @keyvhq/mysql 101 | 102 | # [1.4.0](https://github.com/microlinkhq/keyvhq/compare/v1.3.0...v1.4.0) (2021-09-12) 103 | 104 | **Note:** Version bump only for package @keyvhq/mysql 105 | 106 | # [1.3.0](https://github.com/microlinkhq/keyvhq/compare/v1.2.7...v1.3.0) (2021-09-01) 107 | 108 | **Note:** Version bump only for package @keyvhq/mysql 109 | 110 | ## [1.2.7](https://github.com/microlinkhq/keyvhq/compare/v1.2.6...v1.2.7) (2021-08-24) 111 | 112 | **Note:** Version bump only for package @keyvhq/mysql 113 | 114 | # [1.2.0](https://github.com/microlinkhq/keyvhq/compare/v1.1.1...v1.2.0) (2021-08-11) 115 | 116 | **Note:** Version bump only for package @keyvhq/mysql 117 | 118 | ## [1.1.1](https://github.com/microlinkhq/keyvhq/compare/v1.1.0...v1.1.1) (2021-07-30) 119 | 120 | **Note:** Version bump only for package @keyvhq/mysql 121 | 122 | # [1.1.0](https://github.com/microlinkhq/keyvhq/compare/v1.0.2...v1.1.0) (2021-07-30) 123 | 124 | **Note:** Version bump only for package @keyvhq/mysql 125 | 126 | ## [1.0.2](https://github.com/microlinkhq/keyvhq/compare/v1.0.1...v1.0.2) (2021-07-16) 127 | 128 | **Note:** Version bump only for package @keyvhq/mysql 129 | 130 | ## [1.0.1](https://github.com/microlinkhq/keyvhq/compare/v1.0.0...v1.0.1) (2021-07-16) 131 | 132 | **Note:** Version bump only for package @keyvhq/mysql 133 | 134 | ## [0.2.4](https://github.com/microlinkhq/keyvhq/compare/v0.2.0...v0.2.4) (2021-07-01) 135 | 136 | **Note:** Version bump only for package @keyvhq/mysql 137 | -------------------------------------------------------------------------------- /packages/mysql/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/mysql [keyv](https://github.com/microlinkhq/keyv/packages/mysql) 2 | 3 | > MySQL/MariaDB storage adapter for [Keyv](https://github.com/microlinkhq/keyv). 4 | 5 | ## Install 6 | 7 | ```shell 8 | npm install --save @keyvhq/core @keyvhq/mysql 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | const KeyvMysql = require('@keyvhq/redis') 15 | const Keyv = require('@keyvhq/core') 16 | 17 | const keyv = new Keyv({ 18 | store: new KeyvMysql('mysql://user:pass@localhost:3306/dbname') 19 | }) 20 | ``` 21 | 22 | You can specify a custom table with the `table` option and the primary key size with `keySize`: 23 | 24 | ```js 25 | const KeyvMysql = require('@keyvhq/redis') 26 | const Keyv = require('@keyvhq/core') 27 | 28 | const keyv = new Keyv({ 29 | store: new KeyvMysql('mysql://user:pass@localhost:3306/dbname', { 30 | table: 'cache', 31 | keySize: 255 32 | }) 33 | }) 34 | ``` 35 | 36 | **Note:** Some MySQL/MariaDB installations won't allow a key size longer than 767 bytes. If you get an error on table creation try reducing `keySize` to 191 or lower. 37 | 38 | ## License 39 | 40 | **@keyvhq/mysql** © [Luke Childs](https://lukechilds.co), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
41 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 42 | 43 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 44 | -------------------------------------------------------------------------------- /packages/mysql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/mysql", 3 | "description": "MySQL/MariaDB storage adapter for Keyv", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.7", 6 | "types": "./src/index.d.ts", 7 | "main": "src/index.js", 8 | "author": { 9 | "email": "hello@microlink.io", 10 | "name": "microlink.io", 11 | "url": "https://microlink.io" 12 | }, 13 | "repository": { 14 | "directory": "packages/keyv-mysql", 15 | "type": "git", 16 | "url": "git+https://github.com/microlinkhq/keyv.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/microlinkhq/keyvhq/issues" 20 | }, 21 | "keywords": [ 22 | "adapter", 23 | "cache", 24 | "key", 25 | "keyv", 26 | "mariadb", 27 | "mysql", 28 | "sql", 29 | "storage", 30 | "store", 31 | "ttl", 32 | "value" 33 | ], 34 | "dependencies": { 35 | "@keyvhq/sql": "workspace:*", 36 | "mysql2": "~3.12.0" 37 | }, 38 | "devDependencies": { 39 | "@keyvhq/core": "workspace:*", 40 | "@keyvhq/test-suite": "workspace:*", 41 | "@types/node": "latest", 42 | "ava": "5", 43 | "tsd": "latest", 44 | "typescript": "latest" 45 | }, 46 | "engines": { 47 | "node": ">= 18" 48 | }, 49 | "files": [ 50 | "src" 51 | ], 52 | "scripts": { 53 | "lint": "tsd", 54 | "pretest": "npm run lint", 55 | "test": "ava" 56 | }, 57 | "license": "MIT", 58 | "publishConfig": { 59 | "access": "public" 60 | }, 61 | "tsd": { 62 | "directory": "test" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/mysql/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '@keyvhq/core' 2 | 3 | declare class KeyvMysql implements Store { 4 | constructor (uri?: string) 5 | constructor (options?: KeyvMysql.Options) 6 | constructor (uri: string, options?: KeyvMysql.Options) 7 | 8 | get (key: string): Promise 9 | has (key: string): Promise 10 | set (key: string, value: TValue): Promise 11 | delete (key: string): Promise 12 | clear (): Promise 13 | iterator (): AsyncGenerator 14 | } 15 | 16 | declare namespace KeyvMysql { 17 | interface Options { 18 | uri?: string | undefined 19 | table?: string | undefined 20 | keySize?: number | undefined 21 | } 22 | } 23 | 24 | export = KeyvMysql 25 | -------------------------------------------------------------------------------- /packages/mysql/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const KeyvSql = require('@keyvhq/sql') 4 | const mysql = require('mysql2/promise') 5 | 6 | class KeyvMysql extends KeyvSql { 7 | constructor (uri, options) { 8 | uri = uri || {} 9 | if (typeof uri === 'string') { 10 | uri = { uri } 11 | } 12 | 13 | if (uri.uri) { 14 | uri = Object.assign({ uri: uri.uri }, uri) 15 | } 16 | 17 | options = Object.assign( 18 | { 19 | dialect: 'mysql', 20 | uri: 'mysql://localhost' 21 | }, 22 | uri, 23 | options 24 | ) 25 | const pool = mysql.createPool(options.uri) 26 | options.connect = () => 27 | Promise.resolve() 28 | .then(() => { 29 | return sql => pool.execute(sql).then(data => data[0]) 30 | }) 31 | 32 | super(options) 33 | } 34 | } 35 | 36 | module.exports = KeyvMysql 37 | -------------------------------------------------------------------------------- /packages/mysql/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const keyvTestSuite = require('@keyvhq/test-suite') 4 | const Keyv = require('@keyvhq/core') 5 | const test = require('ava') 6 | 7 | const KeyvMysql = require('..') 8 | 9 | const dbUrl = process.env.MYSQL_URL || 'mysql://root:root@127.0.0.1/keyv_test' 10 | const store = () => new KeyvMysql(dbUrl) 11 | keyvTestSuite(test, Keyv, store) 12 | -------------------------------------------------------------------------------- /packages/mysql/test/index.test-d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-new */ 2 | 3 | import Keyv from '@keyvhq/core' 4 | import KeyvMysql from '../src' 5 | 6 | new Keyv({ store: new KeyvMysql({ uri: 'mysql://user:pass@localhost:3306/dbname', table: 'cache' }) }) 7 | 8 | new KeyvMysql({ uri: 'mysql://user:pass@localhost:3306/dbname' }) 9 | new KeyvMysql({ table: 'cache' }) 10 | new KeyvMysql({ keySize: 100 }) 11 | 12 | const mysql = new KeyvMysql({ uri: 'mysql://user:pass@localhost:3306/dbname' }) 13 | new Keyv({ store: mysql }) 14 | -------------------------------------------------------------------------------- /packages/offline/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.8](https://github.com/microlinkhq/keyv/compare/v2.1.7...v2.1.8) (2025-03-13) 7 | 8 | **Note:** Version bump only for package @keyvhq/offline 9 | 10 | ## [2.1.7](https://github.com/microlinkhq/keyv/compare/v2.1.6...v2.1.7) (2025-02-20) 11 | 12 | **Note:** Version bump only for package @keyvhq/offline 13 | 14 | ## [2.1.5](https://github.com/microlinkhq/keyv/compare/v2.1.4...v2.1.5) (2024-07-30) 15 | 16 | **Note:** Version bump only for package @keyvhq/offline 17 | 18 | ## [2.1.1](https://github.com/microlinkhq/keyv/compare/v2.1.0...v2.1.1) (2024-04-23) 19 | 20 | **Note:** Version bump only for package @keyvhq/offline 21 | 22 | ## [2.0.3](https://github.com/microlinkhq/keyv/compare/v2.0.2...v2.0.3) (2023-06-20) 23 | 24 | **Note:** Version bump only for package @keyvhq/offline 25 | 26 | # [2.0.0](https://github.com/microlinkhq/keyv/compare/v1.6.28...v2.0.0) (2023-04-22) 27 | 28 | ### Features 29 | 30 | * shipped v2 ([#169](https://github.com/microlinkhq/keyv/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyv/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 31 | 32 | ### BREAKING CHANGES 33 | 34 | * The EventEmitter behavior has been completely removed 35 | 36 | ## [1.6.28](https://github.com/microlinkhq/keyvhq/compare/v1.6.27...v1.6.28) (2023-02-27) 37 | 38 | **Note:** Version bump only for package @keyvhq/offline 39 | 40 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 41 | 42 | **Note:** Version bump only for package @keyvhq/offline 43 | 44 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 45 | 46 | **Note:** Version bump only for package @keyvhq/offline 47 | 48 | ## [1.6.6](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.6) (2022-03-09) 49 | 50 | **Note:** Version bump only for package @keyvhq/offline 51 | 52 | ## [1.6.5](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.5) (2022-03-09) 53 | 54 | **Note:** Version bump only for package @keyvhq/offline 55 | 56 | # [1.3.0](https://github.com/microlinkhq/keyvhq/compare/v1.2.7...v1.3.0) (2021-09-01) 57 | 58 | ### Features 59 | 60 | * add offline decorator ([86a57c8](https://github.com/microlinkhq/keyvhq/commit/86a57c86ef463eaf7aebf4d0d0b6fcebdb1925f9)) 61 | 62 | # Changelog 63 | 64 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 65 | 66 | ### 1.0.3 (2021-07-25) 67 | 68 | ### 1.0.2 (2021-07-19) 69 | 70 | ### 1.0.1 (2021-07-19) 71 | 72 | ## [1.0.0](https://github.com/Kikobeats/keyv-offline/compare/v0.0.1...v1.0.0) (2021-07-16) 73 | 74 | ### 0.0.1 (2021-07-16) 75 | -------------------------------------------------------------------------------- /packages/offline/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/offline [keyv](https://github.com/microlinkhq/keyv/packages/offline) 2 | 3 | > Adds offline capabilities for your keyv instance. 4 | 5 | ## Install 6 | 7 | ```bash 8 | $ npm install @keyvhq/offline --save 9 | ``` 10 | 11 | ## Usage 12 | 13 | All you need to do is to wrap your [keyv](https://keyv.js.org) instance: 14 | 15 | ```js 16 | const KeyvRedis = require('@keyvhq/redis') 17 | const Keyv = require('@keyvhq/core') 18 | 19 | const store = new KeyvRedis({ 20 | uri: 'redis://user:pass@localhost:6379', 21 | maxRetriesPerRequest: 1, 22 | emitErrors: false 23 | }) 24 | 25 | const keyv = new Keyv({ store }) 26 | ``` 27 | 28 | Using `@keyvhq/offline` at the top level: 29 | 30 | ```js 31 | const KeyvOffline = require('@keyvhq/offline') 32 | const keyv = keyvOffline(new Keyv({ store })) 33 | ``` 34 | 35 | Since now, if your store suffers network connectivity issues, your keyv set/get petitions will be temporarily bypassed, preventing your application to crash for that, being more resilient than the default keyv behavior. 36 | 37 | As soon as the connection is re-established, it will be work back as expected. 38 | 39 | In case you need, you can see omitted errors enabling debug doing `DEBUG=@keyvhq/offline*` 40 | 41 | ## License 42 | 43 | **@keyvhq/memoize** © [Kiko Beats](https://kikobeats.com), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
44 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 45 | 46 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 47 | -------------------------------------------------------------------------------- /packages/offline/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/offline", 3 | "description": "Adding offline capabilities for your keyv instance.", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.8", 6 | "main": "src/index.js", 7 | "author": { 8 | "email": "hello@microlink.io", 9 | "name": "microlink.io", 10 | "url": "https://microlink.io" 11 | }, 12 | "repository": { 13 | "directory": "packages/offline", 14 | "type": "git", 15 | "url": "git+https://github.com/microlinkhq/keyv.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/microlinkhq/keyvhq/issues" 19 | }, 20 | "keywords": [ 21 | "cache", 22 | "key", 23 | "keyv", 24 | "offline", 25 | "store", 26 | "ttl", 27 | "value" 28 | ], 29 | "dependencies": { 30 | "debug-logfmt": "~1.2.3", 31 | "serialize-error": "~8.1.0" 32 | }, 33 | "devDependencies": { 34 | "@aws-sdk/client-s3": "latest", 35 | "@keyvhq/core": "workspace:*", 36 | "@keyvhq/redis": "workspace:*", 37 | "ava": "5", 38 | "keyv-s3": "latest" 39 | }, 40 | "engines": { 41 | "node": ">= 18" 42 | }, 43 | "files": [ 44 | "src" 45 | ], 46 | "scripts": { 47 | "test": "ava" 48 | }, 49 | "license": "MIT", 50 | "publishConfig": { 51 | "access": "public" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/offline/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const debug = require('debug-logfmt')('keyv-offline') 4 | const { serializeError } = require('serialize-error') 5 | 6 | function KeyvOffline (keyv) { 7 | if (!(this instanceof KeyvOffline)) return new KeyvOffline(keyv) 8 | 9 | return new Proxy(keyv, { 10 | get: (keyv, method) => { 11 | switch (method) { 12 | case 'get': 13 | return async (...args) => { 14 | try { 15 | const result = await keyv.get(...args) 16 | return result 17 | } catch (error) { 18 | debug('get', serializeError(error)) 19 | return undefined 20 | } 21 | } 22 | case 'set': 23 | return async (...args) => { 24 | try { 25 | const result = await keyv.set(...args) 26 | return result 27 | } catch (error) { 28 | debug('set', serializeError(error)) 29 | return false 30 | } 31 | } 32 | default: 33 | return Reflect.get(keyv, method) 34 | } 35 | } 36 | }) 37 | } 38 | 39 | module.exports = KeyvOffline 40 | -------------------------------------------------------------------------------- /packages/offline/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Keyv = require('@keyvhq/core') 4 | const test = require('ava') 5 | 6 | const keyvOffline = require('../src') 7 | 8 | test('.set return true under nornal behavior', async t => { 9 | const store = new Map() 10 | const keyv = keyvOffline(new Keyv({ store })) 11 | const result = await keyv.set('foo', 'expires in 1 second', 1000) 12 | t.is(result, true) 13 | }) 14 | 15 | test('.get return the expected value under nornal behavior', async t => { 16 | const store = new Map() 17 | const keyv = keyvOffline(new Keyv({ store })) 18 | await keyv.set('foo', 'bar') 19 | t.is(await keyv.get('foo'), 'bar') 20 | }) 21 | -------------------------------------------------------------------------------- /packages/offline/test/keyv-redis.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const KeyvRedis = require('@keyvhq/redis') 4 | const Keyv = require('@keyvhq/core') 5 | const test = require('ava') 6 | 7 | const keyvOffline = require('../src') 8 | 9 | const keyvRedis = new Keyv({ 10 | store: new KeyvRedis({ 11 | uri: 'redis://user:pass@localhost:1337', 12 | maxRetriesPerRequest: 0, 13 | emitErrors: false 14 | }) 15 | }) 16 | 17 | test('.set return false if store is unreachable', async t => { 18 | const keyv = keyvOffline(keyvRedis) 19 | const result = await keyv.set('foo', 'expires in 1 second', 1000) 20 | t.is(result, false) 21 | }) 22 | 23 | test('.set return undefined if store is unreachable', async t => { 24 | const keyv = keyvOffline(keyvRedis) 25 | const result = await keyv.get('foo') 26 | t.is(result, undefined) 27 | }) 28 | 29 | test('keep original keyv methods', async t => { 30 | const keyv = keyvOffline(keyvRedis) 31 | t.is(typeof keyv.clear, 'function') 32 | }) 33 | -------------------------------------------------------------------------------- /packages/offline/test/keyv-s3.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const KeyvS3 = require('keyv-s3') 4 | const test = require('ava') 5 | 6 | const keyvOffline = require('../src') 7 | 8 | const keyvS3 = new KeyvS3({ 9 | region: 'us-east-1', 10 | namespace: undefined, 11 | accessKeyId: 'accessKeyId', 12 | secretAccessKey: 'secretAccessKey', 13 | maxRetries: 0 14 | }) 15 | 16 | test('.set return false if store is unreachable', async t => { 17 | const keyv = keyvOffline(keyvS3) 18 | const result = await keyv.set('foo', 'expires in 1 second', 1000) 19 | t.is(result, false) 20 | }) 21 | 22 | test('.set return undefined if store is unreachable', async t => { 23 | const keyv = keyvOffline(keyvS3) 24 | const result = await keyv.get('foo') 25 | t.is(result, undefined) 26 | }) 27 | -------------------------------------------------------------------------------- /packages/postgres/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.7](https://github.com/microlinkhq/keyv/compare/v2.1.6...v2.1.7) (2025-02-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/postgres 9 | 10 | ## [2.1.6](https://github.com/microlinkhq/keyv/compare/v2.1.5...v2.1.6) (2024-09-24) 11 | 12 | **Note:** Version bump only for package @keyvhq/postgres 13 | 14 | ## [2.1.5](https://github.com/microlinkhq/keyv/compare/v2.1.4...v2.1.5) (2024-07-30) 15 | 16 | **Note:** Version bump only for package @keyvhq/postgres 17 | 18 | ## [2.1.1](https://github.com/microlinkhq/keyv/compare/v2.1.0...v2.1.1) (2024-04-23) 19 | 20 | **Note:** Version bump only for package @keyvhq/postgres 21 | 22 | # [2.1.0](https://github.com/microlinkhq/keyv/compare/v2.0.3...v2.1.0) (2023-09-29) 23 | 24 | **Note:** Version bump only for package @keyvhq/postgres 25 | 26 | ## [2.0.2](https://github.com/microlinkhq/keyv/compare/v2.0.1...v2.0.2) (2023-05-16) 27 | 28 | **Note:** Version bump only for package @keyvhq/postgres 29 | 30 | # [2.0.0](https://github.com/microlinkhq/keyv/compare/v1.6.28...v2.0.0) (2023-04-22) 31 | 32 | ### Features 33 | 34 | * shipped v2 ([#169](https://github.com/microlinkhq/keyv/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyv/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 35 | 36 | ### BREAKING CHANGES 37 | 38 | * The EventEmitter behavior has been completely removed 39 | 40 | ## [1.6.28](https://github.com/microlinkhq/keyvhq/compare/v1.6.27...v1.6.28) (2023-02-27) 41 | 42 | **Note:** Version bump only for package @keyvhq/postgres 43 | 44 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 45 | 46 | **Note:** Version bump only for package @keyvhq/postgres 47 | 48 | ## [1.6.23](https://github.com/microlinkhq/keyvhq/compare/v1.6.22...v1.6.23) (2022-08-24) 49 | 50 | **Note:** Version bump only for package @keyvhq/postgres 51 | 52 | ## [1.6.15](https://github.com/microlinkhq/keyvhq/compare/v1.6.14...v1.6.15) (2022-06-07) 53 | 54 | **Note:** Version bump only for package @keyvhq/postgres 55 | 56 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 57 | 58 | **Note:** Version bump only for package @keyvhq/postgres 59 | 60 | ## [1.6.7](https://github.com/microlinkhq/keyvhq/compare/v1.6.6...v1.6.7) (2022-03-13) 61 | 62 | **Note:** Version bump only for package @keyvhq/postgres 63 | 64 | ## [1.6.6](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.6) (2022-03-09) 65 | 66 | ### Bug Fixes 67 | 68 | * changed all adapter apis to support (uri, option) ([f78a5dd](https://github.com/microlinkhq/keyvhq/commit/f78a5dd26ebcb2661d99c61328fbd92d9c462149)) 69 | 70 | ## [1.6.5](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.5) (2022-03-09) 71 | 72 | ### Bug Fixes 73 | 74 | * changed all adapter apis to support (uri, option) ([f78a5dd](https://github.com/microlinkhq/keyvhq/commit/f78a5dd26ebcb2661d99c61328fbd92d9c462149)) 75 | 76 | ## [1.6.3](https://github.com/microlinkhq/keyvhq/compare/v1.6.2...v1.6.3) (2021-12-01) 77 | 78 | **Note:** Version bump only for package @keyvhq/postgres 79 | 80 | ## [1.6.2](https://github.com/microlinkhq/keyvhq/compare/v1.6.1...v1.6.2) (2021-11-08) 81 | 82 | **Note:** Version bump only for package @keyvhq/postgres 83 | 84 | # [1.6.0](https://github.com/microlinkhq/keyvhq/compare/v1.5.2...v1.6.0) (2021-10-05) 85 | 86 | **Note:** Version bump only for package @keyvhq/postgres 87 | 88 | ## [1.5.1](https://github.com/microlinkhq/keyvhq/compare/v1.5.0...v1.5.1) (2021-09-16) 89 | 90 | **Note:** Version bump only for package @keyvhq/postgres 91 | 92 | # [1.4.0](https://github.com/microlinkhq/keyvhq/compare/v1.3.0...v1.4.0) (2021-09-12) 93 | 94 | **Note:** Version bump only for package @keyvhq/postgres 95 | 96 | # [1.3.0](https://github.com/microlinkhq/keyvhq/compare/v1.2.7...v1.3.0) (2021-09-01) 97 | 98 | **Note:** Version bump only for package @keyvhq/postgres 99 | 100 | ## [1.2.7](https://github.com/microlinkhq/keyvhq/compare/v1.2.6...v1.2.7) (2021-08-24) 101 | 102 | **Note:** Version bump only for package @keyvhq/postgres 103 | 104 | # [1.2.0](https://github.com/microlinkhq/keyvhq/compare/v1.1.1...v1.2.0) (2021-08-11) 105 | 106 | **Note:** Version bump only for package @keyvhq/postgres 107 | 108 | ## [1.1.1](https://github.com/microlinkhq/keyvhq/compare/v1.1.0...v1.1.1) (2021-07-30) 109 | 110 | **Note:** Version bump only for package @keyvhq/postgres 111 | 112 | # [1.1.0](https://github.com/microlinkhq/keyvhq/compare/v1.0.2...v1.1.0) (2021-07-30) 113 | 114 | **Note:** Version bump only for package @keyvhq/postgres 115 | 116 | ## [1.0.2](https://github.com/microlinkhq/keyvhq/compare/v1.0.1...v1.0.2) (2021-07-16) 117 | 118 | **Note:** Version bump only for package @keyvhq/postgres 119 | 120 | ## [1.0.1](https://github.com/microlinkhq/keyvhq/compare/v1.0.0...v1.0.1) (2021-07-16) 121 | 122 | **Note:** Version bump only for package @keyvhq/postgres 123 | 124 | ## [0.2.4](https://github.com/microlinkhq/keyvhq/compare/v0.2.0...v0.2.4) (2021-07-01) 125 | 126 | **Note:** Version bump only for package @keyvhq/postgres 127 | -------------------------------------------------------------------------------- /packages/postgres/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/postgres [keyv](https://github.com/microlinkhq/keyv/packages/postgres) 2 | 3 | > PostgreSQL storage adapter for [Keyv](https://github.com/microlinkhq/keyv). 4 | 5 | Requires Postgres 9.5 or newer for `ON CONFLICT` support to allow performant upserts. [Why?](https://stackoverflow.com/questions/17267417/how-to-upsert-merge-insert-on-duplicate-update-in-postgresql/17267423#17267423) 6 | 7 | ## Install 8 | 9 | ```shell 10 | npm install --save @keyvhq/core @keyvhq/postgres 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```js 16 | const KeyvPostgres = require('@keyvhq/postgres') 17 | const Keyv = require('@keyvhq/core') 18 | 19 | const keyv = new Keyv({ 20 | store: new KeyvPostgres({ 21 | uri: 'postgresql://user:pass@localhost:5432/dbname', 22 | ssl: { 23 | rejectUnauthorized: false 24 | } 25 | }) 26 | }) 27 | ``` 28 | 29 | You can specify the `table` option: 30 | 31 | ```js 32 | const KeyvPostgres = require('@keyvhq/postgres') 33 | const Keyv = require('@keyvhq/core') 34 | 35 | const keyv = new Keyv({ 36 | store: new KeyvPostgres('postgresql://user:pass@localhost:5432/dbname', { 37 | table: 'cache' 38 | }) 39 | }) 40 | ``` 41 | 42 | ## License 43 | 44 | **@keyvhq/postgres** © [Luke Childs](https://lukechilds.co), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
45 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 46 | 47 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 48 | -------------------------------------------------------------------------------- /packages/postgres/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/postgres", 3 | "description": "PostgreSQL storage adapter for Keyv", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.7", 6 | "types": "./src/index.d.ts", 7 | "main": "src/index.js", 8 | "author": { 9 | "email": "hello@microlink.io", 10 | "name": "microlink.io", 11 | "url": "https://microlink.io" 12 | }, 13 | "repository": { 14 | "directory": "packages/postgres", 15 | "type": "git", 16 | "url": "git+https://github.com/microlinkhq/keyv.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/microlinkhq/keyvhq/issues" 20 | }, 21 | "keywords": [ 22 | "adapter", 23 | "cache", 24 | "key", 25 | "keyv", 26 | "postgres", 27 | "postgresql", 28 | "sql", 29 | "storage", 30 | "store", 31 | "ttl", 32 | "value" 33 | ], 34 | "dependencies": { 35 | "@keyvhq/sql": "workspace:*", 36 | "pg": "~8.13.3" 37 | }, 38 | "devDependencies": { 39 | "@keyvhq/core": "workspace:*", 40 | "@keyvhq/test-suite": "workspace:*", 41 | "@types/node": "latest", 42 | "ava": "5", 43 | "tsd": "latest", 44 | "typescript": "latest" 45 | }, 46 | "engines": { 47 | "node": ">= 18" 48 | }, 49 | "files": [ 50 | "src" 51 | ], 52 | "scripts": { 53 | "lint": "tsd", 54 | "pretest": "npm run lint", 55 | "test": "ava --serial" 56 | }, 57 | "license": "MIT", 58 | "publishConfig": { 59 | "access": "public" 60 | }, 61 | "tsd": { 62 | "directory": "test" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/postgres/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '@keyvhq/core' 2 | 3 | declare class KeyvPostgres implements Store { 4 | constructor (uri?: string) 5 | constructor (options?: KeyvPostgres.Options) 6 | constructor (uri: string, options?: KeyvPostgres.Options) 7 | 8 | get (key: string): Promise 9 | has (key: string): Promise 10 | set (key: string, value: TValue): Promise 11 | delete (key: string): Promise 12 | clear (): Promise 13 | iterator (): AsyncGenerator 14 | } 15 | 16 | declare namespace KeyvPostgres { 17 | interface Options { 18 | uri?: string | undefined 19 | table?: string | undefined 20 | keySize?: number | undefined 21 | } 22 | } 23 | 24 | export = KeyvPostgres 25 | -------------------------------------------------------------------------------- /packages/postgres/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const KeyvSql = require('@keyvhq/sql') 4 | const Pool = require('pg').Pool 5 | 6 | class KeyvPostgres extends KeyvSql { 7 | constructor (uri, options) { 8 | uri = uri || {} 9 | if (typeof uri === 'string') { 10 | uri = { uri } 11 | } 12 | 13 | if (uri.uri) { 14 | uri = Object.assign({ uri: uri.uri }, uri) 15 | } 16 | options = Object.assign( 17 | { 18 | dialect: 'postgres', 19 | uri: 'postgresql://localhost:5432' 20 | }, 21 | uri, 22 | options 23 | ) 24 | 25 | options.connect = () => 26 | Promise.resolve().then(() => { 27 | const pool = new Pool({ 28 | connectionString: options.uri, 29 | ssl: options.ssl 30 | }) 31 | return sql => pool.query(sql).then(data => data.rows) 32 | }) 33 | 34 | super(options) 35 | } 36 | } 37 | 38 | module.exports = KeyvPostgres 39 | -------------------------------------------------------------------------------- /packages/postgres/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const keyvTestSuite = require('@keyvhq/test-suite') 4 | const Keyv = require('@keyvhq/core') 5 | const test = require('ava') 6 | 7 | const KeyvPostgres = require('..') 8 | 9 | const store = () => 10 | new KeyvPostgres({ 11 | uri: 'postgresql://postgres:postgres@localhost:5432/keyv_test' 12 | }) 13 | 14 | keyvTestSuite(test, Keyv, store) 15 | -------------------------------------------------------------------------------- /packages/postgres/test/index.test-d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-new */ 2 | 3 | import Keyv from '@keyvhq/core' 4 | import KeyvPostgres from '../src' 5 | 6 | new Keyv({ store: new KeyvPostgres({ uri: 'postgres://user:pass@localhost:5432/dbname', table: 'cache' }) }) 7 | 8 | new KeyvPostgres({ uri: 'postgres://user:pass@localhost:5432/dbname' }) 9 | new KeyvPostgres({ table: 'cache' }) 10 | new KeyvPostgres({ keySize: 100 }) 11 | 12 | const postgres = new KeyvPostgres({ uri: 'postgres://user:pass@localhost:5432/dbname' }) 13 | new Keyv({ store: postgres }) 14 | -------------------------------------------------------------------------------- /packages/redis/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.8](https://github.com/microlinkhq/keyv/compare/v2.1.7...v2.1.8) (2025-03-13) 7 | 8 | **Note:** Version bump only for package @keyvhq/redis 9 | 10 | ## [2.1.7](https://github.com/microlinkhq/keyv/compare/v2.1.6...v2.1.7) (2025-02-20) 11 | 12 | **Note:** Version bump only for package @keyvhq/redis 13 | 14 | ## [2.1.5](https://github.com/microlinkhq/keyv/compare/v2.1.4...v2.1.5) (2024-07-30) 15 | 16 | **Note:** Version bump only for package @keyvhq/redis 17 | 18 | ## [2.1.4](https://github.com/microlinkhq/keyv/compare/v2.1.3...v2.1.4) (2024-04-28) 19 | 20 | ### Performance Improvements 21 | 22 | * **redis:** clear while scan ([#207](https://github.com/microlinkhq/keyv/issues/207)) ([91c0af7](https://github.com/microlinkhq/keyv/commit/91c0af710927a5da890ffae4f91f1a0731c446e6)) 23 | 24 | ## [2.1.3](https://github.com/microlinkhq/keyv/compare/v2.1.2...v2.1.3) (2024-04-28) 25 | 26 | ### Bug Fixes 27 | 28 | * **redis:** add Redis type ([e819f7a](https://github.com/microlinkhq/keyv/commit/e819f7a1b19d12e25aa63e23379f8b7feab6f3d3)), closes [#205](https://github.com/microlinkhq/keyv/issues/205) 29 | 30 | ## [2.1.2](https://github.com/microlinkhq/keyv/compare/v2.1.1...v2.1.2) (2024-04-25) 31 | 32 | ### Bug Fixes 33 | 34 | * **redis:** expose Redis constructor ([b8ead88](https://github.com/microlinkhq/keyv/commit/b8ead883b26cc17e097f81207ec52c7e238b67c7)), closes [#205](https://github.com/microlinkhq/keyv/issues/205) 35 | 36 | ## [2.1.1](https://github.com/microlinkhq/keyv/compare/v2.1.0...v2.1.1) (2024-04-23) 37 | 38 | **Note:** Version bump only for package @keyvhq/redis 39 | 40 | # [2.1.0](https://github.com/microlinkhq/keyv/compare/v2.0.3...v2.1.0) (2023-09-29) 41 | 42 | **Note:** Version bump only for package @keyvhq/redis 43 | 44 | # [2.0.0](https://github.com/microlinkhq/keyv/compare/v1.6.28...v2.0.0) (2023-04-22) 45 | 46 | ### Features 47 | 48 | * shipped v2 ([#169](https://github.com/microlinkhq/keyv/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyv/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 49 | 50 | ### BREAKING CHANGES 51 | 52 | * The EventEmitter behavior has been completely removed 53 | 54 | ## [1.6.28](https://github.com/microlinkhq/keyvhq/compare/v1.6.27...v1.6.28) (2023-02-27) 55 | 56 | **Note:** Version bump only for package @keyvhq/redis 57 | 58 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 59 | 60 | **Note:** Version bump only for package @keyvhq/redis 61 | 62 | ## [1.6.21](https://github.com/microlinkhq/keyvhq/compare/v1.6.20...v1.6.21) (2022-07-22) 63 | 64 | **Note:** Version bump only for package @keyvhq/redis 65 | 66 | ## [1.6.20](https://github.com/microlinkhq/keyvhq/compare/v1.6.19...v1.6.20) (2022-07-12) 67 | 68 | **Note:** Version bump only for package @keyvhq/redis 69 | 70 | ## [1.6.19](https://github.com/microlinkhq/keyvhq/compare/v1.6.18...v1.6.19) (2022-07-12) 71 | 72 | **Note:** Version bump only for package @keyvhq/redis 73 | 74 | ## [1.6.18](https://github.com/microlinkhq/keyvhq/compare/v1.6.17...v1.6.18) (2022-07-12) 75 | 76 | **Note:** Version bump only for package @keyvhq/redis 77 | 78 | ## [1.6.17](https://github.com/microlinkhq/keyvhq/compare/v1.6.16...v1.6.17) (2022-06-27) 79 | 80 | **Note:** Version bump only for package @keyvhq/redis 81 | 82 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 83 | 84 | ### Bug Fixes 85 | 86 | * associate `emitErros` with keyv instance ([3a88e0a](https://github.com/microlinkhq/keyvhq/commit/3a88e0a4af316769c1845a9f5c82a3b8d25120e2)) 87 | 88 | ## [1.6.10](https://github.com/microlinkhq/keyvhq/compare/v1.6.9...v1.6.10) (2022-04-01) 89 | 90 | ### Bug Fixes 91 | 92 | * **types:** fixed typings in redis ([9fc462f](https://github.com/microlinkhq/keyvhq/commit/9fc462fd068eac98a1ec185a5e8ffbea912af72f)) 93 | 94 | ## [1.6.6](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.6) (2022-03-09) 95 | 96 | ### Bug Fixes 97 | 98 | * linter ([328d51c](https://github.com/microlinkhq/keyvhq/commit/328d51c1a16f753d9341246184eab79203afdda4)) 99 | 100 | ## [1.6.5](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.5) (2022-03-09) 101 | 102 | ### Bug Fixes 103 | 104 | * linter ([328d51c](https://github.com/microlinkhq/keyvhq/commit/328d51c1a16f753d9341246184eab79203afdda4)) 105 | 106 | ## [1.6.2](https://github.com/microlinkhq/keyvhq/compare/v1.6.1...v1.6.2) (2021-11-08) 107 | 108 | **Note:** Version bump only for package @keyvhq/redis 109 | 110 | ## [1.6.1](https://github.com/microlinkhq/keyvhq/compare/v1.6.0...v1.6.1) (2021-10-14) 111 | 112 | **Note:** Version bump only for package @keyvhq/redis 113 | 114 | # [1.6.0](https://github.com/microlinkhq/keyvhq/compare/v1.5.2...v1.6.0) (2021-10-05) 115 | 116 | **Note:** Version bump only for package @keyvhq/redis 117 | 118 | ## [1.5.1](https://github.com/microlinkhq/keyvhq/compare/v1.5.0...v1.5.1) (2021-09-16) 119 | 120 | **Note:** Version bump only for package @keyvhq/redis 121 | 122 | # [1.4.0](https://github.com/microlinkhq/keyvhq/compare/v1.3.0...v1.4.0) (2021-09-12) 123 | 124 | ### Bug Fixes 125 | 126 | * fixed namespacing bugs ([f14cd4f](https://github.com/microlinkhq/keyvhq/commit/f14cd4f1651fc866e96785dff0f33f807a1b8493)) 127 | * namespace api ([fd07736](https://github.com/microlinkhq/keyvhq/commit/fd07736aee52c9bde9a81f075faa85c39d72cc51)) 128 | 129 | # [1.3.0](https://github.com/microlinkhq/keyvhq/compare/v1.2.7...v1.3.0) (2021-09-01) 130 | 131 | ### Bug Fixes 132 | 133 | * redis iterator not working when keys are present ([ba7d28c](https://github.com/microlinkhq/keyvhq/commit/ba7d28cde0fe86e305f7db125d12629938fc5a70)) 134 | 135 | ## [1.2.7](https://github.com/microlinkhq/keyvhq/compare/v1.2.6...v1.2.7) (2021-08-24) 136 | 137 | **Note:** Version bump only for package @keyvhq/redis 138 | 139 | # [1.2.0](https://github.com/microlinkhq/keyvhq/compare/v1.1.1...v1.2.0) (2021-08-11) 140 | 141 | **Note:** Version bump only for package @keyvhq/redis 142 | 143 | ## [1.1.1](https://github.com/microlinkhq/keyvhq/compare/v1.1.0...v1.1.1) (2021-07-30) 144 | 145 | **Note:** Version bump only for package @keyvhq/redis 146 | 147 | # [1.1.0](https://github.com/microlinkhq/keyvhq/compare/v1.0.2...v1.1.0) (2021-07-30) 148 | 149 | **Note:** Version bump only for package @keyvhq/redis 150 | 151 | ## [1.0.2](https://github.com/microlinkhq/keyvhq/compare/v1.0.1...v1.0.2) (2021-07-16) 152 | 153 | **Note:** Version bump only for package @keyvhq/redis 154 | 155 | ## [1.0.1](https://github.com/microlinkhq/keyvhq/compare/v1.0.0...v1.0.1) (2021-07-16) 156 | 157 | **Note:** Version bump only for package @keyvhq/redis 158 | 159 | ## [0.2.4](https://github.com/microlinkhq/keyvhq/compare/v0.2.0...v0.2.4) (2021-07-01) 160 | 161 | **Note:** Version bump only for package @keyvhq/redis 162 | -------------------------------------------------------------------------------- /packages/redis/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | WORKDIR /repo 3 | RUN npm install 4 | CMD npm test -------------------------------------------------------------------------------- /packages/redis/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/redis [keyv](https://github.com/microlinkhq/keyv/packages/redis) 2 | 3 | > Redis storage adapter for [Keyv](https://github.com/microlinkhq/keyv). 4 | 5 | TTL functionality is handled directly by Redis so no timestamps are stored and expired keys are cleaned up internally. 6 | 7 | ## Install 8 | 9 | ```shell 10 | npm install --save @keyvhq/core @keyvhq/redis 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```js 16 | const KeyvRedis = require('@keyvhq/redis') 17 | const Keyv = require('@keyvhq/core') 18 | 19 | const keyv = new Keyv({ store: new KeyvRedis('redis://user:pass@localhost:6379') }) 20 | ``` 21 | 22 | Any valid [`Redis`](https://github.com/luin/ioredis#connect-to-redis) options will be passed directly through: 23 | 24 | ```js 25 | const KeyvRedis = require('@keyvhq/redis') 26 | const Keyv = require('@keyvhq/core') 27 | 28 | const keyv = new Keyv({ 29 | store: new KeyvRedis('redis://user:pass@localhost:6379', { 30 | disable_resubscribing: true 31 | }) 32 | }) 33 | ``` 34 | 35 | Or you can reuse a previously declared `Redis` instance: 36 | 37 | ```js 38 | const KeyvRedis = require('@keyvhq/redis') 39 | const Keyv = require('@keyvhq/core') 40 | 41 | const { Redis } = KeyvRedis 42 | 43 | const redis = new Redis('redis://user:pass@localhost:6379') 44 | const keyv = new Keyv({ store: new KeyvRedis(redis) }) 45 | ``` 46 | 47 | ## License 48 | 49 | **@keyvhq/redis** © [Luke Childs](https://lukechilds.co), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
50 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 51 | 52 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 53 | -------------------------------------------------------------------------------- /packages/redis/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | redis: 5 | image: redis:latest 6 | 7 | test: 8 | build: . 9 | environment: 10 | REDIS_HOST: redis 11 | volumes: 12 | - ./:/repo -------------------------------------------------------------------------------- /packages/redis/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/redis", 3 | "description": "Redis storage adapter for Keyv", 4 | "homepage": "https://github.com/microlinkhq/keyv", 5 | "version": "2.1.8", 6 | "types": "./src/index.d.ts", 7 | "main": "src/index.js", 8 | "author": { 9 | "email": "hello@microlink.io", 10 | "name": "microlink.io", 11 | "url": "https://microlink.io" 12 | }, 13 | "repository": { 14 | "directory": "packages/redis", 15 | "type": "git", 16 | "url": "git+https://github.com/microlinkhq/keyv.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/microlinkhq/keyvhq/issues" 20 | }, 21 | "keywords": [ 22 | "adapter", 23 | "cache", 24 | "key", 25 | "keyv", 26 | "redis", 27 | "storage", 28 | "store", 29 | "ttl", 30 | "value" 31 | ], 32 | "dependencies": { 33 | "ioredis": "~5.6.0" 34 | }, 35 | "devDependencies": { 36 | "@keyvhq/core": "workspace:*", 37 | "@keyvhq/test-suite": "workspace:*", 38 | "@types/ioredis": "latest", 39 | "@types/node": "latest", 40 | "ava": "5", 41 | "tsd": "latest", 42 | "typescript": "latest" 43 | }, 44 | "engines": { 45 | "node": ">= 18" 46 | }, 47 | "files": [ 48 | "src" 49 | ], 50 | "scripts": { 51 | "lint": "tsd", 52 | "posttest:docker": "docker-compose down --rmi local", 53 | "pretest": "npm run lint", 54 | "test": "ava", 55 | "test:docker": "docker-compose up --build --abort-on-container-exit --exit-code-from test" 56 | }, 57 | "license": "MIT", 58 | "publishConfig": { 59 | "access": "public" 60 | }, 61 | "tsd": { 62 | "directory": "test" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/redis/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '@keyvhq/core' 2 | import { Redis as IORedis, RedisOptions } from 'ioredis' 3 | 4 | declare class KeyvRedis implements Store { 5 | constructor (options?: KeyvRedis.Options) 6 | constructor (redis: IORedis) 7 | constructor (uri: string, options?: KeyvRedis.Options) 8 | 9 | get (key: string): Promise 10 | has (key: string): Promise 11 | set (key: string, value: TValue, ttl?: number): Promise 12 | delete (key: string): Promise 13 | clear (): Promise 14 | iterator (): AsyncGenerator 15 | } 16 | 17 | declare namespace KeyvRedis { 18 | interface Options extends RedisOptions { 19 | uri?: string | undefined 20 | emitErrors?: boolean | true 21 | } 22 | const Redis: typeof IORedis 23 | } 24 | 25 | export = KeyvRedis 26 | -------------------------------------------------------------------------------- /packages/redis/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Redis = require('ioredis') 4 | 5 | const { promisify } = require('util') 6 | const stream = require('stream') 7 | 8 | const { Transform } = stream 9 | 10 | const pipeline = promisify(stream.pipeline) 11 | 12 | const normalizeArguments = (input, options) => { 13 | if (input instanceof Redis) return input 14 | const { uri, ...opts } = Object.assign( 15 | typeof input === 'string' ? { uri: input } : input, 16 | options 17 | ) 18 | return new Redis(uri, opts) 19 | } 20 | 21 | class KeyvRedis { 22 | constructor (uri, options) { 23 | const redis = normalizeArguments(uri, options) 24 | this.redis = redis 25 | } 26 | 27 | async get (key) { 28 | const value = await this.redis.get(key) 29 | return value === null ? undefined : value 30 | } 31 | 32 | async set (key, value, ttl) { 33 | return typeof ttl === 'number' 34 | ? this.redis.set(key, value, 'PX', ttl) 35 | : this.redis.set(key, value) 36 | } 37 | 38 | async delete (key) { 39 | const result = await this.redis.unlink(key) 40 | return result > 0 41 | } 42 | 43 | async clear (namespace) { 44 | const match = namespace ? `${namespace}:*` : '*' 45 | const stream = this.redis.scanStream({ match }) 46 | const unlinkKeys = new Transform({ 47 | objectMode: true, 48 | transform: (keys, _, next) => keys.length > 0 ? this.redis.unlink(keys).then(() => next()) : next() 49 | }) 50 | await promisify(pipeline)(stream, unlinkKeys) 51 | } 52 | 53 | async * iterator (namespace) { 54 | const scan = this.redis.scan.bind(this.redis) 55 | const get = this.redis.mget.bind(this.redis) 56 | async function * iterate (curs, pattern) { 57 | const [cursor, keys] = await scan(curs, 'MATCH', pattern) 58 | if (!keys.length) return 59 | const values = await get(keys) 60 | for (const i in keys) { 61 | if (Object.prototype.hasOwnProperty.call(keys, i)) { 62 | const key = keys[i] 63 | const value = values[i] 64 | yield [key, value] 65 | } 66 | } 67 | 68 | if (cursor !== '0') { 69 | yield * iterate(cursor, pattern) 70 | } 71 | } 72 | 73 | yield * iterate(0, `${namespace ? namespace + ':' : ''}*`) 74 | } 75 | } 76 | 77 | module.exports = KeyvRedis 78 | 79 | module.exports.Redis = Redis 80 | -------------------------------------------------------------------------------- /packages/redis/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const keyvTestSuite = require('@keyvhq/test-suite') 4 | const Keyv = require('@keyvhq/core') 5 | const Redis = require('ioredis') 6 | const test = require('ava') 7 | 8 | const KeyvRedis = require('..') 9 | 10 | const { REDIS_HOST = 'localhost' } = process.env 11 | const redisURI = `redis://${REDIS_HOST}` 12 | 13 | const store = () => new KeyvRedis(redisURI) 14 | keyvTestSuite(test, Keyv, store) 15 | 16 | test('reuse a redis instance', async t => { 17 | const redis = new Redis(redisURI) 18 | redis.singleton = true 19 | 20 | const store = new KeyvRedis(redis) 21 | 22 | const keyvOne = new Keyv({ store, namespace: 'one' }) 23 | 24 | t.true(store.redis.singleton) 25 | 26 | await keyvOne.set('foo', 'bar') 27 | 28 | t.is(await keyvOne.get('foo'), 'bar') 29 | t.is((await keyvOne.deserialize(await redis.get('one:foo'))).value, 'bar') 30 | t.true(keyvOne.store.redis.singleton) 31 | 32 | const keyvTwo = new Keyv({ store, namespace: 'two' }) 33 | await keyvTwo.set('foo', 'bar') 34 | 35 | t.is(await keyvTwo.get('foo'), 'bar') 36 | t.is((await keyvTwo.deserialize(await redis.get('two:foo'))).value, 'bar') 37 | t.true(keyvTwo.store.redis.singleton) 38 | }) 39 | 40 | test.after.always(async () => { 41 | const keyv = new Keyv({ store: store() }) 42 | await keyv.clear() 43 | }) 44 | -------------------------------------------------------------------------------- /packages/redis/test/index.test-d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-new */ 2 | 3 | import Keyv from '@keyvhq/core' 4 | import KeyvRedis from '../src' 5 | 6 | new Keyv({ 7 | store: new KeyvRedis({ 8 | uri: 'redis://user:pass@localhost:6379', 9 | db: 1 10 | }) 11 | }) 12 | 13 | new KeyvRedis({ uri: 'redis://user:pass@localhost:6379' }) 14 | 15 | new KeyvRedis('redis://user:pass@localhost:6379', { 16 | db: 1 17 | }) 18 | 19 | new KeyvRedis('redis://user:pass@localhost:6379', { 20 | uri: 'redis://user:pass@localhost:6379', 21 | db: 1 22 | }) 23 | 24 | const redis = new KeyvRedis('redis://user:pass@localhost:6379') 25 | 26 | new Keyv({ store: redis }) 27 | 28 | new KeyvRedis.Redis('redis://user:pass@localhost:6379') 29 | -------------------------------------------------------------------------------- /packages/sql/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.7](https://github.com/microlinkhq/keyv/compare/v2.1.6...v2.1.7) (2025-02-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/sql 9 | 10 | ## [2.1.6](https://github.com/microlinkhq/keyv/compare/v2.1.5...v2.1.6) (2024-09-24) 11 | 12 | **Note:** Version bump only for package @keyvhq/sql 13 | 14 | ## [2.1.5](https://github.com/microlinkhq/keyv/compare/v2.1.4...v2.1.5) (2024-07-30) 15 | 16 | **Note:** Version bump only for package @keyvhq/sql 17 | 18 | ## [2.1.1](https://github.com/microlinkhq/keyv/compare/v2.1.0...v2.1.1) (2024-04-23) 19 | 20 | **Note:** Version bump only for package @keyvhq/sql 21 | 22 | # [2.0.0](https://github.com/microlinkhq/keyv/compare/v1.6.28...v2.0.0) (2023-04-22) 23 | 24 | ### Features 25 | 26 | * shipped v2 ([#169](https://github.com/microlinkhq/keyv/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyv/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 27 | 28 | ### BREAKING CHANGES 29 | 30 | * The EventEmitter behavior has been completely removed 31 | 32 | ## [1.6.28](https://github.com/microlinkhq/keyvhq/compare/v1.6.27...v1.6.28) (2023-02-27) 33 | 34 | **Note:** Version bump only for package @keyvhq/sql 35 | 36 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 37 | 38 | **Note:** Version bump only for package @keyvhq/sql 39 | 40 | ## [1.6.15](https://github.com/microlinkhq/keyvhq/compare/v1.6.14...v1.6.15) (2022-06-07) 41 | 42 | **Note:** Version bump only for package @keyvhq/sql 43 | 44 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 45 | 46 | **Note:** Version bump only for package @keyvhq/sql 47 | 48 | ## [1.6.7](https://github.com/microlinkhq/keyvhq/compare/v1.6.6...v1.6.7) (2022-03-13) 49 | 50 | **Note:** Version bump only for package @keyvhq/sql 51 | 52 | ## [1.6.6](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.6) (2022-03-09) 53 | 54 | ### Bug Fixes 55 | 56 | * linter ([c36c964](https://github.com/microlinkhq/keyvhq/commit/c36c964a16e79d8bd0e4fd62a926293e89ea0472)) 57 | 58 | ## [1.6.5](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.5) (2022-03-09) 59 | 60 | ### Bug Fixes 61 | 62 | * linter ([c36c964](https://github.com/microlinkhq/keyvhq/commit/c36c964a16e79d8bd0e4fd62a926293e89ea0472)) 63 | 64 | ## [1.6.3](https://github.com/microlinkhq/keyvhq/compare/v1.6.2...v1.6.3) (2021-12-01) 65 | 66 | **Note:** Version bump only for package @keyvhq/sql 67 | 68 | # [1.6.0](https://github.com/microlinkhq/keyvhq/compare/v1.5.2...v1.6.0) (2021-10-05) 69 | 70 | **Note:** Version bump only for package @keyvhq/sql 71 | 72 | # [1.4.0](https://github.com/microlinkhq/keyvhq/compare/v1.3.0...v1.4.0) (2021-09-12) 73 | 74 | ### Bug Fixes 75 | 76 | * namespace api ([fd07736](https://github.com/microlinkhq/keyvhq/commit/fd07736aee52c9bde9a81f075faa85c39d72cc51)) 77 | 78 | # [1.3.0](https://github.com/microlinkhq/keyvhq/compare/v1.2.7...v1.3.0) (2021-09-01) 79 | 80 | **Note:** Version bump only for package @keyvhq/sql 81 | 82 | ## [1.2.7](https://github.com/microlinkhq/keyvhq/compare/v1.2.6...v1.2.7) (2021-08-24) 83 | 84 | **Note:** Version bump only for package @keyvhq/sql 85 | 86 | # [1.2.0](https://github.com/microlinkhq/keyvhq/compare/v1.1.1...v1.2.0) (2021-08-11) 87 | 88 | **Note:** Version bump only for package @keyvhq/sql 89 | 90 | ## [1.1.1](https://github.com/microlinkhq/keyvhq/compare/v1.1.0...v1.1.1) (2021-07-30) 91 | 92 | **Note:** Version bump only for package @keyvhq/sql 93 | 94 | # [1.1.0](https://github.com/microlinkhq/keyvhq/compare/v1.0.2...v1.1.0) (2021-07-30) 95 | 96 | **Note:** Version bump only for package @keyvhq/sql 97 | 98 | ## [1.0.2](https://github.com/microlinkhq/keyvhq/compare/v1.0.1...v1.0.2) (2021-07-16) 99 | 100 | **Note:** Version bump only for package @keyvhq/sql 101 | 102 | ## [1.0.1](https://github.com/microlinkhq/keyvhq/compare/v1.0.0...v1.0.1) (2021-07-16) 103 | 104 | **Note:** Version bump only for package @keyvhq/sql 105 | 106 | ## [0.2.4](https://github.com/microlinkhq/keyvhq/compare/v0.2.0...v0.2.4) (2021-07-01) 107 | 108 | **Note:** Version bump only for package @keyvhq/sql 109 | -------------------------------------------------------------------------------- /packages/sql/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/sql [keyv](https://github.com/microlinkhq/keyv/packages/sql) 2 | 3 | Parent class containing the common logic for SQL based Keyv storage adapters: 4 | 5 | - [`@keyvhq/sqlite`](https://github.com/microlinkhq/packageS/sqlite) 6 | - [`@keyvhq/postgres`](https://github.com/microlinkhq/packageS/postgres) 7 | - [`@keyvhq/mysql`](https://github.com/microlinkhq/packageS/mysql) 8 | 9 | ## License 10 | 11 | **@keyvhq/sql** © [Luke Childs](https://lukechilds.co), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
12 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 13 | 14 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 15 | -------------------------------------------------------------------------------- /packages/sql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/sql", 3 | "description": "Parent class for SQL based Keyv storage adapters", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.7", 6 | "main": "src/index.js", 7 | "author": { 8 | "email": "hello@microlink.io", 9 | "name": "microlink.io", 10 | "url": "https://microlink.io" 11 | }, 12 | "repository": { 13 | "directory": "packages/sql", 14 | "type": "git", 15 | "url": "git+https://github.com/microlinkhq/keyv.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/microlinkhq/keyvhq/issues" 19 | }, 20 | "keywords": [ 21 | "adapter", 22 | "cache", 23 | "key", 24 | "keyv", 25 | "sql", 26 | "storage", 27 | "store", 28 | "ttl", 29 | "value" 30 | ], 31 | "dependencies": { 32 | "sql-ts": "7" 33 | }, 34 | "devDependencies": { 35 | "@keyvhq/core": "workspace:*", 36 | "@keyvhq/test-suite": "workspace:*", 37 | "@vscode/sqlite3": "latest", 38 | "ava": "5", 39 | "pify": "5" 40 | }, 41 | "engines": { 42 | "node": ">= 18" 43 | }, 44 | "files": [ 45 | "src" 46 | ], 47 | "scripts": { 48 | "test": "ava" 49 | }, 50 | "license": "MIT", 51 | "publishConfig": { 52 | "access": "public" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/sql/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Sql } = require('sql-ts') 4 | 5 | class KeyvSql { 6 | constructor (options) { 7 | this.options = Object.assign( 8 | { 9 | table: 'keyv', 10 | keySize: 255, 11 | iterationLimit: 10 12 | }, 13 | options 14 | ) 15 | 16 | const sql = new Sql(options.dialect) 17 | 18 | this.entry = sql.define({ 19 | name: this.options.table, 20 | columns: [ 21 | { 22 | name: 'key', 23 | primaryKey: true, 24 | dataType: `VARCHAR(${Number(this.options.keySize)})` 25 | }, 26 | { 27 | name: 'value', 28 | dataType: 'TEXT' 29 | } 30 | ] 31 | }) 32 | const createTable = this.entry.create().ifNotExists().toString() 33 | 34 | const connected = this.options 35 | .connect() 36 | .then(query => query(createTable).then(() => query)) 37 | 38 | this.query = sqlString => connected.then(query => query(sqlString)) 39 | } 40 | 41 | get (key) { 42 | const select = this.entry.select().where({ key }).toString() 43 | return this.query(select).then(rows => { 44 | const row = rows[0] 45 | if (row === undefined) { 46 | return undefined 47 | } 48 | 49 | return row.value 50 | }) 51 | } 52 | 53 | set (key, value) { 54 | if (this.options.dialect === 'mysql') { 55 | value = value.replace(/\\/g, '\\\\') 56 | } 57 | 58 | const upsert = 59 | this.options.dialect === 'postgres' 60 | ? this.entry 61 | .insert({ key, value }) 62 | .onConflict({ columns: ['key'], update: ['value'] }) 63 | .toString() 64 | : this.entry.replace({ key, value }).toString() 65 | return this.query(upsert) 66 | } 67 | 68 | delete (key) { 69 | if (!key) return false 70 | const select = this.entry.select().where({ key }).toString() 71 | const del = this.entry.delete().where({ key }).toString() 72 | return this.query(select).then(rows => { 73 | const row = rows[0] 74 | if (row === undefined) { 75 | return false 76 | } 77 | 78 | return this.query(del).then(() => true) 79 | }) 80 | } 81 | 82 | clear (namespace) { 83 | const del = this.entry 84 | .delete(this.entry.key.like(`${namespace ? namespace + ':' : ''}%`)) 85 | .toString() 86 | return this.query(del).then(() => undefined) 87 | } 88 | 89 | async * iterator (namespace) { 90 | const limit = Number.parseInt(this.options.iterationLimit, 10) 91 | const entry = this.entry 92 | async function * iterate (offset, query) { 93 | const selectChunk = entry 94 | .select() 95 | .where(entry.key.like(`${namespace ? namespace + ':' : ''}%`)) 96 | .limit(limit) 97 | .offset(offset) 98 | .toString() 99 | const entries = await query(selectChunk) 100 | if (entries.length === 0) { 101 | return 102 | } 103 | 104 | for (const entry of entries) { 105 | offset += 1 106 | yield [entry.key, entry.value] 107 | } 108 | 109 | if (offset !== '0') { 110 | yield * iterate(offset, query) 111 | } 112 | } 113 | 114 | yield * iterate(0, this.query) 115 | } 116 | } 117 | 118 | module.exports = KeyvSql 119 | -------------------------------------------------------------------------------- /packages/sql/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const keyvTestSuite = require('@keyvhq/test-suite') 4 | const sqlite3 = require('@vscode/sqlite3') 5 | const Keyv = require('@keyvhq/core') 6 | const pify = require('pify') 7 | const test = require('ava') 8 | 9 | const KeyvSql = require('..') 10 | 11 | class TestSqlite extends KeyvSql { 12 | constructor (options) { 13 | options = Object.assign( 14 | { 15 | dialect: 'sqlite', 16 | db: 'test/testdb.sqlite' 17 | }, 18 | options 19 | ) 20 | 21 | options.connect = () => 22 | new Promise((resolve, reject) => { 23 | const db = new sqlite3.Database(options.db, error => { 24 | if (error) { 25 | reject(error) 26 | } else { 27 | db.configure('busyTimeout', 30000) 28 | resolve(db) 29 | } 30 | }) 31 | }).then(db => pify(db.all).bind(db)) 32 | 33 | super(options) 34 | } 35 | } 36 | 37 | const store = () => new TestSqlite() 38 | 39 | keyvTestSuite(test, Keyv, store) 40 | -------------------------------------------------------------------------------- /packages/sqlite/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.7](https://github.com/microlinkhq/keyv/compare/v2.1.6...v2.1.7) (2025-02-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/sqlite 9 | 10 | ## [2.1.6](https://github.com/microlinkhq/keyv/compare/v2.1.5...v2.1.6) (2024-09-24) 11 | 12 | **Note:** Version bump only for package @keyvhq/sqlite 13 | 14 | ## [2.1.5](https://github.com/microlinkhq/keyv/compare/v2.1.4...v2.1.5) (2024-07-30) 15 | 16 | **Note:** Version bump only for package @keyvhq/sqlite 17 | 18 | ## [2.1.1](https://github.com/microlinkhq/keyv/compare/v2.1.0...v2.1.1) (2024-04-23) 19 | 20 | **Note:** Version bump only for package @keyvhq/sqlite 21 | 22 | # [2.1.0](https://github.com/microlinkhq/keyv/compare/v2.0.3...v2.1.0) (2023-09-29) 23 | 24 | **Note:** Version bump only for package @keyvhq/sqlite 25 | 26 | # [2.0.0](https://github.com/microlinkhq/keyv/compare/v1.6.28...v2.0.0) (2023-04-22) 27 | 28 | ### Features 29 | 30 | * shipped v2 ([#169](https://github.com/microlinkhq/keyv/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyv/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 31 | 32 | ### BREAKING CHANGES 33 | 34 | * The EventEmitter behavior has been completely removed 35 | 36 | ## [1.6.28](https://github.com/microlinkhq/keyvhq/compare/v1.6.27...v1.6.28) (2023-02-27) 37 | 38 | **Note:** Version bump only for package @keyvhq/sqlite 39 | 40 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 41 | 42 | **Note:** Version bump only for package @keyvhq/sqlite 43 | 44 | ## [1.6.20](https://github.com/microlinkhq/keyvhq/compare/v1.6.19...v1.6.20) (2022-07-12) 45 | 46 | **Note:** Version bump only for package @keyvhq/sqlite 47 | 48 | ## [1.6.15](https://github.com/microlinkhq/keyvhq/compare/v1.6.14...v1.6.15) (2022-06-07) 49 | 50 | **Note:** Version bump only for package @keyvhq/sqlite 51 | 52 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 53 | 54 | **Note:** Version bump only for package @keyvhq/sqlite 55 | 56 | ## [1.6.7](https://github.com/microlinkhq/keyvhq/compare/v1.6.6...v1.6.7) (2022-03-13) 57 | 58 | **Note:** Version bump only for package @keyvhq/sqlite 59 | 60 | ## [1.6.6](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.6) (2022-03-09) 61 | 62 | ### Bug Fixes 63 | 64 | * changed all adapter apis to support (uri, option) ([f78a5dd](https://github.com/microlinkhq/keyvhq/commit/f78a5dd26ebcb2661d99c61328fbd92d9c462149)) 65 | 66 | ## [1.6.5](https://github.com/microlinkhq/keyvhq/compare/v1.6.4...v1.6.5) (2022-03-09) 67 | 68 | ### Bug Fixes 69 | 70 | * changed all adapter apis to support (uri, option) ([f78a5dd](https://github.com/microlinkhq/keyvhq/commit/f78a5dd26ebcb2661d99c61328fbd92d9c462149)) 71 | 72 | ## [1.6.3](https://github.com/microlinkhq/keyvhq/compare/v1.6.2...v1.6.3) (2021-12-01) 73 | 74 | **Note:** Version bump only for package @keyvhq/sqlite 75 | 76 | ## [1.6.2](https://github.com/microlinkhq/keyvhq/compare/v1.6.1...v1.6.2) (2021-11-08) 77 | 78 | **Note:** Version bump only for package @keyvhq/sqlite 79 | 80 | # [1.6.0](https://github.com/microlinkhq/keyvhq/compare/v1.5.2...v1.6.0) (2021-10-05) 81 | 82 | **Note:** Version bump only for package @keyvhq/sqlite 83 | 84 | ## [1.5.1](https://github.com/microlinkhq/keyvhq/compare/v1.5.0...v1.5.1) (2021-09-16) 85 | 86 | **Note:** Version bump only for package @keyvhq/sqlite 87 | 88 | # [1.4.0](https://github.com/microlinkhq/keyvhq/compare/v1.3.0...v1.4.0) (2021-09-12) 89 | 90 | **Note:** Version bump only for package @keyvhq/sqlite 91 | 92 | # [1.3.0](https://github.com/microlinkhq/keyvhq/compare/v1.2.7...v1.3.0) (2021-09-01) 93 | 94 | **Note:** Version bump only for package @keyvhq/sqlite 95 | 96 | ## [1.2.7](https://github.com/microlinkhq/keyvhq/compare/v1.2.6...v1.2.7) (2021-08-24) 97 | 98 | **Note:** Version bump only for package @keyvhq/sqlite 99 | 100 | # [1.2.0](https://github.com/microlinkhq/keyvhq/compare/v1.1.1...v1.2.0) (2021-08-11) 101 | 102 | **Note:** Version bump only for package @keyvhq/sqlite 103 | 104 | ## [1.1.1](https://github.com/microlinkhq/keyvhq/compare/v1.1.0...v1.1.1) (2021-07-30) 105 | 106 | **Note:** Version bump only for package @keyvhq/sqlite 107 | 108 | # [1.1.0](https://github.com/microlinkhq/keyvhq/compare/v1.0.2...v1.1.0) (2021-07-30) 109 | 110 | **Note:** Version bump only for package @keyvhq/sqlite 111 | 112 | ## [1.0.2](https://github.com/microlinkhq/keyvhq/compare/v1.0.1...v1.0.2) (2021-07-16) 113 | 114 | **Note:** Version bump only for package @keyvhq/sqlite 115 | 116 | ## [1.0.1](https://github.com/microlinkhq/keyvhq/compare/v1.0.0...v1.0.1) (2021-07-16) 117 | 118 | **Note:** Version bump only for package @keyvhq/sqlite 119 | 120 | ## [0.2.4](https://github.com/microlinkhq/keyvhq/compare/v0.2.0...v0.2.4) (2021-07-01) 121 | 122 | **Note:** Version bump only for package @keyvhq/sqlite 123 | -------------------------------------------------------------------------------- /packages/sqlite/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/sqlite [keyv](https://github.com/microlinkhq/keyv/packages/sqlite) 2 | 3 | SQLite storage adapter for [Keyv](https://github.com/microlinkhq/keyv). 4 | 5 | ## Install 6 | 7 | ```shell 8 | npm install --save @keyvhq/core @keyvhq/sqlite 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | const KeyvSqlite = require('@keyvhq/sqlite') 15 | const Keyv = require('@keyvhq/core') 16 | 17 | const keyv = new Keyv({ 18 | store: new KeyvSqlite('sqlite://path/to/database.sqlite') 19 | }) 20 | ``` 21 | 22 | You can specify the `table` and [`busyTimeout`](https://sqlite.org/c3ref/busy_timeout.html) option: 23 | 24 | ```js 25 | const KeyvSqlite = require('@keyvhq/sqlite') 26 | const Keyv = require('@keyvhq/core') 27 | 28 | const keyv = new Keyv({ 29 | store: new KeyvSqlite('sqlite://path/to/database.sqlite', { 30 | table: 'cache', 31 | busyTimeout: 10000 32 | }) 33 | }) 34 | ``` 35 | 36 | ## License 37 | 38 | **@keyvhq/sqlite** © [Luke Childs](https://lukechilds.co), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
39 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 40 | 41 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 42 | -------------------------------------------------------------------------------- /packages/sqlite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/sqlite", 3 | "description": "SQLite storage adapter for Keyv", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.7", 6 | "types": "./src/index.d.ts", 7 | "main": "src/index.js", 8 | "author": { 9 | "email": "hello@microlink.io", 10 | "name": "microlink.io", 11 | "url": "https://microlink.io" 12 | }, 13 | "repository": { 14 | "directory": "packages/sqlite", 15 | "type": "git", 16 | "url": "git+https://github.com/microlinkhq/keyv.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/microlinkhq/keyvhq/issues" 20 | }, 21 | "keywords": [ 22 | "adapter", 23 | "cache", 24 | "key", 25 | "keyv", 26 | "sql", 27 | "sqlite", 28 | "storage", 29 | "store", 30 | "ttl", 31 | "value" 32 | ], 33 | "dependencies": { 34 | "@keyvhq/sql": "workspace:*", 35 | "@vscode/sqlite3": "5.1.8-vscode", 36 | "pify": "~5.0.0" 37 | }, 38 | "devDependencies": { 39 | "@keyvhq/core": "workspace:*", 40 | "@keyvhq/test-suite": "workspace:*", 41 | "@types/node": "latest", 42 | "ava": "5", 43 | "tsd": "latest", 44 | "typescript": "latest" 45 | }, 46 | "engines": { 47 | "node": ">= 18" 48 | }, 49 | "files": [ 50 | "src" 51 | ], 52 | "scripts": { 53 | "lint": "tsd", 54 | "pretest": "npm run lint", 55 | "test": "ava" 56 | }, 57 | "license": "MIT", 58 | "publishConfig": { 59 | "access": "public" 60 | }, 61 | "tsd": { 62 | "directory": "test" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/sqlite/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '@keyvhq/core' 2 | 3 | declare class KeyvSqlite implements Store { 4 | constructor (uri?: string) 5 | constructor (options?: KeyvSqlite.Options) 6 | constructor (uri: string, options?: KeyvSqlite.Options) 7 | 8 | get (key: string): Promise 9 | has (key: string): Promise 10 | set (key: string, value: TValue): Promise 11 | delete (key: string): Promise 12 | clear (): Promise 13 | iterator (): AsyncGenerator 14 | } 15 | 16 | declare namespace KeyvSqlite { 17 | interface Options { 18 | uri?: string | undefined 19 | busyTimeout?: number | undefined 20 | table?: string | undefined 21 | keySize?: number | undefined 22 | } 23 | } 24 | 25 | export = KeyvSqlite 26 | -------------------------------------------------------------------------------- /packages/sqlite/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const sqlite3 = require('@vscode/sqlite3') 4 | const KeyvSql = require('@keyvhq/sql') 5 | const pify = require('pify') 6 | 7 | class KeyvSqlite extends KeyvSql { 8 | constructor (uri, options) { 9 | uri = uri || {} 10 | if (typeof uri === 'string') { 11 | uri = { uri } 12 | } 13 | 14 | if (uri.uri) { 15 | uri = Object.assign({ uri: uri.uri }, uri) 16 | } 17 | options = Object.assign( 18 | { 19 | dialect: 'sqlite', 20 | uri: 'sqlite://:memory:' 21 | }, 22 | uri, 23 | options 24 | ) 25 | options.db = options.uri.replace(/^sqlite:\/\//, '') 26 | 27 | options.connect = () => 28 | new Promise((resolve, reject) => { 29 | const db = new sqlite3.Database(options.db, error => { 30 | if (error) { 31 | reject(error) 32 | } else { 33 | if (options.busyTimeout) { 34 | db.configure('busyTimeout', options.busyTimeout) 35 | } 36 | 37 | resolve(db) 38 | } 39 | }) 40 | }).then(db => pify(db.all).bind(db)) 41 | 42 | super(options) 43 | } 44 | } 45 | 46 | module.exports = KeyvSqlite 47 | -------------------------------------------------------------------------------- /packages/sqlite/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const keyvTestSuite = require('@keyvhq/test-suite') 4 | const Keyv = require('@keyvhq/core') 5 | const test = require('ava') 6 | 7 | const KeyvSqlite = require('..') 8 | 9 | const store = () => 10 | new KeyvSqlite({ uri: 'sqlite://test/testdb.sqlite', busyTimeout: 30000 }) 11 | keyvTestSuite(test, Keyv, store) 12 | -------------------------------------------------------------------------------- /packages/sqlite/test/index.test-d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-new */ 2 | 3 | import Keyv from '@keyvhq/core' 4 | import KeyvSqlite from '../src' 5 | 6 | new Keyv({ store: new KeyvSqlite({ uri: 'sqlite://path/to/database.sqlite', table: 'cache' }) }) 7 | 8 | new KeyvSqlite({ uri: 'sqlite://path/to/database.sqlite' }) 9 | new KeyvSqlite({ busyTimeout: 10000 }) 10 | new KeyvSqlite({ table: 'cache' }) 11 | new KeyvSqlite({ keySize: 100 }) 12 | 13 | const sqlite = new KeyvSqlite({ uri: 'sqlite://path/to/database.sqlite' }) 14 | new Keyv({ store: sqlite }) 15 | -------------------------------------------------------------------------------- /packages/stats/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.7](https://github.com/microlinkhq/keyvhq/compare/v2.1.6...v2.1.7) (2025-02-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/stats 9 | 10 | ## [2.1.5](https://github.com/microlinkhq/keyvhq/compare/v2.1.4...v2.1.5) (2024-07-30) 11 | 12 | **Note:** Version bump only for package @keyvhq/stats 13 | 14 | ## [2.1.1](https://github.com/microlinkhq/keyvhq/compare/v2.1.0...v2.1.1) (2024-04-23) 15 | 16 | **Note:** Version bump only for package @keyvhq/stats 17 | 18 | # [2.1.0](https://github.com/microlinkhq/keyvhq/compare/v2.0.3...v2.1.0) (2023-09-29) 19 | 20 | **Note:** Version bump only for package @keyvhq/stats 21 | 22 | ## [2.0.3](https://github.com/microlinkhq/keyvhq/compare/v2.0.2...v2.0.3) (2023-06-20) 23 | 24 | **Note:** Version bump only for package @keyvhq/stats 25 | 26 | # [2.0.0](https://github.com/microlinkhq/keyvhq/compare/v1.6.28...v2.0.0) (2023-04-22) 27 | 28 | ### Features 29 | 30 | * shipped v2 ([#169](https://github.com/microlinkhq/keyvhq/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyvhq/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 31 | 32 | ### BREAKING CHANGES 33 | 34 | * The EventEmitter behavior has been completely removed 35 | -------------------------------------------------------------------------------- /packages/stats/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/stats [keyv](https://github.com/microlinkhq/keyv/packages/stats) 2 | 3 | > Collects metrics for a Keyv instance over time. 4 | 5 | ## Install 6 | 7 | ```shell 8 | npm install --save @keyvhq/stats 9 | ``` 10 | 11 | ## Usage 12 | 13 | Wrap your keyv instance with `@keyvhq/stats`: 14 | 15 | ```js 16 | const KeyvStats = require('@keyvhq/stats') 17 | const KeyvRedis = require('@keyvhq/redis') 18 | const Keyv = require('@keyvhq/core') 19 | 20 | const keyv = KeyvStats(new Keyv({ store: new KeyvRedis() })) 21 | ``` 22 | 23 | That adds some methods for getting stats over your keyv instance: 24 | 25 | ```js 26 | await keyv.get('foo') 27 | 28 | keyv.stats.info() 29 | // => { 30 | // hit: { value: 0, percent: '0%' }, 31 | // miss: { value: 1, percent: '100%' }, 32 | // total: 1 33 | // } 34 | 35 | await keyv.set('foo', 'bar') 36 | await keyv.get('foo') 37 | await keyv.get('foo') 38 | 39 | keyv.stats.info() 40 | // => { 41 | // hit: { value: 2, percent: '67%' }, 42 | // miss: { value: 1, percent: '33%' }, 43 | // total: 3 44 | // } 45 | 46 | keyv.stats.reset() 47 | ``` 48 | 49 | ## API 50 | 51 | ### stats(\[options]) 52 | 53 | #### options 54 | 55 | ##### initialData 56 | 57 | Type: `object`
58 | Default: `{ hit: 0, miss: 0}` 59 | 60 | The data to set for first time or when `.stats.reset()` is called. 61 | 62 | ##### interval 63 | 64 | Type: `number`
65 | Default: `1500` 66 | 67 | ##### key 68 | 69 | Type: `string`
70 | Default: `'__keyv_stats__'` 71 | 72 | The key used for storing the stats inside the store. 73 | 74 | ### .stats.info() 75 | 76 | Returns the collected stats for the store in the shape: 77 | 78 | ``` 79 | { 80 | hit: { 81 | value: number 82 | percent: string 83 | }, 84 | miss: { 85 | value: number 86 | percent: string 87 | } 88 | total: number 89 | } 90 | ``` 91 | 92 | ### .stats.reset() 93 | 94 | Resets the stats counters. 95 | 96 | ### .stats.save() 97 | 98 | Peforms a save of the current buffered stats into the store. 99 | 100 | ## License 101 | 102 | **@keyvhq/stats** © [Jytesh](https://github.com/Jytesh), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
103 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 104 | 105 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 106 | -------------------------------------------------------------------------------- /packages/stats/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/stats", 3 | "description": "Collects metrics for a Keyv instance over time.", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.7", 6 | "types": "./src/index.d.ts", 7 | "main": "src/index.js", 8 | "author": { 9 | "email": "hello@microlink.io", 10 | "name": "microlink.io", 11 | "url": "https://microlink.io" 12 | }, 13 | "repository": { 14 | "directory": "packages/stats", 15 | "type": "git", 16 | "url": "git+https://github.com/microlinkhq/keyvhq.git" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/microlinkhq/keyvhq/issues" 20 | }, 21 | "keywords": [ 22 | "cache", 23 | "info", 24 | "keyv", 25 | "stats" 26 | ], 27 | "devDependencies": { 28 | "@keyvhq/core": "workspace:*", 29 | "@keyvhq/file": "workspace:*", 30 | "ava": "5", 31 | "tsd": "latest", 32 | "typescript": "latest" 33 | }, 34 | "engines": { 35 | "node": ">= 18" 36 | }, 37 | "files": [ 38 | "src" 39 | ], 40 | "scripts": { 41 | "lint": "tsd", 42 | "pretest": "npm run lint", 43 | "test": "ava" 44 | }, 45 | "license": "MIT", 46 | "publishConfig": { 47 | "access": "public" 48 | }, 49 | "tsd": { 50 | "directory": "test" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/stats/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Store } from '@keyvhq/core' 2 | 3 | declare class KeyvStats implements Store { 4 | constructor (keyv: Map, options: KeyvStats.Options) 5 | 6 | get (key: string): Promise 7 | has (key: string): Promise 8 | set (key: string, value: TValue): Promise 9 | delete (key: string): Promise 10 | clear (options?: KeyvStats.ClearOptions): Promise 11 | iterator (): AsyncGenerator 12 | stats: { 13 | reset: Promise 14 | info: Promise 15 | save: Promise 16 | } 17 | } 18 | 19 | declare namespace KeyvStats { 20 | interface Options { 21 | interval?: number 22 | } 23 | 24 | interface Stats { 25 | hit: { 26 | value: number 27 | percent: string 28 | } 29 | miss: { 30 | value: number 31 | percent: string 32 | } 33 | total: number 34 | } 35 | } 36 | 37 | export = KeyvStats 38 | -------------------------------------------------------------------------------- /packages/stats/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const calcPercent = (partial, total) => 4 | `${total === 0 ? 0 : Math.round((partial / total) * 100)}%` 5 | 6 | function KeyvStats ( 7 | keyv, 8 | { 9 | interval = 15000, 10 | key = '__keyv_stats__', 11 | initialData = { hit: 0, miss: 0 } 12 | } = {} 13 | ) { 14 | if (!(this instanceof KeyvStats)) return new KeyvStats(keyv, { interval }) 15 | 16 | const get = keyv.get.bind(keyv) 17 | 18 | const buffer = get(key).then(stats => 19 | stats === undefined ? initialData : stats 20 | ) 21 | 22 | const save = async () => keyv.set(key, await buffer) 23 | 24 | if (interval > 0) setInterval(save).unref() 25 | 26 | keyv.get = async (...args) => { 27 | const result = await get(...args) 28 | ++(await buffer)[result === undefined ? 'miss' : 'hit'] 29 | return result 30 | } 31 | 32 | keyv.stats = {} 33 | 34 | keyv.stats.reset = () => keyv.set(key, initialData) 35 | 36 | keyv.stats.info = async () => { 37 | const { hit, miss } = await buffer 38 | const total = hit + miss 39 | return { 40 | hit: { 41 | value: hit, 42 | percent: calcPercent(hit, total) 43 | }, 44 | miss: { 45 | value: miss, 46 | percent: calcPercent(miss, total) 47 | }, 48 | total 49 | } 50 | } 51 | 52 | keyv.stats.save = save 53 | 54 | return keyv 55 | } 56 | 57 | module.exports = KeyvStats 58 | -------------------------------------------------------------------------------- /packages/stats/test/fixture.json: -------------------------------------------------------------------------------- 1 | {"__keyv_stats__":"{\"value\":{\"hit\":3,\"miss\":1},\"expires\":null}","foo":"{\"value\":\"bar\",\"expires\":null}"} -------------------------------------------------------------------------------- /packages/stats/test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { setTimeout } = require('timers/promises') 4 | const KeyvFile = require('@keyvhq/file') 5 | const Keyv = require('@keyvhq/core') 6 | const path = require('path') 7 | const test = require('ava') 8 | 9 | const KeyvStats = require('..') 10 | 11 | const fixture = path.join(__dirname, 'fixture.json') 12 | 13 | test.beforeEach(() => { 14 | const keyv = new Keyv({ store: new KeyvFile(fixture) }) 15 | keyv.clear() 16 | }) 17 | 18 | test('namespace support', async t => { 19 | const store = new Map() 20 | 21 | const keyvOne = new KeyvStats( 22 | new Keyv({ 23 | store, 24 | namespace: 'one' 25 | }), 26 | { interval: 0 } 27 | ) 28 | 29 | const keyvTwo = new KeyvStats( 30 | new Keyv({ 31 | store, 32 | namespace: 'two' 33 | }), 34 | { interval: 0 } 35 | ) 36 | 37 | t.deepEqual(await keyvOne.stats.info(), { 38 | hit: { value: 0, percent: '0%' }, 39 | miss: { value: 0, percent: '0%' }, 40 | total: 0 41 | }) 42 | 43 | t.deepEqual(await keyvTwo.stats.info(), { 44 | hit: { value: 0, percent: '0%' }, 45 | miss: { value: 0, percent: '0%' }, 46 | total: 0 47 | }) 48 | 49 | await keyvOne.set('foo', 'bar') 50 | await keyvOne.get('foo') 51 | await keyvOne.stats.save() 52 | await keyvTwo.stats.save() 53 | 54 | t.deepEqual(await keyvOne.stats.info(), { 55 | hit: { value: 1, percent: '100%' }, 56 | miss: { value: 0, percent: '0%' }, 57 | total: 1 58 | }) 59 | 60 | t.deepEqual(await keyvTwo.stats.info(), { 61 | hit: { value: 0, percent: '0%' }, 62 | miss: { value: 0, percent: '0%' }, 63 | total: 0 64 | }) 65 | }) 66 | 67 | test('get hit ratio', async t => { 68 | const keyv = new KeyvStats( 69 | new Keyv({ 70 | store: new KeyvFile(fixture) 71 | }), 72 | { interval: 25 } 73 | ) 74 | 75 | await keyv.stats.reset() 76 | 77 | t.deepEqual(await keyv.stats.info(), { 78 | hit: { value: 0, percent: '0%' }, 79 | miss: { value: 0, percent: '0%' }, 80 | total: 0 81 | }) 82 | 83 | await keyv.get('foo') 84 | await setTimeout(50) 85 | 86 | t.deepEqual(await keyv.stats.info(), { 87 | hit: { value: 0, percent: '0%' }, 88 | miss: { value: 1, percent: '100%' }, 89 | total: 1 90 | }) 91 | 92 | await keyv.set('foo', 'bar') 93 | await keyv.get('foo') 94 | await setTimeout(50) 95 | 96 | t.deepEqual(await keyv.stats.info(), { 97 | hit: { value: 1, percent: '50%' }, 98 | miss: { value: 1, percent: '50%' }, 99 | total: 2 100 | }) 101 | 102 | await keyv.get('foo') 103 | await setTimeout(50) 104 | 105 | t.deepEqual(await keyv.stats.info(), { 106 | hit: { value: 2, percent: '67%' }, 107 | miss: { value: 1, percent: '33%' }, 108 | total: 3 109 | }) 110 | 111 | await keyv.get('foo') 112 | await setTimeout(50) 113 | 114 | t.deepEqual(await keyv.stats.info(), { 115 | hit: { value: 3, percent: '75%' }, 116 | miss: { value: 1, percent: '25%' }, 117 | total: 4 118 | }) 119 | }) 120 | -------------------------------------------------------------------------------- /packages/test-suite/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [2.1.7](https://github.com/microlinkhq/keyv/compare/v2.1.6...v2.1.7) (2025-02-20) 7 | 8 | **Note:** Version bump only for package @keyvhq/test-suite 9 | 10 | ## [2.1.5](https://github.com/microlinkhq/keyv/compare/v2.1.4...v2.1.5) (2024-07-30) 11 | 12 | **Note:** Version bump only for package @keyvhq/test-suite 13 | 14 | ## [2.0.3](https://github.com/microlinkhq/keyv/compare/v2.0.2...v2.0.3) (2023-06-20) 15 | 16 | **Note:** Version bump only for package @keyvhq/test-suite 17 | 18 | # [2.0.0](https://github.com/microlinkhq/keyv/compare/v1.6.28...v2.0.0) (2023-04-22) 19 | 20 | ### Features 21 | 22 | * shipped v2 ([#169](https://github.com/microlinkhq/keyv/issues/169)) ([cc78e75](https://github.com/microlinkhq/keyv/commit/cc78e75b281111c7e57e30d7554b9772c83f2baa)) 23 | 24 | ### BREAKING CHANGES 25 | 26 | * The EventEmitter behavior has been completely removed 27 | 28 | ## [1.6.26](https://github.com/microlinkhq/keyvhq/compare/v1.6.25...v1.6.26) (2023-01-29) 29 | 30 | **Note:** Version bump only for package @keyvhq/test-suite 31 | 32 | ## [1.6.13](https://github.com/microlinkhq/keyvhq/compare/v1.6.12...v1.6.13) (2022-05-22) 33 | 34 | **Note:** Version bump only for package @keyvhq/test-suite 35 | 36 | # [1.6.0](https://github.com/microlinkhq/keyvhq/compare/v1.5.2...v1.6.0) (2021-10-05) 37 | 38 | ### Bug Fixes 39 | 40 | * **test:** increased timeout for iterator resolving ([1a51f3e](https://github.com/microlinkhq/keyvhq/commit/1a51f3e90a1112f0cdb83c2c23f959517e8b0f82)) 41 | 42 | # [1.4.0](https://github.com/microlinkhq/keyvhq/compare/v1.3.0...v1.4.0) (2021-09-12) 43 | 44 | ### Bug Fixes 45 | 46 | * no namespaced iteration of maps ([77b9724](https://github.com/microlinkhq/keyvhq/commit/77b9724fe4169331ed09539236729237d7c7cefa)) 47 | * **test:** t.fail instead of t.throw ([5dfd9bb](https://github.com/microlinkhq/keyvhq/commit/5dfd9bbdf2b21473e9f5d9a89ac552cce5e26a69)) 48 | 49 | ### Features 50 | 51 | * use empty namespace by default ([a2872d3](https://github.com/microlinkhq/keyvhq/commit/a2872d3ec5ee3cb6445fc97bd129505d43e90c0e)) 52 | 53 | # [1.3.0](https://github.com/microlinkhq/keyvhq/compare/v1.2.7...v1.3.0) (2021-09-01) 54 | 55 | **Note:** Version bump only for package @keyvhq/test-suite 56 | 57 | # [1.2.0](https://github.com/microlinkhq/keyvhq/compare/v1.1.1...v1.2.0) (2021-08-11) 58 | 59 | **Note:** Version bump only for package @keyvhq/test-suite 60 | 61 | ## [1.1.1](https://github.com/microlinkhq/keyvhq/compare/v1.1.0...v1.1.1) (2021-07-30) 62 | 63 | **Note:** Version bump only for package @keyvhq/test-suite 64 | 65 | # [1.1.0](https://github.com/microlinkhq/keyvhq/compare/v1.0.2...v1.1.0) (2021-07-30) 66 | 67 | **Note:** Version bump only for package @keyvhq/test-suite 68 | 69 | ## [1.0.2](https://github.com/microlinkhq/keyvhq/compare/v1.0.1...v1.0.2) (2021-07-16) 70 | 71 | **Note:** Version bump only for package @keyvhq/test-suite 72 | 73 | ## [1.0.1](https://github.com/microlinkhq/keyvhq/compare/v1.0.0...v1.0.1) (2021-07-16) 74 | 75 | **Note:** Version bump only for package @keyvhq/test-suite 76 | 77 | ## [0.2.4](https://github.com/microlinkhq/keyvhq/compare/v0.2.0...v0.2.4) (2021-07-01) 78 | 79 | ### Bug Fixes 80 | 81 | * typo keyvNamepsaceTests -> keyvNamepsaceTests ([d5b5577](https://github.com/microlinkhq/keyvhq/commit/d5b5577bf0bb151e0717a833ac8fae6cb5d1165f)) 82 | -------------------------------------------------------------------------------- /packages/test-suite/README.md: -------------------------------------------------------------------------------- 1 | # @keyvhq/test-suite [keyv](https://github.com/microlinkhq/keyv/packages/test-suite) 2 | 3 | Complete [AVA](https://github.com/avajs/ava) test suite to test a [Keyv](https://github.com/microlinkhq/keyv) storage adapter for API compliance. 4 | 5 | ## Usage 6 | 7 | ### Install 8 | 9 | Install AVA, Keyv and `@keyvhq/test-suite` as development dependencies. 10 | 11 | ```shell 12 | npm install --save-dev ava keyv @keyvhq/test-suite 13 | ``` 14 | 15 | Then update `keyv` and `@keyvhq/test-suite` versions to `*` in `package.json` to ensure you're always testing against the latest version. 16 | 17 | ### Create Test File 18 | 19 | `test.js` 20 | 21 | ```js 22 | import test from 'ava' 23 | import keyvTestSuite from '@keyvhq/test-suite' 24 | import Keyv from '@keyvhq/core' 25 | import KeyvStore from './' 26 | 27 | const store = () => new KeyvStore() 28 | keyvTestSuite(test, Keyv, store) 29 | ``` 30 | 31 | Where `KeyvStore` is your storage adapter. 32 | 33 | Set your test script in `package.json` to `ava`. 34 | ```json 35 | "scripts": { 36 | "test": "ava" 37 | } 38 | ``` 39 | 40 | ### Test on Active Node.js LTS and Higher 41 | 42 | An example configuration for Travis CI would look like this: 43 | 44 | `.travis.yml` 45 | 46 | ```yaml 47 | language: node_js 48 | node_js: 49 | - '8' 50 | - '6' 51 | - '4' 52 | script: npm test 53 | ``` 54 | 55 | ## Example 56 | 57 | Take a look at [redis](https://github.com/microlinkhq/redis) for an example of an existing storage adapter using `@keyvhq/test-suite`. 58 | 59 | ## License 60 | 61 | **@keyvhq/test-suite** © [Luke Childs](https://lukechilds.co), released under the [MIT](https://github.com/microlinkhq/keyvhq/blob/master/LICENSE.md) License.
62 | Maintained by [Microlink](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/keyvhq/contributors). 63 | 64 | > [microlink.io](https://microlink.io) · GitHub [microlinkhq](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq) 65 | -------------------------------------------------------------------------------- /packages/test-suite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@keyvhq/test-suite", 3 | "description": "Test suite for Keyv API compliancy", 4 | "homepage": "https://keyv.js.org", 5 | "version": "2.1.7", 6 | "main": "src/index.js", 7 | "author": { 8 | "email": "hello@microlink.io", 9 | "name": "microlink.io", 10 | "url": "https://microlink.io" 11 | }, 12 | "repository": { 13 | "directory": "packages/keyv-test-suite", 14 | "type": "git", 15 | "url": "git+https://github.com/microlinkhq/keyv.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/microlinkhq/keyvhq/issues" 19 | }, 20 | "keywords": [ 21 | "cache", 22 | "key", 23 | "keyv", 24 | "store", 25 | "suite", 26 | "test", 27 | "tests", 28 | "ttl", 29 | "value" 30 | ], 31 | "engines": { 32 | "node": ">= 18" 33 | }, 34 | "files": [ 35 | "src" 36 | ], 37 | "scripts": { 38 | "test": "exit 0" 39 | }, 40 | "license": "MIT", 41 | "publishConfig": { 42 | "access": "public" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/test-suite/src/api.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { setTimeout } = require('timers/promises') 4 | 5 | const keyvApiTests = (test, Keyv, store) => { 6 | test.beforeEach(async () => { 7 | const keyv = new Keyv({ store: store() }) 8 | await keyv.clear() 9 | }) 10 | 11 | test.serial('.set(key, value) returns a Promise', t => { 12 | const keyv = new Keyv({ store: store() }) 13 | t.true(keyv.set('foo', 'bar') instanceof Promise) 14 | }) 15 | 16 | test.serial('.set(key, value) resolves to true', async t => { 17 | const keyv = new Keyv({ store: store() }) 18 | t.is(await keyv.set('foo', 'bar'), true) 19 | }) 20 | 21 | test.serial('.set(key, value) sets a value', async t => { 22 | const keyv = new Keyv({ store: store() }) 23 | await keyv.set('foo', 'bar') 24 | t.is(await keyv.get('foo'), 'bar') 25 | }) 26 | 27 | test.serial('.set(key, value, ttl) sets a value that expires', async t => { 28 | const ttl = 1000 29 | const keyv = new Keyv({ store: store() }) 30 | await keyv.set('foo', 'bar', ttl) 31 | t.is(await keyv.get('foo'), 'bar') 32 | await setTimeout(ttl + 1) 33 | t.is(await keyv.get('foo'), undefined) 34 | }) 35 | 36 | test.serial('.get(key) returns a Promise', t => { 37 | const keyv = new Keyv({ store: store() }) 38 | t.true(keyv.get('foo') instanceof Promise) 39 | }) 40 | 41 | test.serial('.get(key) resolves to value', async t => { 42 | const keyv = new Keyv({ store: store() }) 43 | await keyv.set('foo', 'bar') 44 | t.is(await keyv.get('foo'), 'bar') 45 | }) 46 | 47 | test.serial( 48 | '.get(key) with nonexistent key resolves to undefined', 49 | async t => { 50 | const keyv = new Keyv({ store: store() }) 51 | t.is(await keyv.get('foo'), undefined) 52 | } 53 | ) 54 | 55 | test.serial('.delete(key) returns a Promise', t => { 56 | const keyv = new Keyv({ store: store() }) 57 | t.true(keyv.delete('foo') instanceof Promise) 58 | }) 59 | 60 | test.serial('.delete(key) resolves to true', async t => { 61 | const keyv = new Keyv({ store: store() }) 62 | await keyv.set('foo', 'bar') 63 | t.is(await keyv.delete('foo'), true) 64 | }) 65 | 66 | test.serial( 67 | '.delete(key) with nonexistent key resolves to false', 68 | async t => { 69 | const keyv = new Keyv({ store: store() }) 70 | t.is(await keyv.delete(), false) 71 | t.is(await keyv.delete('foo'), false) 72 | } 73 | ) 74 | 75 | test.serial('.delete(key) deletes a key', async t => { 76 | const keyv = new Keyv({ store: store() }) 77 | await keyv.set('foo', 'bar') 78 | t.is(await keyv.delete('foo'), true) 79 | t.is(await keyv.get('foo'), undefined) 80 | }) 81 | 82 | test.serial('.clear() returns a Promise', async t => { 83 | const keyv = new Keyv({ store: store() }) 84 | const returnValue = keyv.clear() 85 | t.true(returnValue instanceof Promise) 86 | await returnValue 87 | }) 88 | 89 | test.serial('.clear() resolves to undefined', async t => { 90 | const keyv = new Keyv({ store: store() }) 91 | t.is(await keyv.clear(), undefined) 92 | await keyv.set('foo', 'bar') 93 | t.is(await keyv.clear(), undefined) 94 | }) 95 | 96 | test.serial('.clear() deletes all key/value pairs', async t => { 97 | const keyv = new Keyv({ store: store() }) 98 | await keyv.set('foo', 'bar') 99 | await keyv.set('fizz', 'buzz') 100 | await keyv.clear() 101 | t.is(await keyv.get('foo'), undefined) 102 | t.is(await keyv.get('fizz'), undefined) 103 | }) 104 | 105 | test.serial('.has(key) returns a Promise', t => { 106 | const keyv = new Keyv({ store: store() }) 107 | t.true(keyv.has('foo') instanceof Promise) 108 | }) 109 | 110 | test.serial('.has(key) resolves to true', async t => { 111 | const keyv = new Keyv({ store: store() }) 112 | await keyv.set('foo', 'bar') 113 | t.is(await keyv.has('foo'), true) 114 | }) 115 | 116 | test.serial('.has(key) with nonexistent key resolves to false', async t => { 117 | const keyv = new Keyv({ store: store() }) 118 | t.is(await keyv.has('foo'), false) 119 | }) 120 | 121 | test.after.always(async () => { 122 | const keyv = new Keyv({ store: store() }) 123 | await keyv.clear() 124 | }) 125 | } 126 | 127 | module.exports = keyvApiTests 128 | -------------------------------------------------------------------------------- /packages/test-suite/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const keyvNamespaceTests = require('./namespace') 4 | const keyvIteratorTests = require('./iteration') 5 | const keyvValueTests = require('./values') 6 | const keyvApiTests = require('./api') 7 | 8 | const keyvTestSuite = (test, Keyv, store) => { 9 | keyvIteratorTests(test, Keyv, store) 10 | keyvApiTests(test, Keyv, store) 11 | keyvValueTests(test, Keyv, store) 12 | keyvNamespaceTests(test, Keyv, store) 13 | } 14 | 15 | Object.assign(keyvTestSuite, { 16 | keyvApiTests, 17 | keyvValueTests, 18 | keyvNamespaceTests, 19 | keyvIteratorTests 20 | }) 21 | 22 | module.exports = keyvTestSuite 23 | -------------------------------------------------------------------------------- /packages/test-suite/src/iteration.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { setTimeout } = require('timers/promises') 4 | 5 | const keyvIteratorTests = (test, Keyv, store) => { 6 | test.beforeEach(async () => { 7 | const keyv = new Keyv({ store: store() }) 8 | await keyv.clear() 9 | }) 10 | 11 | test.serial('.iterator() returns an asyncIterator', t => { 12 | const keyv = new Keyv({ store: store() }) 13 | t.true(typeof keyv.iterator()[Symbol.asyncIterator] === 'function') 14 | }) 15 | 16 | test.serial('iterator() iterates over all values', async t => { 17 | const keyv = new Keyv({ store: store() }) 18 | const map = new Map( 19 | Array.from({ length: 5 }) 20 | .fill(0) 21 | .map((x, i) => [String(i), String(i + 10)]) 22 | ) 23 | const toResolve = [] 24 | for (const [key, value] of map) { 25 | toResolve.push(keyv.set(key, value)) 26 | } 27 | 28 | await Promise.all(toResolve) 29 | t.plan(map.size) 30 | for await (const [key, value] of keyv.iterator()) { 31 | const doesKeyExist = map.has(key) 32 | const isValueSame = map.get(key) === value 33 | t.true(doesKeyExist && isValueSame) 34 | } 35 | }) 36 | 37 | test.serial( 38 | "iterator() doesn't yield values from other namespaces", 39 | async t => { 40 | const KeyvStore = store() 41 | 42 | const keyv1 = new Keyv({ store: KeyvStore, namespace: 'keyv1' }) 43 | const map1 = new Map( 44 | Array.from({ length: 5 }) 45 | .fill(0) 46 | .map((x, i) => [String(i), String(i + 10)]) 47 | ) 48 | const toResolve = [] 49 | for (const [key, value] of map1) { 50 | toResolve.push(keyv1.set(key, value)) 51 | } 52 | 53 | await Promise.all(toResolve) 54 | 55 | const keyv2 = new Keyv({ store: KeyvStore, namespace: 'keyv2' }) 56 | const map2 = new Map( 57 | Array.from({ length: 5 }) 58 | .fill(0) 59 | .map((x, i) => [String(i), String(i + 11)]) 60 | ) 61 | toResolve.length = 0 62 | for (const [key, value] of map2) { 63 | toResolve.push(keyv2.set(key, value)) 64 | } 65 | 66 | await Promise.all(toResolve) 67 | 68 | t.plan(map2.size) 69 | for await (const [key, value] of keyv2.iterator()) { 70 | const doesKeyExist = map2.has(key) 71 | const isValueSame = map2.get(key) === value 72 | t.true(doesKeyExist && isValueSame) 73 | } 74 | } 75 | ) 76 | 77 | test.serial( 78 | "iterator() doesn't yield expired values, and deletes them", 79 | async t => { 80 | const keyv = new Keyv({ store: store() }) 81 | const map = new Map( 82 | Array.from({ length: 5 }) 83 | .fill(0) 84 | .map((x, i) => [String(i), String(i + 10)]) 85 | ) 86 | const toResolve = [] 87 | for (const [key, value] of map) { 88 | toResolve.push(keyv.set(key, value, 200)) 89 | } 90 | 91 | await Promise.all(toResolve) 92 | await setTimeout(250) 93 | for await (const entry of keyv.iterator()) { 94 | t.fail('Found an expired value' + entry) 95 | } 96 | t.pass() 97 | } 98 | ) 99 | } 100 | 101 | module.exports = keyvIteratorTests 102 | -------------------------------------------------------------------------------- /packages/test-suite/src/namespace.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const keyvNamepsaceTests = (test, Keyv, store) => { 4 | test.beforeEach(async () => { 5 | const keyv1 = new Keyv({ store: store(), namespace: 'keyv1' }) 6 | const keyv2 = new Keyv({ store: store(), namespace: 'keyv2' }) 7 | await keyv1.clear() 8 | await keyv2.clear() 9 | }) 10 | 11 | test.serial("namespaced set/get don't collide", async t => { 12 | const keyv1 = new Keyv({ store: store(), namespace: 'keyv1' }) 13 | const keyv2 = new Keyv({ store: store(), namespace: 'keyv2' }) 14 | await keyv1.set('foo', 'keyv1') 15 | await keyv2.set('foo', 'keyv2') 16 | t.is(await keyv1.get('foo'), 'keyv1') 17 | t.is(await keyv2.get('foo'), 'keyv2') 18 | }) 19 | 20 | test.serial( 21 | 'namespaced delete only deletes from current namespace', 22 | async t => { 23 | const keyv1 = new Keyv({ store: store(), namespace: 'keyv1' }) 24 | const keyv2 = new Keyv({ store: store(), namespace: 'keyv2' }) 25 | await keyv1.set('foo', 'keyv1') 26 | await keyv2.set('foo', 'keyv2') 27 | t.is(await keyv1.delete('foo'), true) 28 | t.is(await keyv1.get('foo'), undefined) 29 | t.is(await keyv2.get('foo'), 'keyv2') 30 | } 31 | ) 32 | 33 | test.serial('namespaced clear only clears current namespace', async t => { 34 | const keyv1 = new Keyv({ store: store(), namespace: 'keyv1' }) 35 | const keyv2 = new Keyv({ store: store(), namespace: 'keyv2' }) 36 | await keyv1.set('foo', 'keyv1') 37 | await keyv1.set('bar', 'keyv1') 38 | await keyv2.set('foo', 'keyv2') 39 | await keyv2.set('bar', 'keyv2') 40 | await keyv1.clear() 41 | t.is(await keyv1.get('foo'), undefined) 42 | t.is(await keyv1.get('bar'), undefined) 43 | t.is(await keyv2.get('foo'), 'keyv2') 44 | t.is(await keyv2.get('bar'), 'keyv2') 45 | }) 46 | 47 | test.after.always(async () => { 48 | const keyv1 = new Keyv({ store: store(), namespace: 'keyv1' }) 49 | const keyv2 = new Keyv({ store: store(), namespace: 'keyv2' }) 50 | await keyv1.clear() 51 | await keyv2.clear() 52 | }) 53 | } 54 | 55 | module.exports = keyvNamepsaceTests 56 | -------------------------------------------------------------------------------- /packages/test-suite/src/values.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const keyvValueTests = (test, Keyv, store) => { 4 | test.beforeEach(async () => { 5 | const keyv = new Keyv({ store: store() }) 6 | await keyv.clear() 7 | }) 8 | 9 | test.serial('value can be false', async t => { 10 | const keyv = new Keyv({ store: store() }) 11 | await keyv.set('foo', false) 12 | t.is(await keyv.get('foo'), false) 13 | }) 14 | 15 | test.serial('value can be null', async t => { 16 | const keyv = new Keyv({ store: store() }) 17 | await keyv.set('foo', null) 18 | t.is(await keyv.get('foo'), null) 19 | }) 20 | 21 | test.serial('value can be undefined', async t => { 22 | const keyv = new Keyv({ store: store() }) 23 | await keyv.set('foo', undefined) 24 | t.is(await keyv.get('foo'), undefined) 25 | }) 26 | 27 | test.serial('value can be a number', async t => { 28 | const keyv = new Keyv({ store: store() }) 29 | await keyv.set('foo', 0) 30 | t.is(await keyv.get('foo'), 0) 31 | }) 32 | 33 | test.serial('value can be an object', async t => { 34 | const keyv = new Keyv({ store: store() }) 35 | const value = { fizz: 'buzz' } 36 | await keyv.set('foo', value) 37 | t.deepEqual(await keyv.get('foo'), value) 38 | }) 39 | 40 | test.serial('value can be a buffer', async t => { 41 | const keyv = new Keyv({ store: store() }) 42 | const buf = Buffer.from('bar') 43 | await keyv.set('foo', buf) 44 | t.true(buf.equals(await keyv.get('foo'))) 45 | }) 46 | 47 | test.serial('value can be an object containing a buffer', async t => { 48 | const keyv = new Keyv({ store: store() }) 49 | const value = { buff: Buffer.from('buzz') } 50 | await keyv.set('foo', value) 51 | t.deepEqual(await keyv.get('foo'), value) 52 | }) 53 | 54 | test.serial('value can contain quotes', async t => { 55 | const keyv = new Keyv({ store: store() }) 56 | const value = '"' 57 | await keyv.set('foo', value) 58 | t.deepEqual(await keyv.get('foo'), value) 59 | }) 60 | 61 | test.after.always(async () => { 62 | const keyv = new Keyv({ store: store() }) 63 | await keyv.clear() 64 | }) 65 | } 66 | 67 | module.exports = keyvValueTests 68 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /static/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microlinkhq/keyvhq/9246bdb25a3b69bc79c525042a58336a8325a9c5/static/banner.jpg -------------------------------------------------------------------------------- /static/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microlinkhq/keyvhq/9246bdb25a3b69bc79c525042a58336a8325a9c5/static/banner.png -------------------------------------------------------------------------------- /static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microlinkhq/keyvhq/9246bdb25a3b69bc79c525042a58336a8325a9c5/static/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microlinkhq/keyvhq/9246bdb25a3b69bc79c525042a58336a8325a9c5/static/favicon-32x32.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microlinkhq/keyvhq/9246bdb25a3b69bc79c525042a58336a8325a9c5/static/favicon.ico -------------------------------------------------------------------------------- /static/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microlinkhq/keyvhq/9246bdb25a3b69bc79c525042a58336a8325a9c5/static/logo.jpg -------------------------------------------------------------------------------- /static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microlinkhq/keyvhq/9246bdb25a3b69bc79c525042a58336a8325a9c5/static/logo.png -------------------------------------------------------------------------------- /static/main.min.js: -------------------------------------------------------------------------------- 1 | window.$docsify={repo:"microlinkhq/keyvhq",maxLevel:3,auto2top:!0,externalLinkRel:"noopener noreferrer",plugins:[function(e){e.ready(function(){codecopy("pre")})}]}; -------------------------------------------------------------------------------- /static/style.min.css: -------------------------------------------------------------------------------- 1 | :root{--base:#f5f4f4;--black:rgba(18,16,12,.702);--gray0:#f9f9f9;--gray1:#ededee;--gray2:#e0e1e1;--gray3:#d2d3d4;--gray4:#c3c4c5;--gray5:#b2b3b5;--gray6:#9fa0a2;--gray7:#88898c;--gray8:#6b6c70;--gray9:#3d3f44;--gray10:#000;--orange:#ff5300;--pink:#bc25aa;--red:#ff2159;--primary-color:var(--gray10);--secondary-color:var(--orange);--bg-color:var(--base);--selection-color:#f9e4ac;--serif-font:"IBM Plex Serif",sans-serif;--sans-serif-font:"IBM Plex Sans",sans-serif;--code-font:"IBM Plex Mono",monospace;--border-color:var(--gray3)}::selection{background:var(--selection-color)}::-moz-selection{background:var(--selection-color)}*{-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}body,html{height:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:var(--sans-serif-font);font-size:16px;letter-spacing:0;margin:0;overflow-x:hidden}img{max-width:100%}@keyframes a{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}.github-corner svg{border-bottom:0;color:#fff;position:fixed;right:0;text-decoration:none;top:0;z-index:1;fill:var(--secondary-color);height:80px;width:80px}.github-corner:hover svg .octo-arm{animation:a .56s ease-in-out;opacity:1}.github-corner:focus-visible svg .octo-arm{animation:a .56s ease-in-out;opacity:1}main{display:block;height:100%;position:relative;width:100vw;z-index:0}.sidebar{font-family:var(--sans-serif-font);padding:40px;transition:transform .25s ease-out;width:300px}.sidebar .sidebar-nav{line-height:2em;padding-bottom:40px}.sidebar li{margin:6px 0 6px 15px}.sidebar ul{margin:0;padding:0}.sidebar ul ul{margin-left:15px}.sidebar ul,.sidebar ul li{list-style:none}.sidebar ul li a{color:var(--gray8);display:block;font-size:14px;text-decoration:none}.sidebar ul li a:hover{text-decoration:underline}.sidebar ul li a:focus-visible{text-decoration:underline}.sidebar ul li.active>a{border-right:2px solid var(--secondary-color);color:var(--secondary-color);font-weight:700}.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:focus-visible::-webkit-scrollbar-thumb{background:hsla(0,0%,53%,.4)}.sidebar:hover::-webkit-scrollbar-track{background:hsla(0,0%,53%,.1)}.sidebar:focus-visible::-webkit-scrollbar-track{background:hsla(0,0%,53%,.1)}body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}.content{bottom:0;left:300px;padding-top:60px;position:absolute;right:0;top:0;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}@media print{.github-corner,.sidebar,.sidebar-toggle{display:none}}@media screen and (max-width:768px){.sidebar{position:fixed}.github-corner svg{height:62px!important;width:62px!important}.sidebar{left:-300px}.sidebar-toggle{background:var(--bg-color);border:0;cursor:pointer;display:inherit;height:62px;left:6px;outline:none;padding:0 10px;position:fixed;top:0;transition:opacity .3s;width:100%;z-index:2}.sidebar-toggle span{background-color:var(--secondary-color);display:block;height:3px;margin-bottom:5px;width:24px}body.sidebar-toggle{display:none}body.close .content,body.close .sidebar{transform:translateX(280px)}body.close .sidebar-toggle{background:var(--background-color);transition:background-color .3s;width:284px}main{height:auto;overflow-x:hidden}.content{left:0;max-width:100vw;padding-top:20px;position:static;transition:transform .25s ease}.github-corner:hover .octo-arm{animation:none}.github-corner:focus-visible .octo-arm{animation:none}}.sidebar,body{background-color:var(--bg-color);color:var(--primary-color)}.markdown-section :is(h1,h2,h3,h4,h5){font-family:var(--serif-font);letter-spacing:-.016em;line-height:1.25}.markdown-section :is(h1,h2,h3,h4,h5) a:hover:after{content:"#";display:inline-block;margin-left:6px;position:relative}.markdown-section :is(h1,h2,h3,h4,h5) a:focus-visible:after{content:"#";display:inline-block;margin-left:6px;position:relative}.markdown-section a{color:var(--primary-color);text-decoration:none}.markdown-section :is(li,p) a{color:var(--secondary-color)}.markdown-section :is(li,p) a:hover{text-decoration:underline;text-underline-offset:2px}.markdown-section :is(li,p) a:focus-visible{text-decoration:underline;text-underline-offset:2px}.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 :is(p,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:2px solid var(--secondary-color);margin:2em 0;padding-left:20px}.markdown-section blockquote p{margin-left:0;padding:12px 0}.markdown-section :is(code,pre,img){border-radius:4px}.markdown-section code{border:1px solid var(--border-color);font-family:var(--code-font);font-size:.75rem;padding:3px 10px;white-space:nowrap}.markdown-section pre{border:1px solid var(--border-color);line-height:1.5rem;margin:1.2em 0;overflow:auto;padding:0 .7rem;position:relative;word-wrap:normal}.markdown-section pre:after{color:var(--gray6);content:attr(data-lang);font-size:.6rem;height:15px;line-height:15px;padding:5px 10px 0;position:absolute;right:0;text-align:right;top:0}.markdown-section :is(pre,pre>code){-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial}.markdown-section pre>code{border:0;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 :is(code:after,code:before){letter-spacing:.8px;letter-spacing:.05rem}.token:is(.class-name,.function,.number){color:var(--orange)}.token.string{color:var(--pink)}.token:is(.constant,.keyword){color:var(--red)}.token.comment{color:var(--gray8)} --------------------------------------------------------------------------------