Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Promise api #3

Merged
merged 4 commits into from
Sep 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port
.vscode/
package-lock.json
test/
.github/
.eslintrc
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,17 @@ main()

Checkout the [sqlite3 documentation](https://github.com/TryGhost/node-sqlite3/wiki/API) to see all the available methods.

_Note that Promise is not supported by the `sqlite3` module._
Note that Promise APIs are not supported by the `sqlite3` module by default.
By using the `promiseApi` option, the [`sqlite`](https://github.com/kriasoft/node-sqlite) wrapper will be used
to enhance the Database instance. It has many convenient utilities such as `migration` support.

## Options

You can pass the following options to the plugin:

```js
app.register(require('fastify-sqlite'), {
await app.register(require('fastify-sqlite'), {
promiseApi: true, // the DB instance supports the Promise API. Default false
name: 'mydb', // optional decorator name. Default null
verbose: true, // log sqlite3 queries as trace. Default false
dbFile: ':memory:', // select the database file. Default ':memory:'
Expand All @@ -56,9 +59,7 @@ app.register(require('fastify-sqlite'), {
})

// usage WITH name option
app.sqlite.myDb.all('SELECT * FROM myTable', (err, rows) => {
// do something
})
await app.sqlite.myDb.all('SELECT * FROM myTable')
```

## License
Expand Down
24 changes: 20 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const fp = require('fastify-plugin')
const sqlite3 = require('sqlite3')
const { open } = require('sqlite')

function fastifySqlite (fastify, opts, next) {
const Sqlite = (opts.verbose === true)
Expand All @@ -11,10 +12,22 @@ function fastifySqlite (fastify, opts, next) {
const filename = opts.dbFile || ':memory:'
const mode = opts.mode || (sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE | sqlite3.OPEN_FULLMUTEX)

const db = new Sqlite.Database(filename, mode, (err) => {
if (err) {
if (opts.promiseApi === true) {
open({
filename,
mode,
driver: Sqlite.Database
}).then(setupDatabase, setupDatabase)
} else {
// eslint-disable-next-line
new Sqlite.Database(filename, mode, setupDatabase)
}

function setupDatabase (err) {
if (err && err instanceof Error) {
return next(err)
}
const db = err || this

if (opts.verbose === true) {
db.on('trace', function (trace) {
Expand All @@ -23,7 +36,7 @@ function fastifySqlite (fastify, opts, next) {
}

decorateFastifyInstance(fastify, db, opts, next)
})
}
}

function decorateFastifyInstance (fastify, db, opts, next) {
Expand Down Expand Up @@ -52,7 +65,10 @@ function decorateFastifyInstance (fastify, db, opts, next) {
}

function close (instance, done) {
this.close(done)
const isProm = this.close(done)
if (isProm?.then) {
isProm.then(done, done)
}
}

module.exports = fp(fastifySqlite, {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
},
"dependencies": {
"fastify-plugin": "^4.2.1",
"sqlite": "^4.1.2",
"sqlite3": "^5.0.11"
}
}
35 changes: 35 additions & 0 deletions test/migrations/001-init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--------------------------------------------------------------------------------
-- Up
--------------------------------------------------------------------------------

CREATE TABLE Family (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);

CREATE TABLE Person (
id INTEGER PRIMARY KEY,
familyId INTEGER NOT NULL,
name TEXT NOT NULL,
nick TEXT,
FOREIGN KEY(familyId) REFERENCES Family(id)
);

CREATE INDEX Person_ix_familyId ON Person (familyId);

CREATE TABLE Friend (
personId INTEGER NOT NULL,
friendId INTEGER NOT NULL,
isTheBest NUMERIC NOT NULL DEFAULT 0,
PRIMARY KEY (personId, friendId),
CONSTRAINT Friend_ck_isTheBest CHECK (isTheBest IN (0, 1))
);

--------------------------------------------------------------------------------
-- Down
--------------------------------------------------------------------------------

DROP INDEX Person_ix_familyId;
DROP TABLE Friend;
DROP TABLE Person;
DROP TABLE Family;
26 changes: 26 additions & 0 deletions test/migrations/002-data.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--------------------------------------------------------------------------------
-- Up
--------------------------------------------------------------------------------
INSERT INTO Family (id, name) VALUES (1, 'Foo');
INSERT INTO Family (id, name) VALUES (2, 'Bar');
INSERT INTO Family (id, name) VALUES (3, 'Baz');

INSERT INTO Person (id, familyId, name) VALUES (1, 1, 'John');
INSERT INTO Person (id, familyId, name) VALUES (2, 1, 'Jakie');
INSERT INTO Person (id, familyId, name) VALUES (3, 1, 'Jessie');

INSERT INTO Person (id, familyId, name) VALUES (4, 2, 'Micky');
INSERT INTO Person (id, familyId, name) VALUES (5, 2, 'Lory');
INSERT INTO Person (id, familyId, name) VALUES (6, 2, 'Sara');
INSERT INTO Person (id, familyId, name) VALUES (7, 2, 'Jenny');

INSERT INTO Person (id, familyId, name) VALUES (8, 3, 'Brian');
INSERT INTO Person (id, familyId, name) VALUES (9, 3, 'Brown');
INSERT INTO Person (id, familyId, name) VALUES (10, 3, 'Fuzzy');

INSERT INTO Friend (personId, friendId, isTheBest) VALUES (1, 8, 0);
INSERT INTO Friend (personId, friendId, isTheBest) VALUES (1, 6, 0);
INSERT INTO Friend (personId, friendId, isTheBest) VALUES (1, 9, 1);

INSERT INTO Friend (personId, friendId, isTheBest) VALUES (2, 4, 0);
INSERT INTO Friend (personId, friendId, isTheBest) VALUES (2, 6, 0);
129 changes: 129 additions & 0 deletions test/promise.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
'use strict'

const { test } = require('tap')
const fastify = require('fastify')
const plugin = require('../index')

test('basic test', async t => {
const app = fastify()
app.register(plugin, { promiseApi: true })
t.teardown(app.close.bind(app))

await app.ready()

t.ok(app.sqlite)
t.ok(app.sqlite.migrate)
})

test('promise api', async t => {
const app = fastify()
app.register(plugin, { promiseApi: true })
t.teardown(app.close.bind(app))

await app.ready()

await app.sqlite.migrate({
migrationsPath: 'test/migrations'
})

const johnFriends = await app.sqlite.all(`
SELECT * FROM Person
JOIN Friend ON Person.id = Friend.personId
JOIN Person AS FriendPerson ON Friend.friendId = FriendPerson.id
WHERE Person.name = 'John'
`)

t.equal(johnFriends.length, 3)
})

test('verbose mode', async t => {
const createSql = 'CREATE TABLE foo (id INT, txt TEXT)'

let waitLogResolve
const waitLog = new Promise(resolve => { waitLogResolve = resolve })

function Logger (...args) { this.args = args }
Logger.prototype.info = function (msg) { t.fail() }
Logger.prototype.error = function (msg) { t.fail() }
Logger.prototype.debug = function (msg) { t.fail() }
Logger.prototype.fatal = function (msg) { t.fail() }
Logger.prototype.warn = function (msg) { t.fail() }
Logger.prototype.trace = function (msg) {
t.same(msg, { sql: createSql })
waitLogResolve()
}
Logger.prototype.child = function () { return new Logger() }

const myLogger = new Logger()

const app = fastify({
logger: myLogger
})
app.register(plugin, {
promiseApi: true,
verbose: true
})
t.teardown(app.close.bind(app))

await app.ready()
await app.sqlite.exec(createSql)
t.pass('table created')
await waitLog
})

test('multiple register', async t => {
const app = fastify()
app.register(plugin, { promiseApi: true, name: 'db1' })
app.register(plugin, { promiseApi: true, name: 'db2' })
t.teardown(app.close.bind(app))

await app.ready()

t.ok(app.sqlite.db1)
t.ok(app.sqlite.db2)
})

test('multiple register same name error', async t => {
const app = fastify()
app.register(plugin, { promiseApi: true, name: 'db1' })
app.register(plugin, { promiseApi: true, name: 'db1' })
t.teardown(app.close.bind(app))

try {
await app.ready()
t.fail('should throw')
} catch (error) {
t.match(error.message, 'Connection name [db1] already registered')
}
})

test('multiple register error', async t => {
const app = fastify()
app.register(plugin, { promiseApi: true })
app.register(plugin, { promiseApi: true })
t.teardown(app.close.bind(app))

try {
await app.ready()
t.fail('should throw')
} catch (error) {
t.match(error.message, 'fastify-sqlite has been already registered')
}
})

test('sql connection error', async t => {
const app = fastify()
app.register(plugin, {
promiseApi: true,
dbFile: 'foobar.db',
mode: plugin.sqlite3.OPEN_READONLY
})
t.teardown(app.close.bind(app))

try {
await app.ready()
t.fail('should throw')
} catch (error) {
t.match(error.message, 'SQLITE_CANTOPEN: unable to open database file')
}
})