Skip to main content

API Usage Guide

This document details the API usage methods of Lovrabet SDK, including CRUD operations, query parameters, response handling, and other core features.

Prerequisites

Before starting, please ensure you have completed SDK Configuration. It is recommended to use CLI auto-generated configuration.

The following examples use the standard method client.models.dataset_xxx, but you can also use the alias method client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df (functionally identical).

📊 Basic CRUD Operations​

Query List​

// Basic query
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();

// Query with parameters
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: 1, // Current page number
pageSize: 20, // Records per page
status: "active", // Custom query conditions
keyword: "search-term", // Keyword search
});

// Response structure
console.log(response.tableData); // Data list
console.log(response.total); // Total records
console.log(response.currentPage); // Current page
console.log(response.pageSize); // Page size

Get Single Record​

const user = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getOne("user-id");
console.log(user); // User detail object

Create Record​

const newUser = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.create({
name: "Jane Doe",
email: "jane@example.com",
status: "active",
});

console.log("Newly created user ID:", newUser.id);
OpenAPI Mode Support v1.1.14+

Starting from v1.1.14, OpenAPI mode also supports create() operation. Previous versions only supported WebAPI mode.

Update Record​

const updatedUser = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.update("user-id", {
name: "Jane Smith",
email: "jane.smith@example.com",
});

console.log("Updated user:", updatedUser);
OpenAPI Mode Support v1.1.14+

Starting from v1.1.14, OpenAPI mode also supports update() operation. Previous versions only supported WebAPI mode.

Delete Record​

await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.delete("user-id");
console.log("User deleted");
Operation Limitations

delete() operation is only supported in WebAPI mode (Cookie authentication). OpenAPI mode does not currently support delete operations.

Export Excel v1.1.24+​

Export dataset as Excel file, returns a downloadable file URL.

// Export all data
const fileUrl = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.excelExport();
console.log(fileUrl);
// 'https://yuntoo-export-import.oss-cn-hangzhou.aliyuncs.com/xxx.xlsx?...'

// Open download in browser
window.open(fileUrl, '_blank');

// Export with filter conditions
const fileUrl = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.excelExport({
status: 'active',
createTime: '2025-01-01'
});
window.open(fileUrl, '_blank');
Operation Limitations

excelExport() operation is only supported in WebAPI mode (Cookie authentication). OpenAPI mode does not currently support this feature.

Get Select Options v1.1.18+​

Used to get dropdown option data for data tables, suitable for Select, Radio, Checkbox and other form components.

Important Concept: Data Service Provider Pattern

getSelectOptions() allows data tables to provide their own data as dropdown options, rather than querying other tables.

Correct Understanding:

  • client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions() - Queries users table's own data, generates user list options
  • These options can be used in: order table's "Select User" field, article table's "Select Author" field, etc.

Common Misconception:

  • ❌ Calling client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.getSelectOptions() in order table to get user list
  • ✅ Should call client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions() to get user list

Core Principle: Whose data, who provides options

This reflects important software design principles:

  • đŸ—ī¸ Single Responsibility Principle (SRP) - Each model only manages and provides its own data
  • 🔌 Dependency Inversion Principle (DIP) - Order table depends on user table's provided interface, not direct coupling
  • đŸŽ¯ Separation of Concerns (SoC) - Clear separation of responsibilities between data providers and consumers
  • đŸ“Ļ Service Provider Pattern - Tables as data sources, providing standardized data services externally

This design makes the system easier to maintain, extend, and test.

// Get dropdown options for user table (query users table's own data)
const userOptions = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions({
code: "user_id", // Field name used as option value
label: "user_name", // Field name used as display text
});

// Returns standard format
console.log(userOptions);
// [
// { label: 'Zhang San', value: 'user001' },
// { label: 'Li Si', value: 'user002' },
// { label: 'Wang Wu', value: 'user003' }
// ]

// Use in React
import { Select } from "antd";

const { Option } = Select;

