API Usage Guide
This document provides a detailed guide to the Lovrabet SDK API usage, including CRUD operations, query parameters, response handling, and other core features.
Before you begin, make sure you have completed the SDK configuration. It is recommended to use the CLI to auto-generate configuration.
The examples below use the standard pattern client.models.dataset_xxx. You can also use the alias pattern 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 condition
keyword: "search-term", // Keyword search
});
// Response structure
console.log(response.tableData); // Data list
console.log(response.total); // Total record count
console.log(response.currentPage); // Current page
console.log(response.pageSize); // Page size
Get a Single Recordâ
const user = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getOne("user-id");
console.log(user); // User detail object
Create a 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);
Starting from v1.1.14, OpenAPI mode also supports the create() operation. Previous versions only supported WebAPI mode.
Update a Recordâ
const updatedUser = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.update("user-id", {
name: "Jane Smith",
email: "jane.smith@example.com",
});
console.log("Updated user:", updatedUser);
Starting from v1.1.14, OpenAPI mode also supports the update() operation. Previous versions only supported WebAPI mode.
Delete a Recordâ
await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.delete("user-id");
console.log("User deleted");
The delete() operation is only supported in WebAPI mode (Cookie authentication). OpenAPI mode does not support delete operations yet.
Export Excel v1.1.24+â
Export a dataset as an Excel file, returning 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 condition filtering
const fileUrl = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.excelExport({
status: 'active',
createTime: '2025-01-01'
});
window.open(fileUrl, '_blank');
The excelExport() operation is only supported in WebAPI mode (Cookie authentication). OpenAPI mode does not support this feature yet.
Get Dropdown Options v1.1.18+â
Used to fetch dropdown option data from a data table, suitable for form components like Select, Radio, and Checkbox.
getSelectOptions() allows a data table to provide its own data as dropdown options, rather than querying other tables.
Correct Understanding:
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions()- Queries the users table's own data to generate user list options- These options can be used in: "select user" field in the orders table, "select author" field in the articles table, etc.
Common Misunderstanding:
- Incorrect: Calling
client.models.dataset_a1b2c3d4e5f6789012345678abcdef12.getSelectOptions()from the orders table to get the user list - Correct: Calling
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getSelectOptions()to get the user list
Core Principle: Whose data it is, that model provides the options
This embodies important software design principles:
- Single Responsibility Principle (SRP) - Each model is only responsible for managing and providing its own data
- Dependency Inversion Principle (DIP) - The orders table depends on the interface provided by the users table, rather than direct coupling
- Separation of Concerns (SoC) - Clear separation between data provider and consumer responsibilities
- Service Provider Pattern - Tables act as data sources, providing standardized data services externally
This design makes the system easier to maintain, extend, and test.
// Get dropdown options from the users table (query the 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
});
// Standard return format
console.log(userOptions);
// [
// { label: 'Zhang San', value: 'user001' },
// { label: 'Li Si', value: 'user002' },
// { label: 'Wang Wu', value: 'user003' }
// ]
// Usage 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>
);
}
Practical Use Cases:
// Scenario 1: Select a user in an order form
// Call the 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 a category in an article form
// Call the 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 a department in an employee form
// Call the 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 a supplier in a product form
// Call the 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: For data from which table, call that table's getSelectOptions()
The getSelectOptions() operation is only supported in WebAPI mode (Cookie authentication). OpenAPI mode does not support this feature yet.
Execute Custom SQL v1.1.19+â
Execute custom SQL queries configured on the Lovrabet platform, suitable for complex statistical and aggregate query scenarios.
Starting from v1.1.19, it is recommended to use client.sql.execute() instead of the legacy client.api.executeSql(). The legacy API remains 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 (prevents 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 pattern (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 pattern (backward compatible, not recommended)
const result = await client.api.bff('calculatePrice', {
productId: '123',
quantity: 10
});
The SQL API and BFF API support full TypeScript typing, error handling, and other advanced features. See:
đ Advanced Query Featuresâ
Paginated Queryâ
// Basic pagination
const page1 = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: 1,
pageSize: 10,
});
// Full 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 a larger page size for efficiency
});
allData.push(...response.tableData);
hasMore = response.tableData.length === 100;
currentPage++;
// Prevent 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,
});
Sorted Query v1.1.16+â
Use the sortList parameter for single-field 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.
Starting from v1.1.22, the filter API supports both OpenAPI mode and WebAPI mode.
// Range query: find users aged 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 for users whose username contains a 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: using $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:
- Range queries (
$gte,$lte) - Fuzzy matching (
$contain,$startWith,$endWith) - Set operations (
$in,$ne) - Complex logical combinations (
$and,$or) - Field selection (
fields) to reduce data transfer
For detailed operator descriptions and more examples, see the 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 the 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 oversized requests
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 overly frequent requests
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;
}
};
đ Related Data Queriesâ
Get Related Dataâ
// Get a user and their orders
const getUserWithOrders = async (userId: string) => {
// Get user info
const user = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.getOne(userId);
// Get the 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 with customer info
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,
};
};
Aggregate 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-catch Pattern)â
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 the 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);
Error Handling for Concurrent Requests:
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 the 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 should not be retried
if (
error.status === 400 ||
error.status === 401 ||
error.status === 403
) {
throw error;
}
if (attempt < maxRetries) {
console.log(`Request failed, retrying in ${delay}ms (${attempt}/${maxRetries})`);
await new Promise((resolve) => setTimeout(resolve, delay));
delay *= 2; // Exponential backoff
}
}
}
throw lastError;
};
// Using the retry wrapper
const robustGetUsers = () => withRetry(() => client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter());
Request State Managementâ
// Request state 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();
// Cached query function
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, fetch 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 already in progress, return that Promise
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key);
}
// Create new request
const promise = operation().finally(() => {
// Clear from cache after request completes
this.pendingRequests.delete(key);
});
this.pendingRequests.set(key, promise);
return promise;
}
}
const deduplicator = new RequestDeduplicator();
// Using the deduplicator
const getUsers = (params: any) =>
deduplicator.request(`users_${JSON.stringify(params)}`, () =>
client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter(params)
);
// Multiple calls with the same parameters will be merged
Promise.all([
getUsers({ status: "active" }),
getUsers({ status: "active" }),
getUsers({ status: "active" }),
]); // Only one actual request will be made
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 and 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â
Now that you have mastered API usage, you can continue learning:
- TypeScript Support - Type-safe development experience
- Advanced Features - Multi-project support and performance optimization
- Practical Examples - React/Vue integration examples
â FAQâ
Q: How to handle queries with large amounts of data?â
It is recommended to use paginated queries and avoid loading too much data at once:
// Recommended: paginated query
const response = await client.models.dataset_8d2dcbae08b54bdd84c00be558ed48df.filter({
currentPage: 1,
pageSize: 50, // Adjust based on actual needs
});
// Not recommended: fetch 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. Immediately update UI (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;
}
};