Architecture
This document describes the architecture of the @marlinjai/data-table package.
Package Structure
@marlinjai/data-table/
βββ packages/
β βββ core/ # Core types, interfaces, engines
β β βββ src/
β β β βββ types.ts # All TypeScript interfaces
β β β βββ db-adapter.ts # DatabaseAdapter interface
β β β βββ formula/ # Formula Engine
β β β β βββ FormulaParser.ts # Lexer + recursive descent parser
β β β β βββ FormulaFunctions.ts # 50+ built-in functions
β β β β βββ FormulaEngine.ts # AST evaluation with caching
β β β β βββ index.ts
β β β βββ rollup/ # Rollup Engine
β β β β βββ RollupEngine.ts # Aggregation calculator
β β β β βββ index.ts
β β β βββ index.ts
β β βββ package.json
β β
β βββ react/ # React components + hooks
β β βββ src/
β β β βββ providers/ # DataTableProvider
β β β βββ components/ # TableView, cells, filters
β β β β βββ views/ # View-specific components
β β β β βββ ViewSwitcher.tsx # View tab navigation
β β β β βββ BoardView.tsx # Kanban board view
β β β β βββ CalendarView.tsx # Calendar grid view
β β β βββ hooks/ # useTable, useColumns, useViews
β β β βββ styles/ # CSS variables + base styles
β β βββ package.json
β β
β βββ adapter-memory/ # In-memory adapter
β βββ adapter-d1/ # Cloudflare D1 adapter
β
βββ demo/ # Demo application
Design Patterns
Adapter Pattern
The package uses the Adapter Pattern to decouple data storage from the UI layer:
βββββββββββββββββββ βββββββββββββββββββββ
β React Layer ββββββΆβ DatabaseAdapter β
β (components) β β (interface) β
βββββββββββββββββββ βββββββββββββββββββββ
β
ββββββββββββββββββΌβββββββββββββββββ
βΌ βΌ βΌ
ββββββββββββ ββββββββββββ ββββββββββββ
β Memory β β D1 β β Supabase β
β Adapter β β Adapter β β Adapter β
ββββββββββββ ββββββββββββ ββββββββββββ
Benefits:
- Test with in-memory adapter, deploy with D1/Supabase
- Easy to add new storage backends
- No storage logic in UI components
Component Hierarchy
DataTableProvider (context)
βββ TableView
βββ TableHeader
β βββ HeaderCell (sortable, resizable)
βββ TableBody
β βββ TableRow
β βββ CellRenderer β TextCell | NumberCell | SelectCell | ...
βββ AddRowButton
Portal Pattern for Dropdowns
Dropdowns (SelectCell, MultiSelectCell) use React Portals to render to document.body:
{isOpen && createPortal(
<div className="dt-dropdown" style={{ position: 'fixed', ... }}>
{/* dropdown content */}
</div>,
document.body
)}Why portals?
- Avoids clipping from parent
overflow: hidden - Correct z-index stacking
- Works in scrollable containers
Data Flow
User Action
β
βΌ
Component Event Handler (onClick, onChange)
β
βΌ
Hook Method (updateCell, addRow, etc.)
β
βΌ
DatabaseAdapter Method (async)
β
βΌ
Local State Update (optimistic or after response)
β
βΌ
React Re-render
Database Schema
The adapter operates on these entities:
Tables
interface Table {
id: string;
workspaceId: string;
name: string;
icon?: string;
}Columns
interface Column {
id: string;
tableId: string;
name: string;
type: ColumnType;
position: number;
width: number;
config?: Record<string, unknown>;
}Rows
interface Row {
id: string;
tableId: string;
cells: Record<string, CellValue>; // columnId -> value
}Select Options
interface SelectOption {
id: string;
columnId: string;
name: string;
color?: string;
}CSS Architecture
All styles use CSS variables with the --dt- prefix:
variables.css β Defines all CSS variables (colors, sizing, etc.)
base.css β Base component styles using the variables
Consumers can override any variable:
:root {
--dt-bg-primary: #000;
--dt-text-primary: #fff;
}Dark mode is handled via:
@media (prefers-color-scheme: dark)- Auto.darkclass or[data-theme="dark"]- Manual
Formula Engine
The Formula Engine (packages/core/src/formula/) provides Notion-like formula evaluation capabilities using a recursive descent parser and AST-based evaluation.
Architecture
ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β Formula String ββββββΆβ FormulaParser ββββββΆβ AST β
β "prop("A") + 1" β β (Lexer + Parser)β β (Abstract Tree) β
ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββ ββββββββββββββββββββ
β Cell Value βββββββ FormulaEngine β
β β β (AST Evaluator) β
ββββββββββββββββββββ ββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββ
β FormulaFunctions β
β (50+ built-ins) β
ββββββββββββββββββββ
Components
| File | Responsibility |
|---|---|
FormulaParser.ts | Lexer (tokenizer) + recursive descent parser producing AST |
FormulaFunctions.ts | Registry of 50+ built-in functions (math, text, logic, date) |
FormulaEngine.ts | AST evaluation with caching and custom function support |
Design Pattern: Visitor Pattern
The engine uses the Visitor Pattern for AST evaluation. Each node type has a corresponding evaluation method:
private evaluateNode(node: ASTNode, context: EvaluationContext): FormulaValue {
switch (node.type) {
case 'NumberLiteral':
return node.value;
case 'PropertyReference':
return this.evaluatePropertyReference(node.propertyName, context);
case 'BinaryExpression':
return this.evaluateBinaryExpression(node.operator, node.left, node.right, context);
case 'FunctionCall':
return this.evaluateFunctionCall(node.name, node.arguments, context);
// ... other node types
}
}AST Node Types
ASTNode
βββ NumberLiteral (e.g., 42, 3.14)
βββ StringLiteral (e.g., "hello")
βββ BooleanLiteral (true, false)
βββ PropertyReference (e.g., prop("Column Name"))
βββ BinaryExpression (e.g., a + b, x == y)
βββ UnaryExpression (e.g., not x, -5)
βββ FunctionCall (e.g., add(1, 2))
βββ ConditionalExpression (ternary: a ? b : c)
Built-in Function Categories
| Category | Functions |
|---|---|
| Math | add, subtract, multiply, divide, mod, abs, round, floor, ceil, min, max, pow, sqrt, sign, ln, log10, exp |
| Text | concat, length, contains, replace, lower, upper, trim, slice, split, startsWith, endsWith, indexOf, repeat, padStart, padEnd, format, toNumber |
| Logic | if, and, or, not, empty, coalesce, equal, unequal, larger, smaller, largerEq, smallerEq |
| Date | now, today, dateAdd, dateSubtract, dateBetween, formatDate, year, month, day, dayOfWeek, hour, minute, second, timestamp, date, parseDate, startOf, endOf |
Performance Features
- AST Caching: Parsed formulas are cached to avoid re-parsing identical formula strings
- Lazy Evaluation: Conditional expressions only evaluate the taken branch
- Depth Limiting: Configurable max evaluation depth prevents infinite recursion
Rollup Engine
The Rollup Engine (packages/core/src/rollup/) calculates aggregated values from related rows based on rollup configuration.
Architecture
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β Current Row β β Related Rows β β RollupEngine β
β (has relation) ββββββΆβ (via relation) ββββββΆβ .calculate() β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Aggregated Valueβ
β (number/array) β
βββββββββββββββββββ
Supported Aggregation Types (14 total)
| Category | Aggregations |
|---|---|
| Counting | count, countValues, countUnique, countEmpty, countNotEmpty |
| Numeric | sum, average, min, max |
| Percentage | percentEmpty, percentNotEmpty |
| Display | showOriginal, showUnique |
Usage Example
const engine = new RollupEngine();
const result = engine.calculate(
{ relationColumnId: 'rel1', targetColumnId: 'price', aggregation: 'sum' },
relatedRows,
priceColumn
);
// result = 150 (sum of all related row prices)View System
The View System provides multiple ways to visualize the same table data. Views are persisted and can be customized with filters, sorts, and view-specific configurations.
View Types
type ViewType = 'table' | 'board' | 'calendar' | 'gallery' | 'timeline' | 'list';Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β View Layer (React) β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β ViewSwitcherβ β useViews β β View β β
β β (component) β β (hook) β β Components β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DatabaseAdapter (Core) β
β createView | getViews | updateView | deleteView | reorderViews β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Storage Backend β
β (Memory, D1, Supabase, etc.) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
View Data Model
interface View {
id: string;
tableId: string;
name: string;
type: ViewType;
position: number;
config: ViewConfig;
isDefault?: boolean;
}
interface ViewConfig {
filters?: FilterConfig[];
sorts?: SortConfig[];
hiddenColumns?: string[];
boardConfig?: BoardViewConfig; // For board views
calendarConfig?: CalendarViewConfig; // For calendar views
// ... other view-specific configs
}useViews Hook
Located at packages/react/src/hooks/useViews.ts, provides view state management:
interface UseViewsResult {
views: View[];
currentView: View | null;
isLoading: boolean;
error: Error | null;
// Operations
createView: (input) => Promise<View>;
updateView: (viewId, updates) => Promise<View>;
deleteView: (viewId) => Promise<void>;
reorderViews: (viewIds) => Promise<void>;
setCurrentView: (viewId) => void;
refresh: () => Promise<void>;
}ViewSwitcher Component
Located at packages/react/src/components/views/ViewSwitcher.tsx, provides:
- Tab-based view switching UI
- Inline view renaming
- View creation menu (all 6 view types)
- View deletion with confirmation
BoardView (Kanban)
The BoardView (packages/react/src/components/views/BoardView.tsx) displays data as a Kanban board with drag-and-drop support.
Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BoardView β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ ββββββββββββ
β β No Status β β Column 1 β β Column 2 β β ... ββ
β β (null group)β β (option 1) β β (option 2) β β ββ
β β β β β β β β ββ
β β βββββββββ β β βββββββββ β β βββββββββ β β ββ
β β β Card β β β β Card β β β β Card β β β ββ
β β βββββββββ β β βββββββββ β β βββββββββ β β ββ
β β βββββββββ β β βββββββββ β β β β ββ
β β β Card β β β β Card β β β β β ββ
β β βββββββββ β β βββββββββ β β β β ββ
β βββββββββββββββ βββββββββββββββ βββββββββββββββ ββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Drag-and-Drop Architecture
User Drag Start
β
βΌ
handleDragStart()
- Set draggingRowId state
- Set dataTransfer data
- Apply visual feedback (opacity)
β
βΌ
handleDragOver()
- Track dragOverGroup for visual feedback
- Allow drop (preventDefault)
β
βΌ
handleDrop()
- Determine new group value
- Update cell value via onCellChange
- Clear drag state
β
βΌ
handleDragEnd()
- Reset all drag-related state
- Restore visual styling
Configuration
interface BoardViewConfig {
groupByColumnId: string; // Must be select or multi_select column
cardProperties: string[]; // Column IDs to display on cards
showEmptyGroups?: boolean; // Show columns with no cards
}Key Features
- Groups rows by select/multi-select column values
- Drag-and-drop to change row's group value
- "No Status" column for rows without a group value
- Configurable card properties (which fields to display)
- Add card button per column (pre-fills group value)
CalendarView
The CalendarView (packages/react/src/components/views/CalendarView.tsx) displays data on a date-based grid layout.
Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β CalendarView β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β < > Today January 2024 β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β βββββββ¬ββββββ¬ββββββ¬ββββββ¬ββββββ¬ββββββ¬ββββββ β
β β Sun β Mon β Tue β Wed β Thu β Fri β Sat β β
β βββββββΌββββββΌββββββΌββββββΌββββββΌββββββΌββββββ€ β
β β β 1 β 2 β 3 β 4 β 5 β 6 β β
β β βEventβ β βEventβ β β β
β βββββββΌββββββΌββββββΌββββββΌββββββΌββββββΌββββββ€ β
β β 7 β 8 β 9 β 10 β 11 β 12 β 13 β β
β β β βEventβMulti-day spanβββββ β β
β βββββββ΄ββββββ΄ββββββ΄ββββββ΄ββββββ΄ββββββ΄ββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Event Mapping Flow
Row Data Calendar Events
ββββββββββββββββββββ ββββββββββββββββββββ
β Row 1 β β β
β startDate: 1/5 βββββββββΆβ Jan 5: [Event 1] β
β endDate: 1/7 β β Jan 6: [Event 1] β
β β β Jan 7: [Event 1] β
ββββββββββββββββββββ€ ββββββββββββββββββββ€
β Row 2 β β β
β startDate: 1/5 βββββββββΆβ Jan 5: [Event 2] β
β endDate: null β β β
ββββββββββββββββββββ ββββββββββββββββββββ
Configuration
interface CalendarViewConfig {
dateColumnId: string; // Column containing start date
endDateColumnId?: string; // Optional column for multi-day events
showWeekends?: boolean;
}Key Features
- Month navigation (prev/next/today)
- Multi-day event spanning
- Visual distinction for current day, selected day, and out-of-month days
- Event click handlers
- Day click handlers (for adding new events)
- Event count footer
Sub-items (Hierarchical Rows)
The data table supports hierarchical row structures where rows can have parent-child relationships, similar to Notion's sub-items feature.
Data Model
interface Row {
id: string;
tableId: string;
parentRowId?: string; // For sub-items/hierarchical rows
cells: Record<string, CellValue>;
// ...
}Configuration
interface SubItemsConfig {
enabled: boolean;
displayMode?: 'nested' | 'flat'; // nested shows hierarchy, flat shows all
filterMode?: 'all' | 'parents' | 'subitems'; // What to show
collapsedParents?: string[]; // Parent row IDs that are collapsed
}Query Options
interface RowQueryOptions {
// ...
parentRowId?: string | null; // null = top-level only, undefined = all, string = children of parent
includeSubItems?: boolean; // Include all sub-items recursively
}Creating Sub-items
interface CreateRowInput {
tableId: string;
parentRowId?: string; // For creating sub-items
cells?: Record<string, CellValue>;
}Display Modes
| Mode | Description |
|---|---|
nested | Shows hierarchy with indentation, parent rows have expand/collapse |
flat | Shows all rows at the same level, ignores hierarchy |
Filter Modes
| Mode | Description |
|---|---|
all | Show all rows (parents and sub-items) |
parents | Show only top-level rows (no sub-items) |
subitems | Show only sub-items (no top-level rows) |
Grouping System
The grouping system allows rows to be organized into collapsible groups based on select or multi-select column values.
Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TableView β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β GroupHeader (Group A) [βΌ] 5 β β
β β βββ Row 1 β β
β β βββ Row 2 β β
β β βββ Row 3 β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β GroupHeader (Group B) [βΆ] 3 β β
β β (collapsed - rows hidden) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
GroupHeader Component
Located at packages/react/src/components/GroupHeader.tsx:
interface GroupHeaderProps {
label: string; // Group name (option label)
rowCount: number; // Number of rows in group
isCollapsed: boolean; // Collapse state
onToggleCollapse: () => void;
colSpan: number; // Table columns to span
className?: string;
}Features
- Collapsible groups: Click header to expand/collapse
- Row count badge: Shows number of rows in each group
- Visual feedback: Chevron rotates to indicate state
- "No Status" group: Rows without a group value appear in a special group
Grouping Configuration
Grouping is configured through the view config:
interface ViewConfig {
// ...
groupByColumnId?: string; // Must be select or multi_select column
}Usage
- Add a
selectormulti_selectcolumn to your table - Configure the view with
groupByColumnIdpointing to that column - Rows are automatically grouped by their value in that column
- Rows with no value appear in the "No Status" group