function UserSelector() {
const [options, setOptions] = useState([]);

useEffect(() => {
loadOptions();
}, []);

const loadOptions = async () => {
const data = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions({
code: "id",
label: "name",
});
setOptions(data);
};

return (
<Select placeholder="Select User">
{options.map((option) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
);
}

Real Application Scenarios:

// Scenario 1: Select user in order form
// Call users table to provide user options
const userOptions = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions({
code: "user_id",
label: "user_name",
});
// Used in: <Select>Select Order User</Select>

// Scenario 2: Select category in article form
// Call categories table to provide category options
const categoryOptions = await client.models.dataset_c1d2e3f4a5b6789012345678abcdef34.getSelectOptions({
code: "category_id",
label: "category_name",
});
// Used in: <Select>Select Article Category</Select>

// Scenario 3: Select department in employee form
// Call departments table to provide department options
const deptOptions = await client.models.dataset_d1e2f3a4b5c6789012345678abcdef56.getSelectOptions({
code: "dept_id",
label: "dept_name",
});
// Used in: <Select>Select Department</Select>

// Scenario 4: Select supplier in product form
// Call suppliers table to provide supplier options
const supplierOptions = await client.models.dataset_e1f2a3b4c5d6789012345678abcdef78.getSelectOptions({
code: "supplier_id",
label: "supplier_name",
});
// Used in: <Select>Select Supplier</Select>

Remember the core principle: Which table's data, call that table's getSelectOptions()

Operation Limitations

getSelectOptions() operation is only supported in WebAPI mode (Cookie authentication). OpenAPI mode does not currently support this feature.

Execute Custom SQL v1.1.19+​

Execute custom SQL queries configured on the Lovrabet platform, suitable for complex statistics and aggregation query scenarios.

API Change

From v1.1.19 onwards, it is recommended to use client.sql.execute() instead of the old client.api.executeSql(). The old API is still available for backward compatibility.

// Recommended: Use client.sql namespace
const data = await client.sql.execute({
sqlCode: "fc8e7777-06e3847d"
});

// Check execution result
if (data.execSuccess && data.execResult) {
console.log(`Query successful, returned ${data.execResult.length} records`);
data.execResult.forEach((row) => {
console.log(row);
});
} else {
console.error("SQL execution failed");
}

// Parameterized query (prevent SQL injection)
const data = await client.sql.execute({
sqlCode: "fc8e7777-xxxxx",
params: {
userId: "123",
startDate: "2025-01-01",
endDate: "2025-12-31",
}
});

// Or use direct parameter passing (compatible)
const data = await client.sql.execute("fc8e7777-xxxxx", {
userId: "123",
startDate: "2025-01-01",
});

// Alias method (backward compatible, not recommended)
const data = await client.api.executeSql("fc8e7777-xxxxx", {
userId: "123",
});

Call BFF Backend Functions v1.3.0+​

Call BFF (Backend For Frontend) backend functions configured on the Lovrabet platform.

// Recommended: Use client.bff namespace
const result = await client.bff.execute({
scriptName: 'calculatePrice',
params: { productId: '123', quantity: 10 }
});

// With type hints
interface PriceResult {
originalPrice: number;
discountedPrice: number;
discount: number;
}

const price = await client.bff.execute<PriceResult>({
scriptName: 'calculatePrice',
params: { productId: '123', quantity: 10 }
});
console.log(`Original price: ${price.originalPrice}, Discounted price: ${price.discountedPrice}`);

// Alias method (backward compatible, not recommended)
const result = await client.api.bff('calculatePrice', {
productId: '123',
quantity: 10
});
Detailed Usage Guide

SQL API and BFF API support complete TypeScript types, error handling, and other advanced features. See:

🔍 Advanced Query Features​

Pagination Query​

// Basic pagination
const page1 = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: 1,
pageSize: 10,
});

// Complete loading for large datasets
const loadAllData = async () => {
let allData = [];
let currentPage = 1;
let hasMore = true;

while (hasMore) {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage,
pageSize: 100, // Use larger page size for efficiency
});

allData.push(...response.tableData);
hasMore = response.tableData.length === 100;
currentPage++;

// Avoid infinite loop
if (currentPage > 1000) break;
}

return allData;
};

Conditional Query​

// Multi-condition query
const activeUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
status: "active",
role: "admin",
createdAfter: "2024-01-01",
currentPage: 1,
pageSize: 50,
});

// Keyword search
const searchResults = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
keyword: "john", // Search for records containing "john"
pageSize: 20,
});

Sort Query v1.1.16+​

Use sortList parameter for single or multi-field sorting:

