How DrawDB Implements Custom Data Types: A Technical Deep Dive into the Type System

DrawDB implements custom data types through a proxy-based catalog system in src/data/datatypes.js that returns false for unknown types, effectively bypassing built-in validation and allowing user-defined types to pass through to the target database.

DrawDB is an open-source database diagramming tool that supports multiple SQL dialects through a flexible type-definition architecture. The system handles both built-in types and custom data types by cataloging type metadata in JavaScript base objects and exposing them through Proxy wrappers. This design allows the UI to validate standard types while gracefully falling back to permissive mode for any user-defined or database-specific types not present in the internal registry.

The Core Type Registry in src/data/datatypes.js

The foundation of DrawDB's type system lives in src/data/datatypes.js, which declares comprehensive catalogs of database-specific types and wraps them in JavaScript Proxies for safe access.

Base Type Catalogs

The file defines several base objects that enumerate supported types for each database engine:

  • defaultTypesBase
  • mysqlTypesBase
  • postgresTypesBase
  • sqliteTypesBase
  • mssqlTypesBase
  • oraclesqlTypesBase

Each entry in these catalogs is a structured object describing a data type's properties. A typical type definition includes:

  • type: The string identifier (e.g., "INTEGER", "VARCHAR")
  • color: Display color used in the UI diagram
  • checkDefault: Validation function for default values
  • isSized: Boolean indicating if the type supports length specifications
  • defaultSize: Default length value when applicable
  • hasPrecision: Boolean for numeric precision support
  • hasQuotes: Boolean indicating if values require quote wrapping
  • noDefault: Boolean flag for types that cannot have default values

Proxy Wrapper Pattern

Each base catalog is wrapped in a JavaScript Proxy that standardizes access patterns:

export const postgresTypes = new Proxy(postgresTypesBase, {
  get: (target, prop) => (prop in target ? target[prop] : false),
});

The proxy's getter returns the full type definition if the property exists in the base object, or false if the type is unknown. This eliminates undefined checks throughout the codebase and provides a consistent signal for custom type detection.

Runtime Type Resolution with useTypes

The useTypes hook in src/hooks/useTypes.js bridges the static type catalogs with the dynamic UI state, selecting the appropriate proxy based on the user's selected database engine.

Engine-Specific Selection

When a user creates a diagram, the application stores the selected database engine (e.g., "mysql", "postgres"). The useTypes hook consumes this state and returns the corresponding proxy object:

import { useTypes } from "./hooks/useTypes";

const { dbEngine } = useSettings();           // e.g., "postgres"
const types = useTypes(dbEngine);             // returns postgresTypes proxy

// Accessing a built-in type
const intDef = types["INTEGER"];              // → type definition object

Custom Type Detection Logic

When a column references a type not present in the catalog, the proxy returns false:

const customDef = types["MY_CUSTOM_TYPE"];    // → false

if (!customDef) {
  // UI treats this as a custom data type
  // Skip built-in validation; delegate to target database
}

This behavior allows DrawDB to distinguish between validated built-in types and arbitrary user-defined types without maintaining an exhaustive list of every possible database-specific extension.

Validation Behavior and Fallbacks

The type system uses the proxy's return value to control validation logic throughout the application.

Bypassing Built-in Validation

Most UI components that edit column properties call type.checkDefault(field) before persisting default values. Because the proxy returns false for unknown types, calling code can detect custom types and skip validation:

// Inside column editing components
const typeDef = types[column.type];

if (typeDef && typeDef.checkDefault) {
  // Validate against built-in rules
  const isValid = typeDef.checkDefault(field);
} else {
  // Custom data type detected
  // Allow any default value; database will validate on execution
}

This validation fallback ensures that users can specify any type name—such as PostgreSQL's domain types or MySQL's spatial extensions—without DrawDB blocking the schema definition.

Extending the Type System

While the proxy system handles ad-hoc custom types gracefully, developers can also extend the built-in catalogs permanently.

Adding New Types to the Catalog

To add a new custom type to the internal registry, extend the appropriate base object in src/data/datatypes.js with a definition following the established schema:

// In src/data/datatypes.js
postgresTypesBase["JSONB"] = {
  type: "JSONB",
  color: documentColor,
  checkDefault: (field) => true,    // Always valid
  hasCheck: false,
  isSized: false,
  hasPrecision: false,
  hasQuotes: true,
  noDefault: true,
};

export const postgresTypes = new Proxy(postgresTypesBase, {
  get: (target, prop) => (prop in target ? target[prop] : false),
});

Once added to the base object, the proxy automatically exposes the new type, and the UI treats it as a first-class citizen with full validation support.

Summary

  • Proxy-based catalogs: DrawDB wraps type definitions in JavaScript Proxies that return false for unknown types, creating a clear distinction between built-in and custom data types.
  • Engine-specific resolution: The useTypes hook in src/hooks/useTypes.js dynamically selects the appropriate type catalog based on the active database engine.
  • Validation bypass: When the proxy returns false, UI components skip checkDefault validation, allowing user-defined types to pass through unchecked.
  • Simple extensibility: Developers can extend base objects like postgresTypesBase to add permanent custom types with full validation logic.

Frequently Asked Questions

What happens when DrawDB encounters an unknown data type?

When a type name is not found in the engine-specific base catalog, the Proxy in src/data/datatypes.js returns false. The UI interprets this as a custom data type and bypasses built-in validation for that column, allowing the target database to handle type checking when the SQL is executed.

How does the useTypes hook determine which type catalog to use?

The useTypes hook reads the current database engine from the application settings (e.g., "mysql", "postgres") and returns the corresponding pre-configured Proxy object (mysqlTypes, postgresTypes, etc.). This ensures that type validation and UI colors match the specific SQL dialect being diagrammed.

Can I add my own custom types to DrawDB's internal registry?

Yes. You can extend any base object in src/data/datatypes.js (such as postgresTypesBase or mysqlTypesBase) with a new type definition object containing properties like type, color, checkDefault, and isSized. After rebuilding, the Proxy will expose your new type as a built-in option with full validation support.

Why does the type proxy return false instead of undefined?

Returning false instead of undefined provides a falsy value that explicitly indicates "unknown type" while avoiding accidental undefined reference errors. This allows the UI to use simple truthiness checks (if (typeDef)) to determine whether to apply built-in validation or treat the column as a custom data type requiring no client-side checks.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →