npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mysql2-extended

v2.0.0

Published

Thin convenience layer for mysql2 library.

Downloads

39

Readme

MySQL2 Extended

Thin convenience extension for the mysql2 library.

You may be interested in this if:

  • You want something more than the raw query function provided by mysql2.
  • You don't want a full-blown ORM.
  • You like hand-crafted SQL.
  • You want typed database results.

Coverage Status Build Status

Installation:

  • npm install mysql2-extended mysql2
  • yarn add mysql2-extended mysql2

Usage

Setup

import { MySQL2Extended } from 'mysql2-extended';
import { createPool } from 'mysql2/promise';

const pool = createPool({
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'test',
});

const db = new MySQL2Extended(pool);

Typings

Types can be passed to each query function in order to get typed data back without casting. Note that the types are not validated against the actual data, but just a convenient way for the developer to add types.

This package provides generic types to help with common workflows. For example, you probably want types for your tables, like this:

type User = {
    id: number;                  // Auto increment in MySQL
    email: string;
    firstname: string | null;
    lastname: string | null;
    active: boolean;             // Default 'true' in MySQL column definition
};

When creating (inserting) a new user, you don't want to be forced to provide values for columns that have default values in the database. You can use the OptionalDefaultFields utility type:

type UserDefaultFields = 'id' | 'firstname' | 'lastname' | 'active';

// CreateUser will only enforce 'email' as required
type CreateUser = OptionalDefaultFields<User, UserDefaultFields>;

Querying

Note that all supplied parameters will use parameter binding in the underlying database driver.

Insert

Single
await db.insert<CreateUser>('users', {
    email: '[email protected]',
    firstname: 'Bob',
    active: true,
});
INSERT INTO `users` (`email`, `firstname`, `active`) VALUES ('[email protected]', 'Bob', 1);
Bulk
await db.insert<CreateUser>('users', [
    { email: '[email protected]' },
    { email: '[email protected]' },
]);
INSERT INTO `users` (`email`) VALUES ('[email protected]'), ('[email protected]');

Select

Specific columns
const users = await db.select<Pick<User, 'firstname' | 'lastname'>>(
    ['firstname', 'lastname'],
    'users',
);
SELECT `firstname`, `lastname` FROM `users`;
All columns

First column argument used above is optional. If no columns are specified, all columns will be selected:

const users = await db.select<User>('users');
SELECT * FROM `users`;
WHERE conditions

Conditions provided in the object will be combined with AND. For more complex use-cases, see Raw for now.

const users = await db.select<User>('users', { id: 1, active: true });
SELECT * FROM `users` WHERE `id` = 1 AND `active` = 1;
Limit and offset
const users = await db.select<User>(
  'users',
  { id: 1, active: true }, // undefined/null if no conditions are present
  { limit: 10, offset: 5 },
);
SELECT * FROM `users` WHERE `id` = 1 AND `active` = 1 LIMIT 5, 10;
Ordering
const users = await db.select<User>(
  'users',
  { id: 1, active: 1 },
  {
    limit: 10,
    offset: 5,
    order: ['id', 'desc'],
    // order: [['firstname', 'desc'], ['lastname', 'asc']],
  },
);
SELECT * FROM `users`
WHERE `id` = 1 AND `active` = 1
ORDER BY `id` DESC
LIMIT 5, 10;

Update

All
await db.update<User>('users', { active: true })
UPDATE `users` SET `active` = 1;
WHERE conditions
await db.update('users', { firstname: 'Bob' }, { id: 1 }, { limit: 1 });
UPDATE `users` SET `firstname` = 'Bob' WHERE `id` = 1 LIMIT 1;

Delete

All
await db.delete('users');
DELETE FROM `users`;
WHERE conditions
await db.delete<User>('users', { id: 1, active: true });
DELETE FROM `users` WHERE `id` = 1 AND `active` = 1;

Raw

// Multiple rows
const users = await db.query<{ firstname: string }>('SELECT firstname FROM users LIMIT 2');

// One row
const [user] = await db.query<User>('SELECT * FROM users LIMIT 1');

// Parameter binding
await db.query<User>('SELECT * FROM users LIMIT ?', [1]);

Transactions

Managed transaction

A managed transaction will automatically commit if the supplied callback doesn't throw/reject any error. If the callback throws/rejects, it will rollback the transaction, and re-throw the error that caused the rollback.

Successful example
await db.transaction(async t => {
    await t.insert<CreateUser>('users', { email: '[email protected]' });
    await t.insert<CreateUser>('users', { email: '[email protected]' });
});
BEGIN;
INSERT INTO `users` (`email`) VALUES ('[email protected]');
INSERT INTO `users` (`email`) VALUES ('[email protected]');
COMMIT;
Rollback example
try {
    await db.transaction(async t => {
        await t.insert<CreateUser>('users', { email: '[email protected]' });
        throw new Error('Oops')
        await t.insert<CreateUser>('users', { email: '[email protected]' });
    })
} catch (err) {
    // err === Error('Oops')
}
BEGIN;
INSERT INTO `users` (`email`) VALUES ('[email protected]');
ROLLBACK;
Manual transactions
// Manual transaction (user controls commit/rollback)
const transaction = await db.begin();
await transaction.insert<CreateUser>('users', { email: '[email protected]' });
await transaction.insert<CreateUser>('users', { email: '[email protected]' });
await transaction.commit(); // Or rollback()
BEGIN;
INSERT INTO `users` (`email`) VALUES ('[email protected]');
INSERT INTO `users` (`email`) VALUES ('[email protected]');
COMMIT;

Future/TODO

  • Increase performance by optimizing hot code paths.
  • Return affected row count.
  • Make the library work with more databases.
  • Be able to generate SQL queries in specific formats/flavours.
  • Support for more complex condition objects.
  • Run tests with an actual database connection.