// Single field sort
const sortedUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(
{ currentPage: 1, pageSize: 20 },
[{ createTime: "desc" }] // Sort by creation time descending
);

// Multi-field sort
const complexSort = await client.models.dataset_f7e6d5c4b3a2901234567890fedcba98.filter(
{ currentPage: 1, pageSize: 50 },
[
{ priority: "desc" }, // First priority: sort by priority descending
{ createTime: "desc" }, // Second priority: sort by creation time descending
{ name: "asc" }, // Third priority: sort by name ascending
]
);

Advanced Filter Query (filter API) v1.1.22+​

The filter() method provides more powerful and flexible query capabilities than getList(), supporting complex condition combinations, range queries, fuzzy matching, and other advanced features.

Full Mode Support

Starting from v1.1.22, filter API supports both OpenAPI mode and WebAPI mode.

// Range query: Find users aged between 18-35
const youngUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
where: {
age: {
$gte: 18, // Greater than or equal to 18
$lte: 35, // Less than or equal to 35
},
},
pageSize: 50,
});

// Set matching: Find orders with specific statuses
const pendingOrders = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.filter({
where: {
status: { $in: ['pending', 'processing', 'confirmed'] },
},
sortList: [{ createTime: 'desc' }],
currentPage: 1,
pageSize: 20,
});

// Fuzzy search: Search users whose username contains specific keyword
const searchUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
where: {
username: { $contain: 'john' },
email: { $endWith: '@example.com' },
},
fields: ['id', 'username', 'email', 'createTime'],
});

// Complex condition combination: Use $and and $or
const complexQuery = await client.models.dataset_f7e6d5c4b3a2901234567890fedcba98.filter({
where: {
$and: [
{ price: { $gte: 100, $lte: 500 } },
{
$or: [
{ category: { $eq: 'electronics' } },
{ tags: { $contain: 'featured' } },
],
},
],
},
sortList: [{ price: 'asc' }],
pageSize: 30,
});

Advantages of filter over getList:

  • đŸŽ¯ Supports range queries ($gte, $lte)
  • 🔍 Supports fuzzy matching ($contain, $startWith, $endWith)
  • 🧮 Supports set operations ($in, $ne)
  • 🔗 Supports complex logic combinations ($and, $or)
  • 📋 Supports field filtering (fields) to reduce data transmission

For detailed operator descriptions and more examples, please refer to Filter API Complete Guide.

Complex Query Examples​

// Combined query: Get recently registered active users
const recentActiveUsers = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(
{
status: "active",
registeredAfter: new Date(
Date.now() - 30 * 24 * 60 * 60 * 1000
).toISOString(), // Within 30 days
currentPage: 1,
pageSize: 20,
},
[{ registeredAt: "desc" }] // Sort by registration time descending
);

// Get user statistics by specific role
const getUsersByRole = async (role: string) => {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
role,
pageSize: 1, // Only need total count
});

return {
role,
count: response.total,
users: response.tableData,
};
};

📈 Batch Operations​

Batch Create​

// Batch create users
const createBatchUsers = async (
users: Array<{ name: string; email: string }>
) => {
const results = [];

// Process in batches to avoid single request being too large
const batchSize = 10;
for (let i = 0; i < users.length; i += batchSize) {
const batch = users.slice(i, i + batchSize);

const batchResults = await Promise.all(
batch.map((user) => client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.create(user))
);

results.push(...batchResults);

// Avoid requests being too frequent
if (i + batchSize < users.length) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
}

return results;
};

Batch Update​

// Batch update user status
const batchUpdateStatus = async (userIds: string[], status: string) => {
const updates = userIds.map((id) =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.update(id, { status })
);

try {
const results = await Promise.all(updates);
console.log(`Successfully updated ${results.length} users`);
return results;
} catch (error) {
console.error("Batch update failed:", error);
throw error;
}
};
// Get user and their orders
const getUserWithOrders = async (userId: string) => {
// Get user information
const user = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getOne(userId);

// Get user's orders
const orders = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.filter({
userId: userId,
currentPage: 1,
pageSize: 50,
});

return {
user,
orders: orders.tableData,
totalOrders: orders.total,
};
};

// Get order and customer information
const getOrderWithCustomer = async (orderId: string) => {
const order = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.getOne(orderId);
const customer = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getOne(order.customerId);

return {
...order,
customer,
};
};

Aggregation Query Simulation​

