Skip to content
Advertisement

Ensure data types in Dexie.js fields

I have a Dexie.js database with the table “businessLayers” in my React application. I’d like to ensure de data types of the tuples inserted in that table. I thought the method Table.defineClass() would do that, but it does not. My db is the following:

import Dexie from 'dexie';

const db = new Dexie('MyDB');

db.version(1).stores({
  businessLayers: '++id, layer'
});

const BusinessLayer = db.businessLayers.defineClass({
  id: Number,
  layer: String,
  values: Object
});

export default db;

I’d like to make not possible to insert an invalid data type on each field. I haven’t found any built-in method to do this. Do you know any? Thank you!

Advertisement

Answer

Table.defineClass() was an old feature in Dexie 1.x for code completion only – no enforcements. The method should have been deprecated. But the functionality you need can be implemented using a DBCore middleware or creating/updating hooks. DBCore middlware would be the most performant solution as it does not need to verify existing data.

Below is a dry coded full example. Please test and reply if it works. It should support String, Number, Boolean, Array, Object, Set, Map, ArrayBuffer, Uint8Array, etc… and even custom classes. If anyone wants to make a package of this code, please go ahead! I think it could be a nice addon to dexie:

import Dexie from 'dexie';

const db = new Dexie('MyDB');

db.version(1).stores({
  businessLayers: '++id, layer'
});

// Use a DBCore middleware "enforceSchema" defined further down...
db.use(
  enforceSchema({
    businessLayers: {
      id: Number,
      layer: String,
      values: Object
    }
  }
);

// This is the function that returns the middlware:
function enforceSchema(dbSchema) {
  return {
    stack: "dbcore",
    name: "SchemaEnforcement",
    create (downlevelDatabase) {
      return {
        ...downlevelDatabase, 
        table (tableName) {
          const downlevelTable = downlevelDatabase.table(tableName);
          const tableSchema = dbSchema[tableName];
          if (!tableSchema) return downlevelTable; // No schema for this table.
          return {
            ...downlevelTable,
            mutate: req => {
              if (req.type === "add" || req.type === "put") {
                for (obj of req.values) {
                  validateSchema(tableName, tableSchema, obj);
                }
              }
              return downlevelTable.mutate(req);
            }
          }
        }
      };
    }
  };
}

function validateSchema(tableName, schema, obj) {
  const invalidProp = Object.keys(schema).find(key => 
  {
    const value = obj[key];
    const type = schema[key];
    switch (type) {
      // Handle numbers, strings and booleans specifically:
      case Number: return typeof value !== "number";
      case String: return typeof value !== "string";
      case Boolean: return typeof value !== "boolean";
      // All other types will be supported in the following
      // single line:
      default: return !(value instanceof type); 
    }
  });
  if (invalidProp) {
    // Throw exception to abort the transaction and make the
    // user get a rejected promise:
    throw new TypeError(`Invalid type given for property ${invalidProp} in table ${tableName}. ${schema[invalidProp].name} expected.`);
  }
}
Advertisement