Service Layer Patterns¶
This document describes the standard patterns for implementing services in the app application. Our service architecture follows a layered approach with clear separation of concerns between base business logic, server-side operations, client-side operations, and WhatsApp bot integrations.
Overview¶
Services in our application follow a four-layer architecture pattern, with each layer having specific responsibilities:
- Base Layer (
base.ts) - Pure business logic and database CRUD operations - Server Layer (
server.ts) - Server-side operations with external services (AI, geocoding, etc.) - Client Layer (
client.ts) - Client-side operations for React components - Sana Layer (
sana.ts) - WhatsApp bot-specific integrations
Important: Services must use helper functions (databaseHelper, storageHelper, authHelper) instead of direct Supabase client calls. This provides abstraction and makes the codebase more maintainable.
Service Layer Architecture¶
Base Service Layer (base.ts)¶
Purpose: Pure business logic and database operations only. No external service calls.
Responsibilities:
- CRUD operations using databaseHelper
- Business logic and validation
- Data transformations
- Internal service calculations
Restrictions:
- ❌ NO external API calls (AI, geocoding, email, etc.)
- ❌ NO calls to other services' server layers
- ❌ NO side effects beyond database operations
- ❌ NO ServiceResponse pattern (return raw types)
- ❌ NO logging (no clientLogger, serverLogger, or console)
- ❌ NO error handling with try/catch (let errors bubble up)
- ✅ CAN call other base services
- ✅ CAN use helper functions (databaseHelper, storageHelper)
- ✅ Returns raw data types (T | null, boolean, string, etc.)
Example:
// packages/app/src/services/transaction/base.ts
export async function createTransaction(
data: TransactionCreateData,
client?: DatabaseClient
): Promise<Transaction | null> {
// Pure database operation - no external calls
// Returns raw type, not ServiceResponse
const transaction = await create<Transaction>(
DatabaseTable.TRANSACTIONS,
data,
{ client }
);
return transaction;
}
export async function updateTransactionStatus(
id: string,
status: string,
client?: DatabaseClient
): Promise<Transaction | null> {
// Business logic + database update only
// Returns raw type, not ServiceResponse
const updated = await update<Transaction>(
DatabaseTable.TRANSACTIONS,
id,
{ status },
{ client }
);
return updated;
}
export async function deleteTransaction(
id: string,
client?: DatabaseClient
): Promise<boolean> {
// Returns boolean for success/failure
return await deleteRecord(DatabaseTable.TRANSACTIONS, id, { client });
}
Server Service Layer (server.ts)¶
Purpose: Handle server-side operations that require external services or complex orchestration.
Responsibilities:
- AI service integrations
- Geocoding and mapping services
- Email and notification services
- Complex multi-service orchestrations
- Wrapping base operations with external enhancements
Patterns:
- ✅ CAN call its own base service
- ✅ CAN call other services' server layers
- ✅ CAN make external API calls
- ❌ SHOULD NOT import other services' base layers directly
Example:
// packages/app/src/services/transaction/server.ts
import { createTransaction as createTransactionBase } from './base';
import { createAddress } from '@/services/address/server'; // Server-to-server OK
import { aiHubClient } from '@/services/aiHub/server';
import serverLogger from '@/lib/logger/server';
export async function processOcrExtraction(
step: AIProcessingJobStep
): Promise<ServiceResponse<Transaction | null>> {
try {
const { result_data } = step;
// Create transaction using base layer (returns raw type)
const transaction = await createTransactionBase(data);
if (!transaction) {
throw new Error('Failed to create transaction');
}
if (result_data.address) {
// Call another service's server layer for AI-enhanced address creation
const addressResult = await createAddress({
...result_data.address,
transactionId: transaction.id,
});
if (addressResult.error) {
serverLogger.error('Failed to create address', addressResult.error);
}
}
return { data: transaction, error: null };
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
serverLogger.error('Error processing OCR extraction', err);
return { data: null, error: err };
}
}
Client Service Layer (client.ts)¶
Purpose: Client-side operations for React components.
Responsibilities:
- Data fetching for UI components
- Client-side state management helpers
- Browser-specific operations
- React Query integration helpers
Patterns:
- Uses client-side Supabase instance
- Optimized for React component consumption
- Handles client-side caching strategies
Example:
// packages/app/src/services/transaction/client.ts
import { fetchUserTransactions as fetchUserTransactionsBase } from './base';
import clientLogger from '@/lib/logger/client';
export async function fetchUserTransactions(
userId: string,
client?: DatabaseClient
): Promise<ServiceResponse<Transaction[]>> {
try {
// Call base layer which returns raw type
const transactions = await fetchUserTransactionsBase(userId, client);
return { data: transactions, error: null };
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
clientLogger.error('Error fetching user transactions', err, { userId });
return { data: null, error: err };
}
}
Sana Service Layer (sana.ts)¶
Purpose: WhatsApp bot-specific implementations.
Responsibilities:
- WhatsApp message handling
- Bot-specific business logic
- WhatsApp API integrations
- Conversation flow management
Patterns:
- Can use both base and server layers
- Handles WhatsApp-specific data formats
- Manages conversation state
Example:
// packages/app/src/services/transaction/sana.ts
import { createTransaction } from './base';
import { processWithAI } from './server';
import sanaLogger from '@/lib/logger/sana';
export async function handleTransactionUpload(
message: WhatsAppMessage,
fileUrl: string
): Promise<WhatsAppResponse> {
try {
// Create transaction (base returns raw type)
const transaction = await createTransaction(data);
if (!transaction) {
throw new Error('Failed to create transaction');
}
// Process with AI (server layer returns ServiceResponse)
const aiResult = await processWithAI(transaction.id, fileUrl);
if (aiResult.error) {
sanaLogger.error('AI processing failed', aiResult.error);
}
// Return WhatsApp-formatted response
return formatWhatsAppResponse(transaction);
} catch (error) {
sanaLogger.error('Error handling transaction upload', error as Error);
return createErrorResponse('Failed to process transaction');
}
}
Service Communication Patterns¶
Allowed Communication Flows¶
Client → Base (same service)
Client → Server (for external operations)
Server → Base (same service)
Server → Server (other services)
Sana → Base (any service)
Sana → Server (any service)
Base → Base (any service)
Prohibited Communication Flows¶
❌ Server → Other Base (use other's server layer instead)
❌ Base → Server (base should be pure)
❌ Base → External APIs (use server layer)
Example: Address Creation with AI Matching¶
// ❌ WRONG - Base layer calling external service
// packages/app/src/services/address/base.ts
export async function createAddress(data) {
const aiMatch = await callAIService(data); // ❌ NO external calls in base
return create(DatabaseTable.ADDRESSES, data);
}
// ✅ CORRECT - Server layer handles AI, base handles CRUD
// packages/app/src/services/address/server.ts
import { createAddress as createAddressBase } from './base';
import { findExistingAddresses } from './base';
import { aiHubClient } from '@/services/aiHub/server';
export async function createAddress(data) {
// Use AI to find potential duplicates
const existingAddresses = await findExistingAddresses();
const aiMatch = await aiHubClient.findAddressMatch(data, existingAddresses);
if (aiMatch) {
return { data: aiMatch, error: null };
}
// Geocode the new address
const geocoded = await geocodeAddress(data);
// Create using base layer
return createAddressBase({ ...data, ...geocoded });
}
Location and Naming¶
- Location:
/packages/app/src/services/[service-name]/ - File Structure:
services/ └── transaction/ ├── base.ts # Pure business logic ├── server.ts # Server-side with external services ├── client.ts # Client-side operations └── sana.ts # WhatsApp bot integration - Import Convention:
// Import specific layer import { createTransaction } from '@/services/transaction/base'; import { processWithAI } from '@/services/transaction/server';
Service Response Pattern¶
IMPORTANT: The ServiceResponse pattern is ONLY used in wrapper layers (client.ts, server.ts, sana.ts), NOT in base.ts.
Base Layer Returns¶
Base layer functions return raw data types:
// base.ts - Returns raw types
export async function getUser(id: string): Promise<User | null> {
return await findOne<User>(DatabaseTable.USERS, { id });
}
export async function deleteUser(id: string): Promise<boolean> {
return await deleteRecord(DatabaseTable.USERS, id);
}
Wrapper Layers Return ServiceResponse¶
Client, server, and sana layers wrap base functions with ServiceResponse:
interface ServiceResponse<T> {
data: T | null;
error: Error | null;
}
// client.ts - Wraps with ServiceResponse and adds logging
export async function getUser(
id: string
): Promise<ServiceResponse<User | null>> {
try {
const user = await getUserBase(id);
return { data: user, error: null };
} catch (error) {
const err = error as Error;
clientLogger.error('Failed to get user', err, { id });
return { data: null, error: err };
}
}
Error Handling¶
Base Layer - No Error Handling¶
Base layer functions let errors bubble up naturally:
// base.ts - No try/catch, no logging
import { findOne } from '@/lib/databaseHelper';
import { DatabaseTable } from '@/constants/database';
export async function fetchSomething(
id: string,
client?: DatabaseClient
): Promise<Something | null> {
// Just return the result directly
const data = await findOne<Something>(
DatabaseTable.SOMETHING,
{ id },
{
select: 'id, name, status',
client,
}
);
return data;
}
Wrapper Layers - Handle Errors¶
Client/server/sana layers add error handling and logging:
// client.ts - Adds error handling and logging
import { fetchSomething as fetchSomethingBase } from './base';
import clientLogger from '@/lib/logger/client';
export async function fetchSomething(
id: string,
client?: DatabaseClient
): Promise<ServiceResponse<Something | null>> {
try {
const data = await fetchSomethingBase(id, client);
if (!data) {
const error = new Error('Something not found');
clientLogger.error('Failed to fetch something', error, { id });
return { data: null, error };
}
return { data, error: null };
} catch (error) {
const err = error as Error;
clientLogger.error('Unexpected error fetching something', err, { id });
return { data: null, error: err };
}
}
Multi-Step Operations¶
Base Layer - Simple Multi-Step Logic¶
// base.ts - Multi-step without error handling
import { create } from '@/lib/databaseHelper';
import { uploadFile, deleteFile } from '@/lib/storageHelper';
import { DatabaseTable, StorageBucket } from '@/constants/database';
export async function createWithFile(
data: CreateData,
file: File,
client?: DatabaseClient
): Promise<Result | null> {
// Step 1: Upload file
const uploadResult = await uploadFile({
path: 'some/path/file.jpg',
file,
bucketName: StorageBucket.FILES,
contentType: 'image/jpeg',
database: client,
});
if (!uploadResult) {
return null;
}
// Step 2: Create database record
const record = await create<Result>(
DatabaseTable.SOME_TABLE,
{
...data,
file_path: uploadResult.path,
},
{ client }
);
if (!record) {
// Cleanup on failure
await deleteFile({
path: uploadResult.path,
bucketName: StorageBucket.FILES,
database: client,
});
return null;
}
return record;
}
Wrapper Layer - With Error Handling¶
// client.ts - Adds error handling and logging
import { createWithFile as createWithFileBase } from './base';
import clientLogger from '@/lib/logger/client';
export async function createWithFile(
data: CreateData,
file: File,
client?: DatabaseClient
): Promise<ServiceResponse<Result>> {
try {
const result = await createWithFileBase(data, file, client);
if (!result) {
const error = new Error('Failed to create record with file');
clientLogger.error('Creation failed', error);
return { data: null, error };
}
return { data: result, error: null };
} catch (error) {
const err = error as Error;
clientLogger.error('Unexpected error in createWithFile', err);
return { data: null, error: err };
}
}
File Upload Pattern¶
Base Layer - File Upload Logic¶
Base layer handles the core upload logic without ServiceResponse:
// base.ts - Pure upload logic
import { create, update } from '@/lib/databaseHelper';
import { uploadFile, deleteFile } from '@/lib/storageHelper';
import { DatabaseTable, StorageBucket } from '@/constants/database';
export async function uploadProjectFile(
file: File,
projectId: string,
userId: string,
client?: DatabaseClient
): Promise<{ attachmentId: string; storagePath: string } | null> {
// Generate unique filename
const timestamp = Date.now();
const sanitizedName = file.name.replace(/[^a-zA-Z0-9.-]/g, '_');
const storedFilename = `${projectId}_${timestamp}_${sanitizedName}`;
const storagePath = `projects/${projectId}/files/${storedFilename}`;
// Upload to storage
const uploadResult = await uploadFile({
path: storagePath,
file,
bucketName: StorageBucket.FILES,
contentType: file.type,
upsert: false,
database: client,
});
if (!uploadResult) {
return null;
}
// Create attachment record
const attachment = await create(
DatabaseTable.ATTACHMENTS,
{
parent_entity_type: 'project',
parent_entity_id: projectId,
file_name_original: file.name,
file_name_stored: storedFilename,
storage_path_or_url: `${StorageBucket.FILES}/${storagePath}`,
mime_type: file.type,
file_size_bytes: file.size,
uploaded_by_user_id: userId,
},
{ client, select: 'id' }
);
if (!attachment || !('id' in attachment)) {
// Cleanup on failure
await deleteFile({
path: storagePath,
bucketName: StorageBucket.FILES,
database: client,
});
return null;
}
// Update parent record
const updatedProject = await update(
DatabaseTable.PROJECTS,
projectId,
{ attachment_id: attachment.id as string },
{ client }
);
if (!updatedProject) {
// Note: Attachment remains but parent not updated
// Wrapper layer can decide how to handle this
return null;
}
return {
attachmentId: attachment.id as string,
storagePath: `${StorageBucket.FILES}/${storagePath}`,
};
}
Wrapper Layer - File Upload with Error Handling¶
// client.ts - Adds authentication, error handling, and logging
import { uploadProjectFile as uploadProjectFileBase } from './base';
import * as authHelper from '@/lib/authHelper';
import clientLogger from '@/lib/logger/client';
export async function uploadProjectFile(
file: File,
projectId: string
): Promise<ServiceResponse<{ attachmentId: string; storagePath: string }>> {
try {
// Get current user
const user = await authHelper.getCurrentUser();
if (!user) {
const error = new Error('User not authenticated');
clientLogger.error('Authentication required for upload', error);
return { data: null, error };
}
// Call base layer
const result = await uploadProjectFileBase(file, projectId, user.id);
if (!result) {
const error = new Error('Upload failed');
clientLogger.error('Failed to upload project file', error, { projectId });
return { data: null, error };
}
clientLogger.info('File uploaded successfully', {
projectId,
attachmentId: result.attachmentId,
});
return { data: result, error: null };
} catch (error) {
const err = error as Error;
clientLogger.error('Unexpected error uploading file', err, { projectId });
return { data: null, error: err };
}
}
Authentication in Services¶
IMPORTANT: Authentication checks should be done in wrapper layers (client/server/sana), NOT in base.ts.
Base Layer¶
Base functions receive userId as a parameter when needed:
// base.ts - Receives userId as parameter
export async function createUserProject(
userId: string,
projectData: ProjectData,
client?: DatabaseClient
): Promise<Project | null> {
return await create<Project>(
DatabaseTable.PROJECTS,
{
...projectData,
created_by_user_id: userId,
},
{ client }
);
}
Wrapper Layers¶
Wrapper layers handle authentication and pass userId to base:
// client.ts - Gets current user and passes to base
import * as authHelper from '@/lib/authHelper';
import { createUserProject as createUserProjectBase } from './base';
import clientLogger from '@/lib/logger/client';
export async function createUserProject(
projectData: ProjectData
): Promise<ServiceResponse<Project>> {
try {
// Get current user
const user = await authHelper.getCurrentUser();
if (!user) {
const error = new Error('User not authenticated');
clientLogger.error('Authentication required', error);
return { data: null, error };
}
// Pass userId to base layer
const project = await createUserProjectBase(user.id, projectData);
if (!project) {
const error = new Error('Failed to create project');
return { data: null, error };
}
clientLogger.info('Project created by user', {
userId: user.id,
projectId: project.id,
});
return { data: project, error: null };
} catch (error) {
const err = error as Error;
clientLogger.error('Error creating project', err);
return { data: null, error: err };
}
}
Testing Services¶
Mock Setup¶
// Mock helper dependencies
jest.mock('@/lib/logger/client');
jest.mock('@/lib/databaseHelper');
jest.mock('@/lib/storageHelper');
jest.mock('@/lib/authHelper');
// Import mocked helpers
const { findOne, create, update } = jest.requireActual(
'@/lib/__mocks__/databaseHelper'
);
const { uploadFile, deleteFile } = jest.requireActual(
'@/lib/__mocks__/storageHelper'
);
const authHelper = jest.requireActual('@/lib/__mocks__/authHelper');
Test Structure¶
import { findOne } from '@/lib/databaseHelper';
import { DatabaseTable } from '@/constants/database';
import { serviceMethod } from './serviceFile';
// Mock the helper functions
const mockFindOne = findOne as jest.MockedFunction<typeof findOne>;
describe('serviceName', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('methodName', () => {
it('should handle success case', async () => {
// Mock successful database response
const mockData = { id: '123', name: 'Test' };
mockFindOne.mockResolvedValue(mockData);
const result = await serviceMethod('123');
expect(result.data).toEqual(mockData);
expect(result.error).toBeNull();
expect(mockFindOne).toHaveBeenCalledWith(
DatabaseTable.SOMETHING,
{ id: '123' },
expect.objectContaining({
select: expect.any(String),
})
);
});
it('should handle not found case', async () => {
// Mock database returning null (not found)
mockFindOne.mockResolvedValue(null);
const result = await serviceMethod('nonexistent');
expect(result.data).toBeNull();
expect(result.error).toBeInstanceOf(Error);
expect(result.error?.message).toBe('Something not found');
expect(clientLogger.error).toHaveBeenCalled();
});
it('should handle database error', async () => {
// Mock database throwing error
const dbError = new Error('Database connection failed');
mockFindOne.mockRejectedValue(dbError);
const result = await serviceMethod('123');
expect(result.data).toBeNull();
expect(result.error).toBe(dbError);
expect(clientLogger.error).toHaveBeenCalledWith(
'Unexpected error fetching something',
dbError
);
});
});
});
Type Safety in Tests¶
Avoid any types:
// ❌ Bad - Using any type
const mockAuthHelper = {
getCurrentUser: jest.fn().mockResolvedValue(mockUser),
} as any;
// ✅ Good - Proper typing
const mockAuthHelper = {
getCurrentUser: jest.fn().mockResolvedValue(mockUser),
isAuthenticated: jest.fn().mockResolvedValue(true),
} as typeof authHelper;
Example Service Implementation¶
Here's a complete example following all patterns:
Base Layer (base.ts)¶
import { findOne, findMany, create, update } from '@/lib/databaseHelper';
import { DatabaseTable } from '@/constants/database';
import { ProjectWithRelations, CreateProjectData } from '@/types/project';
import { DatabaseClient } from '@/types/database';
export async function getProject(
projectId: string,
client?: DatabaseClient
): Promise<ProjectWithRelations | null> {
const project = await findOne<ProjectWithRelations>(
DatabaseTable.PROJECTS,
{ id: projectId },
{
select: `
id,
project_code,
title,
status,
poster_attachment:poster_attachment_id (
storage_path_or_url
)
`,
client,
}
);
return project;
}
export async function createProject(
data: CreateProjectData,
client?: DatabaseClient
): Promise<ProjectWithRelations | null> {
const project = await create<ProjectWithRelations>(
DatabaseTable.PROJECTS,
data,
{ client }
);
return project;
}
export async function getUserProjects(
userId: string,
client?: DatabaseClient
): Promise<ProjectWithRelations[]> {
const projects = await findMany<ProjectWithRelations>(
DatabaseTable.PROJECTS,
{ created_by_user_id: userId },
{
orderBy: { column: 'created_at', ascending: false },
client,
}
);
return projects || [];
}
Client Layer (client.ts)¶
import * as projectBase from './base';
import clientLogger from '@/lib/logger/client';
import { ServiceResponse } from '@/types/database';
import { ProjectWithRelations, CreateProjectData } from '@/types/project';
export async function getProject(
projectId: string
): Promise<ServiceResponse<ProjectWithRelations | null>> {
try {
clientLogger.debug('Fetching project', { projectId });
const project = await projectBase.getProject(projectId);
if (!project) {
const error = new Error('Project not found');
clientLogger.error('Failed to fetch project', error, { projectId });
return { data: null, error };
}
clientLogger.info('Project fetched successfully', {
projectId: project.id,
projectCode: project.project_code,
});
return { data: project, error: null };
} catch (error) {
const err = error as Error;
clientLogger.error('Unexpected error fetching project', err, { projectId });
return { data: null, error: err };
}
}
export async function createProject(
data: CreateProjectData
): Promise<ServiceResponse<ProjectWithRelations>> {
try {
const project = await projectBase.createProject(data);
if (!project) {
const error = new Error('Failed to create project');
clientLogger.error('Project creation failed', error);
return { data: null, error };
}
clientLogger.info('Project created', { projectId: project.id });
return { data: project, error: null };
} catch (error) {
const err = error as Error;
clientLogger.error('Error creating project', err);
return { data: null, error: err };
}
}
Server Layer (server.ts)¶
import * as projectBase from './base';
import { sendNotification } from '@/services/notification/server';
import serverLogger from '@/lib/logger/server';
import { ServiceResponse } from '@/types/database';
import { CreateProjectData, ProjectWithRelations } from '@/types/project';
export async function createProjectWithNotification(
data: CreateProjectData,
notifyUsers: string[]
): Promise<ServiceResponse<ProjectWithRelations>> {
try {
// Create project using base layer
const project = await projectBase.createProject(data);
if (!project) {
throw new Error('Failed to create project');
}
// Send notifications (external service)
for (const userId of notifyUsers) {
const notifyResult = await sendNotification({
userId,
type: 'project_created',
data: { projectId: project.id, projectName: project.title },
});
if (notifyResult.error) {
serverLogger.warn('Failed to notify user', {
userId,
error: notifyResult.error,
});
}
}
serverLogger.info('Project created with notifications', {
projectId: project.id,
notifiedUsers: notifyUsers.length,
});
return { data: project, error: null };
} catch (error) {
const err = error as Error;
serverLogger.error('Error in createProjectWithNotification', err);
return { data: null, error: err };
}
}
Best Practices¶
- Layer Separation - Keep base layer pure, server layer for external calls
- Use helper functions - Never use Supabase client directly, always use databaseHelper, storageHelper, authHelper
- Always log errors with context using
clientLogger.error() - Never throw errors - always return them in the response object
- Include relevant context in error logs for debugging
- Handle cleanup for multi-step operations
- Test all paths - success, errors, and edge cases
- Use TypeScript strictly - no
anytypes - Document complex logic with clear comments
- Keep services focused - one service per domain area
- Import from constants - Use DatabaseTable and StorageBucket enums for consistency
- Service boundaries - Respect layer responsibilities and communication patterns
- Idempotency - Design operations to be safely retryable
- Transaction boundaries - Keep database transactions within base layer
Migration Guide¶
When refactoring existing code to use current service patterns:
- Separate concerns into appropriate layers:
- Move pure CRUD operations to
base.ts - Move external service calls to
server.ts - Keep client-specific logic in
client.ts -
Extract WhatsApp logic to
sana.ts -
Replace direct Supabase calls with helper function calls:
supabaseClient.from().select()→findOne()orfindMany()supabaseClient.from().insert()→create()supabaseClient.from().update()→update()supabaseClient.storage.from().upload()→uploadFile()-
supabaseClient.auth.getUser()→authHelper.getCurrentUser() -
Update service-to-service communication:
- Change
import from './otherService/base'toimport from './otherService/server' -
Move external API calls from base to server layer
-
Update imports to use helper functions and constants
- Replace direct error handling with ServiceResponse pattern
- Update tests to mock helper functions instead of Supabase client
- Add comprehensive tests for each service layer
- Update error boundaries if needed
Helper Function Reference¶
Database Helper Functions¶
import {
findOne,
findMany,
create,
update,
remove,
ServiceResponse,
} from '@/lib/databaseHelper';
import { DatabaseTable } from '@/constants/database';
// Find single record
const user = await findOne<User>(DatabaseTable.USERS, { id: 'user-id' });
// Find multiple records with conditions
const projects = await findMany<Project>(
DatabaseTable.PROJECTS,
{ status: 'active' },
{ orderBy: { column: 'created_at', ascending: false } }
);
// Create new record
const newProject = await create<Project>(DatabaseTable.PROJECTS, {
title: 'New Project',
status: 'draft',
});
// Update existing record
const updatedProject = await update(DatabaseTable.PROJECTS, 'project-id', {
status: 'active',
});
Storage Helper Functions¶
import { uploadFile, deleteFile, getStorageUrl } from '@/lib/storageHelper';
import { StorageBucket } from '@/constants/database';
// Upload file
const uploadResult = await uploadFile({
path: 'projects/poster.jpg',
file: fileObject,
bucketName: StorageBucket.FILES,
contentType: 'image/jpeg',
});
// Delete file
const deleteSuccess = await deleteFile({
path: 'projects/poster.jpg',
bucketName: StorageBucket.FILES,
});
// Get file URL
const fileUrl = await getStorageUrl({
path: 'projects/poster.jpg',
bucketName: StorageBucket.FILES,
});
Auth Helper Functions¶
import * as authHelper from '@/lib/authHelper';
// Get current user
const user = await authHelper.getCurrentUser();
// Check authentication status
const isAuth = await authHelper.isAuthenticated();
Future Considerations¶
- Consider adding request caching for frequently accessed data
- Implement retry logic for transient failures in helper functions
- Add request debouncing for user-triggered operations
- Consider service composition for complex workflows
- Enhance helper functions with more advanced query capabilities