// User order statistics
const getUserOrderStats = async (userId: string) => {
const orders = await client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.filter({
userId,
pageSize: 1000, // Get all orders
});

const stats = {
totalOrders: orders.total,
totalAmount: 0,
averageAmount: 0,
statusBreakdown: {} as Record<string, number>,
};

// Calculate statistics
orders.tableData.forEach((order) => {
stats.totalAmount += order.amount || 0;
stats.statusBreakdown[order.status] =
(stats.statusBreakdown[order.status] || 0) + 1;
});

stats.averageAmount =
stats.totalOrders > 0 ? stats.totalAmount / stats.totalOrders : 0;

return stats;
};

âš ī¸ Error Handling​

Basic Error Handling​

try {
const users = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();
console.log("Successfully fetched users:", users);
} catch (error) {
console.error("Failed to fetch users:", error);

// Handle based on error type
if (error.status === 404) {
console.log("Resource not found");
} else if (error.status === 403) {
console.log("Insufficient permissions");
} else if (error.status === 500) {
console.log("Server error");
}
}

Using safe Function (No try-catch)v1.3.0+​

import { safe } from "@lovrabet/sdk";

// Concise error handling
const { data, error } = await safe(() =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter()
);

if (error) {
console.error("Failed to fetch users:", error.message, error.description);
return;
}

console.log("Successfully fetched users:", data);

Concurrent request error handling:

const [usersResult, ordersResult] = await Promise.all([
safe(() => client.models.users.filter()),
safe(() => client.models.orders.filter()),
]);

if (usersResult.error) {
console.error("Failed to load users");
}
if (ordersResult.error) {
console.error("Failed to load orders");
}

// Use data
console.log("Users:", usersResult.data);
console.log("Orders:", ordersResult.data);

Advanced Error Handling​

// Request wrapper with retry
const withRetry = async <T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delay: number = 1000
): Promise<T> => {
let lastError: any;

for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error: any) {
lastError = error;

// Some errors don't need retry
if (
error.status === 400 ||
error.status === 401 ||
error.status === 403
) {
throw error;
}

if (attempt < maxRetries) {
console.log(`Request failed, retrying after ${delay}ms (${attempt}/${maxRetries})`);
await new Promise((resolve) => setTimeout(resolve, delay));
delay *= 2; // Exponential backoff
}
}
}

throw lastError;
};

// Use retry wrapper
const robustGetUsers = () => withRetry(() => client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter());

Request Status Management​

// Request status management class
class RequestManager {
private loadingStates = new Map<string, boolean>();
private errors = new Map<string, any>();

async executeRequest<T>(
key: string,
operation: () => Promise<T>
): Promise<T> {
this.loadingStates.set(key, true);
this.errors.delete(key);

try {
const result = await operation();
return result;
} catch (error) {
this.errors.set(key, error);
throw error;
} finally {
this.loadingStates.set(key, false);
}
}

isLoading(key: string): boolean {
return this.loadingStates.get(key) || false;
}

getError(key: string): any {
return this.errors.get(key);
}
}

// Usage example
const requestManager = new RequestManager();

const loadUsers = () =>
requestManager.executeRequest("users_list", () =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter()
);

đŸŽ¯ Performance Optimization​

Request Caching​

// Simple in-memory cache
class SimpleCache {
private cache = new Map<string, { data: any; timestamp: number }>();
private ttl = 5 * 60 * 1000; // 5 minute TTL

set(key: string, data: any) {
this.cache.set(key, { data, timestamp: Date.now() });
}

get(key: string): any | null {
const item = this.cache.get(key);
if (!item) return null;

if (Date.now() - item.timestamp > this.ttl) {
this.cache.delete(key);
return null;
}

return item.data;
}

clear() {
this.cache.clear();
}
}

const cache = new SimpleCache();

// Query function with caching
const getCachedUsers = async (params: any = {}) => {
const cacheKey = `users_${JSON.stringify(params)}`;

// Try to get from cache
let users = cache.get(cacheKey);
if (users) {
console.log("Returning data from cache");
return users;
}

// Cache miss, get from API
users = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(params);
cache.set(cacheKey, users);
console.log("Fetched data from API and cached");

return users;
};

Request Deduplication​

// Request deduplication and merging
class RequestDeduplicator {
private pendingRequests = new Map<string, Promise<any>>();

async request<T>(key: string, operation: () => Promise<T>): Promise<T> {
// If the same request is in progress, return that Promise directly
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key);
}

// Create new request
const promise = operation().finally(() => {
// Clear cache after request completes
this.pendingRequests.delete(key);
});

this.pendingRequests.set(key, promise);
return promise;
}
}

const deduplicator = new RequestDeduplicator();

// Use deduplicator
const getUsers = (params: any) =>
deduplicator.request(`users_${JSON.stringify(params)}`, () =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(params)
);

// Multiple calls with same parameters will be merged
Promise.all([
getUsers({ status: "active" }),
getUsers({ status: "active" }),
getUsers({ status: "active" }),
]); // Actually only makes one request

Pagination Optimization​

// Progressive loading
class InfiniteLoader {
private data: any[] = [];
private currentPage = 1;
private hasMore = true;
private loading = false;

async loadMore(pageSize: number = 20): Promise<any[]> {
if (this.loading || !this.hasMore) {
return this.data;
}

this.loading = true;

try {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: this.currentPage,
pageSize,
});

this.data.push(...response.tableData);
this.hasMore = response.tableData.length === pageSize;
this.currentPage++;

return this.data;
} finally {
this.loading = false;
}
}

reset() {
this.data = [];
this.currentPage = 1;
this.hasMore = true;
this.loading = false;
}

getData() {
return this.data;
}

isLoading() {
return this.loading;
}

canLoadMore() {
return this.hasMore && !this.loading;
}
}

📊 Data Processing Tools​

Data Transformation​

// Data transformation utility
const transformUserData = (rawUser: any) => ({
id: rawUser.id,
name: rawUser.name,
email: rawUser.email,
displayName: `${rawUser.name} (${rawUser.email})`,
isActive: rawUser.status === "active",
createdAt: new Date(rawUser.created_at),
avatar: rawUser.avatar_url || "/default-avatar.png",
});

// Batch transformation
const transformUserList = (response: any) => ({
users: response.tableData.map(transformUserData),
pagination: {
current: response.currentPage,
size: response.pageSize,
total: response.total,
pages: Math.ceil(response.total / response.pageSize),
},
});

// Usage
const getUsersForUI = async () => {
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter();
return transformUserList(response);
};

Data Validation​

// Data validation utility
const validateUserData = (userData: any) => {
const errors: string[] = [];

if (!userData.name || userData.name.trim().length === 0) {
errors.push("Name cannot be empty");
}

if (!userData.email || !/\S+@\S+\.\S+/.test(userData.email)) {
errors.push("Please enter a valid email address");
}

if (userData.age && (userData.age < 0 || userData.age > 120)) {
errors.push("Age must be between 0-120");
}

return {
isValid: errors.length === 0,
errors,
};
};

// Safe user creation
const createUserSafely = async (userData: any) => {
const validation = validateUserData(userData);

if (!validation.isValid) {
throw new Error(`Data validation failed: ${validation.errors.join(", ")}`);
}

return await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.create(userData);
};

📖 Next Steps​

After mastering API usage, you can continue learning:

❓ Frequently Asked Questions​

Q: How to handle large data volume queries?​

It's recommended to use pagination queries to avoid loading too much data at once:

// Recommended: Pagination query
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: 1,
pageSize: 50, // Adjust according to actual needs
});

// Not recommended: Get all data
// const allUsers = await loadAllData(); // May cause performance issues

Q: How to debug when API calls fail?​

Enable detailed error logging:

const client = createClient({
options: {
onError: (error) => {
console.group("API Error Details");
console.log("Message:", error.message);
console.log("Status:", error.status);
console.log("URL:", error.url);
console.log("Request Data:", error.requestData);
console.log("Response Data:", error.responseData);
console.groupEnd();
},
},
});

Q: How to implement optimistic updates?​

// Optimistic update example
const optimisticUpdate = async (userId: string, updates: any) => {
// 1. Update UI immediately (optimistic update)
updateUserInUI(userId, updates);

try {
// 2. Send API request
const updatedUser = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.update(userId, updates);

// 3. Update UI with server response data
updateUserInUI(userId, updatedUser);

return updatedUser;
} catch (error) {
// 4. If failed, rollback UI state
revertUserInUI(userId);
throw error;
}
};