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:
defaultTypesBasemysqlTypesBasepostgresTypesBasesqliteTypesBasemssqlTypesBaseoraclesqlTypesBase
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 diagramcheckDefault: Validation function for default valuesisSized: Boolean indicating if the type supports length specificationsdefaultSize: Default length value when applicablehasPrecision: Boolean for numeric precision supporthasQuotes: Boolean indicating if values require quote wrappingnoDefault: 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
falsefor unknown types, creating a clear distinction between built-in and custom data types. - Engine-specific resolution: The
useTypeshook insrc/hooks/useTypes.jsdynamically selects the appropriate type catalog based on the active database engine. - Validation bypass: When the proxy returns
false, UI components skipcheckDefaultvalidation, allowing user-defined types to pass through unchecked. - Simple extensibility: Developers can extend base objects like
postgresTypesBaseto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →