DEV Community

Cover image for How to handle boolean values in SQLite using JavaScript Proxies
Emmanouil Liakos
Emmanouil Liakos

Posted on • Originally published at blog.manos-liakos.dev

5 1

How to handle boolean values in SQLite using JavaScript Proxies

The problem with Booleans in SQLite

If you've ever worked with SQLite, you should be aware of the supported data types and Boolean isn't one of them. More specifically as stated here:

2.1. Boolean Datatype

SQLite does not have a separate Boolean storage class. Instead, Boolean values are stored as integers 0 (false) and 1 (true).

SQLite recognizes the keywords "TRUE" and "FALSE", as of version 3.23.0 (2018-04-02) but those keywords are really just alternative spellings for the integer literals 1 and 0 respectively.

Most JavaScript libraries for SQLite3 don't support TRUE and FALSE keywords and they require you to prepare the statements in your code using integers. For example, in better-sqlite3 you would have to do this:

const payload = {
  isActive: 1, // <======
  username: 'Brad',
  password: '1234',
  email: 'brad@gmail.com',
};

const result = database
  .prepare(
    `INSERT INTO accounts(isActive, username, password, email) VALUES(@isActive, @username, @password, @email) `
  )
  .run({ bucketID, taskSiteID, name, username, password, email }).changes;
Enter fullscreen mode Exit fullscreen mode

Using number instead of boolean across your entire app would make for a terrible developer experience (plus probably use more memory).

You could use a helper function to transform your payload objects' boolean properties to numbers (I had actually done this once, in the past), but then you'd have to manually run it before every query. Yikes. Wouldn't it be great if this logic was executed in the background, every time we prepared and ran a statement?

Welcome ES6 Proxies 👋 

One of the newer JavaScript features is the Proxy object. Proxies are essentially "traps" that intercept object operations like getters, setters and function calls. Using Proxies we can modify the SQLite JS wrapper library to execute our own logic, kind of like a middleware.

Writing the helper function

For ease of development, we are going to use mapValues & isPlainObject utility functions from lodash , but you can of course code your own. The function below will map through an object (one-level deep) and convert values of type boolean to type number.

import { mapValues } from 'lodash';

const booleanEntriesToNumbers = (object) =>
  mapValues(object, (value) =>
    typeof value === 'boolean' ? Number(value) : value
  );
Enter fullscreen mode Exit fullscreen mode

Using proxies to intercept query calls

Below we import better-sqlite3 library and create a new database instance. Afterwards, we override the default prepare method with our own, which in turn overrides the methods run, get and all, by creating a new proxy for each one. You can of course create a proxy for any other method you want.

import Database from 'better-sqlite3';

// Create new database instance
const db = new Database(dbFilePath);

// We will use this function to override the default "prepare" method
const proxiedPrepare = new Proxy(db.prepare, {
    apply: (prepare, prepareThisArg, [stringStatement]) => {
      const statement = prepare.call(prepareThisArg, stringStatement);

      // Override the default "run" method
      statement.run = new Proxy(statement.run, {
        apply: (run, runThisArg, args) => {
          const mappedArgs = args.map((arg) =>
            isPlainObject(arg) ? booleanEntriesToNumbers(arg) : arg
          );

          return run.call(runThisArg, ...mappedArgs);
        },
      });

      // Override the default "get" method
      statement.get = new Proxy(statement.get, {
        apply: (get, getThisArg, args) => {
          const mappedArgs = args.map((arg) =>
            isPlainObject(arg) ? booleanEntriesToNumbers(arg) : arg
          );

          return get.call(getThisArg, ...mappedArgs);
        },
      });

      // Override the default "all" method
      statement.all = new Proxy(statement.all, {
        apply: (all, allThisArg, args) => {
          const mappedArgs = args.map((arg) =>
            isPlainObject(arg) ? booleanEntriesToNumbers(arg) : arg
          );

          return all.call(allThisArg, ...mappedArgs);
        },
      });

      return statement;
    },
  });

// Override the default "prepare" method
db.prepare = proxiedPrepare;
Enter fullscreen mode Exit fullscreen mode

Essentially, once a call to the prepare method is triggered, we tell JavaScript: Wait! We want to modify this function call. Instead of executing the logic that the original developer intended, we instead want to execute our own logic first (which is the mapping of the object payload). After executing our own logic, we return the result of calling the original method by using call to bind the this argument. If you want to read more about how proxies work, read here. For our implementation we used the apply method here.

Thanks for reading this post, I hope it helped someone working with SQLite in JavaScript 👊

SurveyJS custom survey software

Simplify data collection in your JS app with a fully integrated form management platform. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more. Integrates with any backend system, giving you full control over your data and no user limits.

Learn more

Top comments (0)

Billboard image

Try REST API Generation for MS SQL Server.

DreamFactory generates live REST APIs from database schemas with standardized endpoints for tables, views, and procedures in OpenAPI format. We support on-prem deployment with firewall security and include RBAC for secure, granular security controls.

See more!

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay