Data Explorer API
Use the Data Explorer API to fetch data from the database with ease
The DataExplorerService
is a comprehensive service for querying, manipulating, and analyzing data in Supamode.
It provides a type-safe, permission-aware interface for interacting with database tables while maintaining security through Row Level Security (RLS) policies.
Table of Contents
- Basic Setup
- Basic Usage
- Core Methods
- Query & Filtering
- Data Manipulation
- Permissions & Security
- Advanced Features
- Error Handling
- Examples
Basic Setup
import { createDataExplorerService } from '@kit/data-explorer';import { Context } from 'hono';// Create service instanceconst dataExplorerService = createDataExplorerService(context);
Basic Usage
Service Creation
// The service requires a Hono context for authentication and database accessfunction createDataExplorerService(context: Context): DataExplorerService
Authentication
All operations automatically use the authenticated user's permissions through the Hono context. The service respects Row Level Security (RLS) policies and Supamode permission system.
Core Methods
1. Query Table Data
Query table data with pagination, filtering, sorting, and search capabilities.
async queryTableData(params: { schemaName: string; tableName: string; page: number; pageSize: number; properties?: Record<string, string | number | boolean | string[] | null>; search?: string; sortColumn?: string; sortDirection?: 'asc' | 'desc';}): Promise<{ data: Record<string, unknown>[]; total: number; hasMore: boolean; table: TableMetadata; columns: ColumnMetadata[];}>
Parameters:
schemaName
: Database schema name (e.g., 'public')tableName
: Target table namepage
: Page number (0-based)pageSize
: Number of records per pageproperties
: Filter conditions (see Query & Filtering)search
: Search term for searchable columnssortColumn
: Column name to sort bysortDirection
: Sort direction ('asc' or 'desc')
Example:
const result = await dataExplorerService.queryTableData({ schemaName: 'public', tableName: 'users', page: 0, pageSize: 20, properties: { 'status.eq': 'active', 'created_at.after': '2024-01-01' }, search: 'john', sortColumn: 'created_at', sortDirection: 'desc'});
2. Get Table Metadata
Retrieve table structure and configuration information.
async getTableMetadata(params: { schemaName: string; tableName: string;}): Promise<{ table: TableMetadata; columns: ColumnMetadata[];}>
Example:
const metadata = await dataExplorerService.getTableMetadata({ schemaName: 'public', tableName: 'users'});console.log(metadata.table.displayName); // "Users"console.log(metadata.columns.length); // Number of columns
3. Insert Record
Create a new record in the specified table.
async insertRecord(params: { schemaName: string; tableName: string; data: Record<string, unknown>;}): Promise<{ success: boolean; error?: string; data?: Record<string, unknown>;}>
Example:
const result = await dataExplorerService.insertRecord({ schemaName: 'public', tableName: 'users', data: { name: 'John Doe', email: 'john@example.com', status: 'active' }});if (result.success) { console.log('Record created:', result.data);} else { console.error('Error:', result.error);}
4. Update Record
Update an existing record by ID.
async updateRecord(params: { schemaName: string; tableName: string; id: string; data: Record<string, unknown>;}): Promise<{ success: boolean; error?: string; data?: Record<string, unknown>;}>
Example:
const result = await dataExplorerService.updateRecord({ schemaName: 'public', tableName: 'users', id: 'user-123', data: { name: 'John Smith', updated_at: new Date().toISOString() }});
5. Delete Record
Delete a record by ID.
async deleteRecordById(params: { schemaName: string; tableName: string; id: string;}): Promise<{ success: boolean; error?: string;}>
Example:
const result = await dataExplorerService.deleteRecordById({ schemaName: 'public', tableName: 'users', id: 'user-123'});
6. Batch Delete Records
Delete multiple records based on conditions.
async batchDeleteRecords(params: { schemaName: string; tableName: string; items: Array<Record<string, unknown>>;}): Promise<Array<{ success: boolean; condition: Record<string, unknown>; response: unknown;}>>
Example:
const results = await dataExplorerService.batchDeleteRecords({ schemaName: 'public', tableName: 'users', items: [ { id: 'user-1' }, { id: 'user-2' }, { id: 'user-3' } ]});
7. Get Field Values
Get unique values for a specific field, useful for dropdowns and filters.
async getFieldValues(params: { schemaName: string; tableName: string; fieldName: string; search?: string; limit?: number; includeTopHits?: boolean;}): Promise<{ values: Array<{ value: string; count?: number }>; topHits?: Array<{ value: string; count: number }>;}>
Example:
const fieldValues = await dataExplorerService.getFieldValues({ schemaName: 'public', tableName: 'users', fieldName: 'status', includeTopHits: true, limit: 10});console.log(fieldValues.values); // [{ value: 'active' }, { value: 'inactive' }]console.log(fieldValues.topHits); // [{ value: 'active', count: 150 }]
8. Get Data Permissions
Check user permissions for a specific table.
async getDataPermissions(params: { schemaName: string; tableName: string;}): Promise<{ canCreate: boolean; canUpdate: boolean; canDelete: boolean;}>
Example:
const permissions = await dataExplorerService.getDataPermissions({ schemaName: 'public', tableName: 'users'});if (permissions.canCreate) { // Show create button}
Query & Filtering
SQL Operators
The service supports a comprehensive set of SQL operators for filtering:
Comparison Operators
eq
- Equal toneq
- Not equal tolt
- Less thanlte
- Less than or equal togt
- Greater thangte
- Greater than or equal to
Text Operators
contains
- Text contains (case-insensitive)startsWith
- Text starts withendsWith
- Text ends withlike
- SQL LIKE pattern matchingilike
- Case-insensitive LIKE
Array Operators
in
- Value in arraynotIn
- Value not in arrayarrayContains
- Array contains valuearrayContainedBy
- Array contained by valueoverlaps
- Arrays overlap
Null Operators
isNull
- Value is nullnotNull
- Value is not null
Range Operators
between
- Value between rangenotBetween
- Value not between range
Date Operators
before
- Date beforebeforeOrOn
- Date before or onafter
- Date afterafterOrOn
- Date after or onduring
- Date during
JSON Operators (for JSONB fields)
containsText
- JSON contains texthasKey
- JSON has keykeyEquals
- JSON key equals valuepathExists
- JSON path exists
Filter Syntax
Filters are specified using the format column.operator
:
const filters = { // Basic comparison 'status.eq': 'active', 'age.gte': 18, 'name.contains': 'john', // Array operations 'category.in': ['electronics', 'books'], 'tags.arrayContains': 'featured', // Date operations 'created_at.after': '2024-01-01', 'updated_at.between': ['2024-01-01', '2024-12-31'], // Null checks 'deleted_at.isNull': true, // JSON operations 'metadata.hasKey': 'premium', 'settings.keyEquals': { theme: 'dark' }};
Relative Date Filtering
The service supports relative date filtering with predefined options:
const filters = { 'created_at.eq': '__rel_date:today', 'updated_at.eq': '__rel_date:last_7_days', 'published_at.eq': '__rel_date:this_month'};
Available Relative Dates:
today
- Today's date rangeyesterday
- Yesterday's date rangetomorrow
- Tomorrow's date rangethisWeek
- Current weeklastWeek
- Previous weekthisMonth
- Current monthlastMonth
- Previous monththisYear
- Current yearlastYear
- Previous yearlast_7_days
- Last 7 dayslast_30_days
- Last 30 dayslast_90_days
- Last 90 days
Search Functionality
The search parameter performs a case-insensitive search across all columns marked as is_searchable: true
:
const results = await dataExplorerService.queryTableData({ schemaName: 'public', tableName: 'users', search: 'john doe', page: 0, pageSize: 20});
Sorting
Sort results by any sortable column:
const results = await dataExplorerService.queryTableData({ schemaName: 'public', tableName: 'users', sortColumn: 'created_at', sortDirection: 'desc', page: 0, pageSize: 20});
Pagination
Efficient pagination with total count:
const results = await dataExplorerService.queryTableData({ schemaName: 'public', tableName: 'users', page: 0, // First page (0-based) pageSize: 20, // 20 records per page});console.log(results.data.length); // Number of records returnedconsole.log(results.total); // Total number of recordsconsole.log(results.hasMore); // Whether there are more pages
Data Manipulation
Advanced Update Operations
Update by Conditions
Update records based on complex conditions:
async updateRecordByConditions(params: { schemaName: string; tableName: string; conditions: Record<string, unknown>; data: Record<string, unknown>;}): Promise<{ success: boolean; error?: string; data?: Record<string, unknown>;}>
Example:
const result = await dataExplorerService.updateRecordByConditions({ schemaName: 'public', tableName: 'users', conditions: { status: 'pending', created_at: { lt: '2024-01-01' } }, data: { status: 'expired' }});
Delete by Conditions
Delete records based on conditions:
async deleteRecordByConditions(params: { schemaName: string; tableName: string; conditions: Record<string, unknown>;}): Promise<{ success: boolean; error?: string;}>
Example:
const result = await dataExplorerService.deleteRecordByConditions({ schemaName: 'public', tableName: 'sessions', conditions: { expires_at: { lt: new Date().toISOString() } }});
Data Validation
All data operations respect:
- Database constraints (foreign keys, unique constraints, etc.)
- Column types and formatting
- Required fields validation
- Custom validation rules defined in table metadata
Transaction Safety
All operations are wrapped in database transactions to ensure data consistency:
// All operations are automatically wrapped in transactionsconst result = await dataExplorerService.insertRecord({ schemaName: 'public', tableName: 'orders', data: orderData});// If the operation fails, the transaction is automatically rolled back
Permissions & Security
Row Level Security (RLS)
The service automatically respects PostgreSQL Row Level Security policies:
// Only returns records the user has permission to seeconst results = await dataExplorerService.queryTableData({ schemaName: 'public', tableName: 'sensitive_data', page: 0, pageSize: 20});
Audit Logging
All data modifications are automatically logged for audit purposes:
// This operation will be logged in the audit trailconst result = await dataExplorerService.updateRecord({ schemaName: 'public', tableName: 'users', id: 'user-123', data: { status: 'inactive' }});
Bulk Operations
Efficient bulk operations for large datasets:
// Batch delete multiple recordsconst results = await dataExplorerService.batchDeleteRecords({ schemaName: 'public', tableName: 'temporary_records', items: recordsToDelete});// Check resultsresults.forEach((result, index) => { if (result.success) { console.log(`Record ${index} deleted successfully`); } else { console.error(`Failed to delete record ${index}`); }});
Field Value Analytics
Get insights about field distributions:
const fieldAnalytics = await dataExplorerService.getFieldValues({ schemaName: 'public', tableName: 'users', fieldName: 'country', includeTopHits: true, limit: 10});// Use for analytics or dropdown populationfieldAnalytics.topHits.forEach(hit => { console.log(`${hit.value}: ${hit.count} users`);});
Error Handling
Common Error Patterns
try { const result = await dataExplorerService.insertRecord({ schemaName: 'public', tableName: 'users', data: userData }); if (!result.success) { // Handle application-level errors console.error('Insert failed:', result.error); return; } // Success case console.log('Record created:', result.data);} catch (error) { // Handle system-level errors console.error('System error:', error);}
Permission Errors
Checking permissions is mostly useful for UI purposes, to show or hide buttons based on the user's permissions, because RLS will already prevent the user from accessing the data if they don't have the necessary permissions.
const permissions = await dataExplorerService.getDataPermissions({ schemaName: 'public', tableName: 'admin_users'});if (!permissions.canCreate) { throw new Error('Insufficient permissions to create admin users');}
Examples
Example 1: User Management Dashboard
class UserDashboard { constructor(private dataExplorer: DataExplorerService) {} async loadUsers(page: number = 0, filters: Record<string, any> = {}) { const results = await this.dataExplorer.queryTableData({ schemaName: 'public', tableName: 'users', page, pageSize: 20, properties: filters, sortColumn: 'created_at', sortDirection: 'desc' }); return { users: results.data, totalPages: Math.ceil(results.total / 20), hasMore: results.hasMore }; } async searchUsers(searchTerm: string) { return await this.dataExplorer.queryTableData({ schemaName: 'public', tableName: 'users', search: searchTerm, page: 0, pageSize: 10 }); } async createUser(userData: any) { const permissions = await this.dataExplorer.getDataPermissions({ schemaName: 'public', tableName: 'users' }); if (!permissions.canCreate) { throw new Error('No permission to create users'); } return await this.dataExplorer.insertRecord({ schemaName: 'public', tableName: 'users', data: userData }); } async updateUser(userId: string, updates: any) { return await this.dataExplorer.updateRecord({ schemaName: 'public', tableName: 'users', id: userId, data: updates }); } async deleteUser(userId: string) { const permissions = await this.dataExplorer.getDataPermissions({ schemaName: 'public', tableName: 'users' }); if (!permissions.canDelete) { throw new Error('No permission to delete users'); } return await this.dataExplorer.deleteRecordById({ schemaName: 'public', tableName: 'users', id: userId }); }}
Example 2: E-commerce Product Management
class ProductManager { constructor(private dataExplorer: DataExplorerService) {} async getProducts(filters: { category?: string; priceRange?: [number, number]; inStock?: boolean; search?: string; } = {}) { const properties: Record<string, any> = {}; if (filters.category) { properties['category.eq'] = filters.category; } if (filters.priceRange) { properties['price.between'] = filters.priceRange; } if (filters.inStock) { properties['stock_quantity.gt'] = 0; } return await this.dataExplorer.queryTableData({ schemaName: 'public', tableName: 'products', properties, search: filters.search, page: 0, pageSize: 50, sortColumn: 'name', sortDirection: 'asc' }); } async getLowStockProducts() { return await this.dataExplorer.queryTableData({ schemaName: 'public', tableName: 'products', properties: { 'stock_quantity.lte': 10, 'status.eq': 'active' }, sortColumn: 'stock_quantity', sortDirection: 'asc', page: 0, pageSize: 100 }); } async updateStock(productId: string, quantity: number) { return await this.dataExplorer.updateRecord({ schemaName: 'public', tableName: 'products', id: productId, data: { stock_quantity: quantity, updated_at: new Date().toISOString() } }); } async getProductCategories() { return await this.dataExplorer.getFieldValues({ schemaName: 'public', tableName: 'products', fieldName: 'category', includeTopHits: true }); }}
Example 3: Analytics Dashboard
class AnalyticsDashboard { constructor(private dataExplorer: DataExplorerService) {} async getRecentActivity() { return await this.dataExplorer.queryTableData({ schemaName: 'public', tableName: 'activities', properties: { 'created_at.after': '__rel_date:last_7_days' }, sortColumn: 'created_at', sortDirection: 'desc', page: 0, pageSize: 20 }); } async getMonthlyStats() { return await this.dataExplorer.queryTableData({ schemaName: 'public', tableName: 'analytics', properties: { 'date.during': '__rel_date:this_month' }, sortColumn: 'date', sortDirection: 'asc', page: 0, pageSize: 31 }); } async getTopPerformers() { return await this.dataExplorer.queryTableData({ schemaName: 'public', tableName: 'performance_metrics', properties: { 'score.gte': 80, 'active.eq': true }, sortColumn: 'score', sortDirection: 'desc', page: 0, pageSize: 10 }); }}