├── .gitattributes ├── .github ├── dependabot.yml ├── stale.yml └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── eslint.config.js ├── index.js ├── package.json ├── test └── index.test.js └── types ├── index.d.ts └── index.test-d.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behavior to automatically convert line endings 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: "npm" 10 | directory: "/" 11 | schedule: 12 | interval: "monthly" 13 | open-pull-requests-limit: 10 14 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 15 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - "discussion" 8 | - "feature request" 9 | - "bug" 10 | - "help wanted" 11 | - "plugin suggestion" 12 | - "good first issue" 13 | # Label to use when marking an issue as stale 14 | staleLabel: stale 15 | # Comment to post when marking an issue as stale. Set to `false` to disable 16 | markComment: > 17 | This issue has been automatically marked as stale because it has not had 18 | recent activity. It will be closed if no further activity occurs. Thank you 19 | for your contributions. 20 | # Comment to post when closing a stale issue. Set to `false` to disable 21 | closeComment: false 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next 8 | - 'v*' 9 | paths-ignore: 10 | - 'docs/**' 11 | - '*.md' 12 | pull_request: 13 | paths-ignore: 14 | - 'docs/**' 15 | - '*.md' 16 | 17 | permissions: 18 | contents: read 19 | 20 | jobs: 21 | test: 22 | permissions: 23 | contents: write 24 | pull-requests: write 25 | uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5 26 | with: 27 | license-check: true 28 | lint: true 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | # Vim swap files 133 | *.swp 134 | 135 | # macOS files 136 | .DS_Store 137 | 138 | # Clinic 139 | .clinic 140 | 141 | # lock files 142 | bun.lockb 143 | package-lock.json 144 | pnpm-lock.yaml 145 | yarn.lock 146 | 147 | # editor files 148 | .vscode 149 | .idea 150 | 151 | #tap files 152 | .tap/ 153 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Fastify 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @fastify/leveldb 2 | 3 | [![CI](https://github.com/fastify/fastify-leveldb/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/fastify-leveldb/actions/workflows/ci.yml) 4 | [![NPM version](https://img.shields.io/npm/v/@fastify/leveldb.svg?style=flat)](https://www.npmjs.com/package/@fastify/leveldb) 5 | [![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard) 6 | 7 | 8 | Fastify LevelDB connection plugin, with this you can share the same Level connection in every part of your server. 9 | 10 | Under the hood [Levelup](https://github.com/Level/levelup) is used, the options that you pass to register will be passed to Levelup. 11 | 12 | ## Install 13 | ``` 14 | npm i @fastify/leveldb 15 | ``` 16 | 17 | ### Compatibility 18 | | Plugin version | Fastify version | 19 | | ---------------|-----------------| 20 | | `>=6.x` | `^5.x` | 21 | | `^5.x` | `^4.x` | 22 | | `>=3.x <5.x` | `^3.x` | 23 | | `^0.x <3.x` | `^2.x` | 24 | | `^0.x <3.x` | `^1.x` | 25 | 26 | Please note that if a Fastify version is out of support, then so are the corresponding versions of this plugin 27 | in the table above. 28 | See [Fastify's LTS policy](https://github.com/fastify/fastify/blob/main/docs/Reference/LTS.md) for more details. 29 | 30 | ## Usage 31 | Add it to you project with `register`, configure the database name and you are done! 32 | 33 | You can access LevelDB via `fastify.level[name]`. 34 | ```js 35 | const fastify = require('fastify')() 36 | 37 | fastify.register( 38 | require('@fastify/leveldb'), 39 | { name: 'db' } 40 | ) 41 | 42 | fastify.get('/foo', async function (req, reply) { 43 | const val = await this.level.db.get(req.query.key) 44 | return val 45 | }) 46 | 47 | fastify.post('/foo', async function (req, reply) { 48 | await this.level.db.put(req.body.key, req.body.value) 49 | return { status: 'ok' } 50 | }) 51 | 52 | fastify.listen({ port: 3000 }, err => { 53 | if (err) throw err 54 | console.log(`server listening on ${fastify.server.address().port}`) 55 | }) 56 | ``` 57 | 58 | By default, [Leveldown](https://github.com/Level/leveldown) is used for the store but you can use any [Abstact-leveldown](https://github.com/Level/abstract-leveldown/) compliant store, such as [memdown](https://github.com/Level/memdown). 59 | 60 | First, you must install the store: 61 | 62 | ```sh 63 | npm i memdown 64 | ``` 65 | 66 | Next, initialize the plugin with the given store: 67 | 68 | ```js 69 | fastify.register(require('@fastify/leveldb'), { 70 | name: 'db', 71 | options: { 72 | store: require('memdown') 73 | } 74 | }) 75 | ``` 76 | 77 | By default the path where the db will be created is the name option, but you can also pass a custom path as well. 78 | ```js 79 | fastify.register( 80 | require('@fastify/leveldb'), 81 | { name: 'db', path: '.local' } 82 | ) 83 | ``` 84 | 85 | ## Acknowledgments 86 | 87 | This project is kindly sponsored by: 88 | - [nearForm](https://nearform.com) 89 | - [LetzDoIt](https://www.letzdoitapp.com/) 90 | 91 | ## License 92 | 93 | Licensed under [MIT](./LICENSE). 94 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = require('neostandard')({ 4 | ignores: require('neostandard').resolveIgnoresFromGitignore(), 5 | ts: true 6 | }) 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fp = require('fastify-plugin') 4 | const levelup = require('levelup') 5 | const leveldown = require('leveldown') 6 | const encode = require('encoding-down') 7 | 8 | // mostly from level-packager 9 | function levelMore (location, options, next) { 10 | const store = options.store || leveldown 11 | delete options.store 12 | for (const m of ['destroy', 'repair']) { 13 | if (typeof store[m] === 'function') { 14 | levelMore[m] = store[m].bind(store) 15 | } 16 | } 17 | 18 | return levelup(encode(store(location), options), options, next) 19 | } 20 | 21 | levelMore.errors = levelup.errors 22 | 23 | function fastifyLeveldb (fastify, opts, next) { 24 | if (!opts.name) { 25 | return next(new Error('Missing database name')) 26 | } 27 | 28 | const { name, path } = opts 29 | opts.options = opts.options || {} 30 | 31 | if (!fastify.hasDecorator('level')) { 32 | fastify.decorate('level', {}) 33 | } 34 | 35 | if (fastify.level[name]) { 36 | return next(new Error(`Level namespace already used: ${name}`)) 37 | } 38 | 39 | fastify.addHook('onClose', (instance, done) => { 40 | instance.level[name].close(done) 41 | }) 42 | 43 | fastify.level[name] = levelMore(path || name, opts.options, next) 44 | } 45 | 46 | module.exports = fp(fastifyLeveldb, { 47 | fastify: '5.x', 48 | name: '@fastify/leveldb' 49 | }) 50 | module.exports.default = fastifyLeveldb 51 | module.exports.fastifyLeveldb = fastifyLeveldb 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fastify/leveldb", 3 | "version": "6.1.0", 4 | "description": "Plugin to share a common LevelDB connection across Fastify.", 5 | "main": "index.js", 6 | "type": "commonjs", 7 | "types": "types/index.d.ts", 8 | "scripts": { 9 | "lint": "eslint", 10 | "lint:fix": "eslint --fix", 11 | "test": "npm run test:unit && npm run test:typescript", 12 | "test:typescript": "tsd", 13 | "test:unit": "c8 --100 node --test" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/fastify/fastify-leveldb.git" 18 | }, 19 | "keywords": [ 20 | "fastify", 21 | "level", 22 | "leveldb", 23 | "database" 24 | ], 25 | "author": "Tomas Della Vedova - @delvedor (http://delved.org)", 26 | "contributors": [ 27 | { 28 | "name": "Matteo Collina", 29 | "email": "hello@matteocollina.com" 30 | }, 31 | { 32 | "name": "Manuel Spigolon", 33 | "email": "behemoth89@gmail.com" 34 | }, 35 | { 36 | "name": "James Sumners", 37 | "url": "https://james.sumners.info" 38 | }, 39 | { 40 | "name": "Frazer Smith", 41 | "email": "frazer.dev@icloud.com", 42 | "url": "https://github.com/fdawgs" 43 | } 44 | ], 45 | "license": "MIT", 46 | "bugs": { 47 | "url": "https://github.com/fastify/fastify-leveldb/issues" 48 | }, 49 | "homepage": "https://github.com/fastify/fastify-leveldb#readme", 50 | "funding": [ 51 | { 52 | "type": "github", 53 | "url": "https://github.com/sponsors/fastify" 54 | }, 55 | { 56 | "type": "opencollective", 57 | "url": "https://opencollective.com/fastify" 58 | } 59 | ], 60 | "dependencies": { 61 | "encoding-down": "^7.0.0", 62 | "fastify-plugin": "^5.0.0", 63 | "leveldown": "^6.0.0", 64 | "levelup": "^5.0.0" 65 | }, 66 | "devDependencies": { 67 | "@fastify/pre-commit": "^2.1.0", 68 | "@types/levelup": "^5.1.0", 69 | "c8": "^10.1.3", 70 | "eslint": "^9.17.0", 71 | "fastify": "^5.0.0", 72 | "memdown": "^6.0.0", 73 | "neostandard": "^0.12.0", 74 | "rimraf": "^6.0.1", 75 | "tsd": "^0.32.0" 76 | }, 77 | "publishConfig": { 78 | "access": "public" 79 | }, 80 | "pre-commit": [ 81 | "lint", 82 | "test" 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test, after } = require('node:test') 4 | const { existsSync } = require('node:fs') 5 | const rimraf = require('rimraf') 6 | const Fastify = require('fastify') 7 | const memdown = require('memdown') 8 | const fastifyLeveldb = require('..') 9 | 10 | after(() => { 11 | rimraf.sync('./test_db') 12 | rimraf.sync('./foo') 13 | rimraf.sync('./bar') 14 | }) 15 | 16 | test('level namespace should exist', async t => { 17 | t.plan(1) 18 | 19 | const fastify = Fastify() 20 | await fastify.register(fastifyLeveldb, { name: 'test_db' }).ready() 21 | 22 | t.assert.ok(fastify.level.test_db) 23 | return fastify.close() 24 | }) 25 | 26 | test('missing database name', async t => { 27 | t.plan(1) 28 | 29 | const fastify = Fastify() 30 | await t.assert.rejects(() => fastify 31 | .register(fastifyLeveldb, { name: undefined }) 32 | .ready(), undefined, 'Missing database name') 33 | }) 34 | 35 | test('level should support leveldb operations', async t => { 36 | t.plan(1) 37 | 38 | const fastify = Fastify() 39 | await fastify 40 | .register(fastifyLeveldb, { name: 'test_db' }) 41 | .ready() 42 | 43 | await fastify.level.test_db.put('a', 'b') 44 | const val = await fastify.level.test_db.get('a') 45 | t.assert.deepStrictEqual(val, 'b') 46 | return fastify.close() 47 | }) 48 | 49 | test('level should support other stores (memdown)', async t => { 50 | t.plan(1) 51 | 52 | const fastify = Fastify() 53 | await fastify 54 | .register(fastifyLeveldb, { name: 'test_db', options: { store: memdown } }) 55 | .ready() 56 | 57 | await fastify.level.test_db.put('a', 'b') 58 | 59 | const val = await fastify.level.test_db.get('a') 60 | t.assert.deepStrictEqual(val, 'b') 61 | return fastify.close() 62 | }) 63 | 64 | test('level should support leveldb operations (async await)', async t => { 65 | t.plan(1) 66 | const fastify = Fastify() 67 | await fastify.register(fastifyLeveldb, { name: 'test_db' }) 68 | await fastify.level.test_db.put('a', 'b') 69 | const val = await fastify.level.test_db.get('a') 70 | t.assert.deepStrictEqual(val, 'b') 71 | return fastify.close() 72 | }) 73 | 74 | test('namespaces', async t => { 75 | t.plan(2) 76 | const fastify = Fastify() 77 | await fastify.register(fastifyLeveldb, { name: 'foo' }) 78 | await fastify.register(fastifyLeveldb, { name: 'bar' }) 79 | await fastify.level.foo.put('a', 'b') 80 | await fastify.level.bar.put('a', 'b') 81 | t.assert.deepStrictEqual(await fastify.level.foo.get('a'), 'b') 82 | t.assert.deepStrictEqual(await fastify.level.bar.get('a'), 'b') 83 | return fastify.close() 84 | }) 85 | 86 | test('reuse namespaces', async t => { 87 | t.plan(1) 88 | const fastify = Fastify() 89 | fastify.register(fastifyLeveldb, { name: 'foo' }) 90 | fastify.register(fastifyLeveldb, { name: 'foo' }) 91 | await t.assert.rejects(() => fastify.ready(), undefined, 'Level namespace already used: foo') 92 | return fastify.close() 93 | }) 94 | 95 | test('store json', async t => { 96 | t.plan(1) 97 | const fastify = Fastify() 98 | await fastify.register(fastifyLeveldb, { 99 | name: 'test_db', 100 | options: { valueEncoding: 'json' } 101 | }) 102 | await fastify.level.test_db.put('greeting', { hello: 'world' }) 103 | t.assert.deepStrictEqual(await fastify.level.test_db.get('greeting'), { hello: 'world' }) 104 | return fastify.close() 105 | }) 106 | 107 | test('custom path', async t => { 108 | t.plan(4) 109 | const fastify = Fastify() 110 | await fastify.register(fastifyLeveldb, { name: 'first', path: 'foo' }) 111 | await fastify.register(fastifyLeveldb, { name: 'second', path: 'bar' }) 112 | await fastify.level.first.put('a', 'b') 113 | await fastify.level.second.put('a', 'b') 114 | t.assert.deepStrictEqual(await fastify.level.second.get('a'), 'b') 115 | t.assert.deepStrictEqual(await fastify.level.second.get('a'), 'b') 116 | t.assert.ok(existsSync('./foo')) 117 | t.assert.ok(existsSync('./bar')) 118 | return fastify.close() 119 | }) 120 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { FastifyPluginCallback } from 'fastify' 2 | import { LevelUp } from 'levelup' 3 | 4 | declare module 'fastify' { 5 | interface FastifyInstance { 6 | level: { 7 | [key: string]: LevelUp 8 | } 9 | } 10 | } 11 | 12 | type FastifyLeveldb = FastifyPluginCallback 13 | 14 | declare namespace fastifyLeveldb { 15 | export interface FastifyLeveldbOptions { 16 | name: string, 17 | path: string, 18 | options: any 19 | } 20 | /** 21 | * @deprecated Use FastifyLeveldbOptions instead 22 | */ 23 | export type LevelDBOptions = FastifyLeveldbOptions 24 | 25 | export const fastifyLeveldb: FastifyLeveldb 26 | export { fastifyLeveldb as default } 27 | } 28 | 29 | declare function fastifyLeveldb (...params: Parameters): ReturnType 30 | export = fastifyLeveldb 31 | -------------------------------------------------------------------------------- /types/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import fastify from 'fastify' 2 | import { LevelUp } from 'levelup' 3 | import { expectAssignable, expectDeprecated, expectType } from 'tsd' 4 | import fastifyLeveldb, { FastifyLeveldbOptions, LevelDBOptions } from '..' 5 | 6 | const app = fastify() 7 | 8 | app 9 | .register(fastifyLeveldb, { 10 | name: 'test', 11 | path: '.local', 12 | options: { valueEncoding: 'json' } 13 | }) 14 | .after(async (_err) => { 15 | expectType(app.level.test) 16 | }) 17 | 18 | expectDeprecated({} as LevelDBOptions) 19 | expectAssignable({} as LevelDBOptions) 20 | --------------------------------------------------------------------------------