Skip to main content

Syntax Sugar

This document shows how to use the safe and sqlSafe syntax sugar provided by the SDK to simplify error handling code.

Version Requirements
  • safe function requires @lovrabet/sdk v1.3.0 or higher
  • sqlSafe function requires @lovrabet/sdk v1.3.0 or higher

Traditional Approach: Verbose and Error-Prone

Scenario 1: Regular API Calls

// 📌 Traditional approach: Two-level nesting
try {
const users = await client.models.users.filter();
console.log(users);
} catch (error) {
if (error instanceof LovrabetError) {
console.error(error.message, error.description);
} else {
console.error(error);
}
}

Problems:

  • try-catch adds nesting levels
  • Manual error type checking required
  • Large code volume, poor readability

Scenario 2: SQL Queries

// 📌 Traditional approach: Three-level checking
try {
const result = await client.sql.execute({ sqlCode: "xxx" });

// Business layer check
if (result.execSuccess && result.execResult) {
console.log(`Found ${result.execResult.length} records`);
result.execResult.forEach((row) => console.log(row));
} else {
console.error("SQL execution failed");
}
} catch (error) {
if (error instanceof LovrabetError) {
console.error("Request failed:", error.message);
}
}

Problems:

  • HTTP errors + business logic errors, two-level checking
  • Nested access to execResult
  • Code duplication, easy to miss checks

Scenario 3: Concurrent Requests

// 📌 Traditional approach: Scattered error handling
const [users, orders, stats] = await Promise.all([
client.models.users.filter().catch((e) => ({ error: e })),
client.models.orders.filter().catch((e) => ({ error: e })),
client.sql.execute({ sqlCode: "stats" }).catch((e) => ({ error: e })),
]);

if (users.error) console.error("Failed to get users");
if (orders.error) console.error("Failed to get orders");
if (stats.error) {
console.error("Failed to get statistics");
} else {
if (!stats.data.execSuccess) {
console.error("SQL execution failed");
}
}

Problems:

  • Each request needs separate error handling
  • SQL business status checking is more complex
  • Inconsistent code structure

Using safe: Simplify Regular API Error Handling

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

// 💡 Syntax sugar: Single check
const { data, error } = await safe(() => client.models.users.filter());

if (error) {
console.error("Query failed:", error.message, error.description);
return;
}

// data is directly the result data
console.log(data);

Comparison:

Traditionalsafe
try-catch nestingFlat destructuring
Manual error type checkingAutomatically converts to LovrabetError
5-10 lines of code3 lines of code

Concurrent Requests: Unified Structure

// ✅ Concise: Unified handling
const [usersResult, ordersResult, statsResult] = await Promise.all([
safe(() => client.models.users.filter()),
safe(() => client.models.orders.filter()),
safe(() => client.sql.execute({ sqlCode: "stats" })),
]);

// Check individually
if (usersResult.error) console.error("Users failed");
if (ordersResult.error) console.error("Orders failed");
if (statsResult.error) console.error("Stats failed");

// Use successful data
usersResult.data?.forEach((user) => console.log(user));

Using sqlSafe: Optimized for SQL

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

// ✅ Concise: Single check, directly get array
const { data, error } = await sqlSafe(() =>
client.sql.execute({ sqlCode: "user-stats" })
);

if (!error) {
// data is directly the query result array
console.log(`Found ${data.length} records`);
data.forEach((row) => console.log(row));
}

Comparison:

TraditionalsqlSafe
try-catch + execSuccess checkSingle if check
Access result.execResultDirect data access
10-15 lines of code5 lines of code

Typed SQL Queries

interface UserStat {
id: number;
name: string;
login_count: number;
}

const { data, error } = await sqlSafe<UserStat>(() =>
client.sql.execute<UserStat>({ sqlCode: "user-stats" })
);

if (error) return;

// data is UserStat[], type-safe
data.forEach((stat) => {
console.log(`${stat.name}: ${stat.login_count} logins`);
});

Syntax Sugar Comparison Table

ScenarioTraditional (Lines of Code)Syntax Sugar (Lines of Code)
Regular API error handling8-10 lines3 lines
Complete SQL query handling15-20 lines5 lines
Concurrent requests30+ lines10 lines
Type-safe accessRequires type assertionsAuto-inferred

Best Practices

1. Prefer Syntax Sugar

// 💡 Syntax sugar: Concise
const { data, error } = await safe(() => api.call());
if (error) return;

// 📌 Traditional: Verbose
try {
const data = await api.call();
} catch (e) {
if (e instanceof LovrabetError) {
// ...
}
}

2. SQL Must Use sqlSafe

// 💡 Syntax sugar: Single check
const { data, error } = await sqlSafe(() => client.sql.execute(...));

// ❌ Avoid: Easy to miss business checks
const result = await client.sql.execute(...);
result.execResult?.forEach(...); // May have execSuccess=false

3. Early Return Pattern

const fetchUsers = async () => {
const { data, error } = await safe(() => client.models.users.filter());
if (error) return { success: false, error: error.message };
return { success: true, data };
};

4. Unified Concurrent Request Handling

const loadDashboard = async () => {
const [users, orders, stats] = await Promise.all([
sqlSafe(() => client.sql.execute({ sqlCode: "users" })),
sqlSafe(() => client.sql.execute({ sqlCode: "orders" })),
sqlSafe(() => client.sql.execute({ sqlCode: "stats" })),
]);

if (users.error || orders.error || stats.error) {
console.error("Data loading failed");
return;
}

return {
users: users.data!,
orders: orders.data!,
stats: stats.data!,
};
};

API Reference

safe

function safe<T>(fn: Promise<T> | (() => Promise<T>)): Promise<SafeResult<T>>;

interface SafeResult<T> {
data: T | null;
error: LovrabetError | null;
}

sqlSafe

function sqlSafe<T>(
fn: Promise<SqlExecuteResult<T>> | (() => Promise<SqlExecuteResult<T>>)
): Promise<SqlSafeResult<T>>;

interface SqlSafeResult<T> {
data: T[] | null;
error: LovrabetError | null;
}