From 1b78e5425a9297a29337ebd005a1007ab230ce08 Mon Sep 17 00:00:00 2001 From: David Snelling Date: Thu, 19 Jun 2025 16:03:04 -0700 Subject: [PATCH] **refactor: add async error handler utility and standardize route handlers** ### Changes: - Introduced `asyncHandler` utility to simplify error handling in asynchronous route handlers. - Updated all route definitions in `cloud-wrapper/src/routes.ts` to use the new `asyncHandler` wrapper: - Standardized error management across CRUD operations for nouns, verbs, and search functionality. - Replaced redundant `try-catch` blocks with the `asyncHandler` wrapper to streamline code. - Added `NextFunction` and adjusted TypeScript typings for improved clarity and error handling. ### Purpose: Refactored route handlers to enhance maintainability, readability, and consistency by centralizing asynchronous error handling with a reusable `asyncHandler` utility. This reduces redundancy and simplifies debugging for API routes. --- cloud-wrapper/src/routes.ts | 74 ++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/cloud-wrapper/src/routes.ts b/cloud-wrapper/src/routes.ts index ebdbfd36..735a498d 100644 --- a/cloud-wrapper/src/routes.ts +++ b/cloud-wrapper/src/routes.ts @@ -1,4 +1,4 @@ -import { Router, Request, Response } from 'express'; +import { Router, Request, Response, NextFunction, RequestHandler } from 'express'; import { BrainyData, NounType, VerbType } from '@soulcraft/brainy'; // Define a custom request type that includes the Brainy instance @@ -6,11 +6,17 @@ interface BrainyRequest extends Request { brainy: BrainyData; } +// Helper function to handle async route handlers +const asyncHandler = (fn: (req: BrainyRequest, res: Response, next: NextFunction) => Promise) => + (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req as BrainyRequest, res, next)).catch(next); + }; + export function setupRoutes() { const router = Router(); // Get database status - router.get('/status', async (req: BrainyRequest, res: Response) => { + router.get('/status', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { const status = await req.brainy.status(); res.status(200).json(status); @@ -18,13 +24,13 @@ export function setupRoutes() { console.error('Error getting status:', error); res.status(500).json({ error: 'Failed to get database status' }); } - }); + })); // Add a noun (entity) - router.post('/nouns', async (req: BrainyRequest, res: Response) => { + router.post('/nouns', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { const { text, metadata } = req.body; - + if (!text) { return res.status(400).json({ error: 'Text is required' }); } @@ -35,45 +41,45 @@ export function setupRoutes() { console.error('Error adding noun:', error); res.status(500).json({ error: 'Failed to add noun' }); } - }); + })); // Get a noun by ID - router.get('/nouns/:id', async (req: BrainyRequest, res: Response) => { + router.get('/nouns/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { const { id } = req.params; const noun = await req.brainy.get(id); - + if (!noun) { return res.status(404).json({ error: 'Noun not found' }); } - + res.status(200).json(noun); } catch (error) { console.error('Error getting noun:', error); res.status(500).json({ error: 'Failed to get noun' }); } - }); + })); // Update noun metadata - router.put('/nouns/:id', async (req: BrainyRequest, res: Response) => { + router.put('/nouns/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { const { id } = req.params; const { metadata } = req.body; - + if (!metadata) { return res.status(400).json({ error: 'Metadata is required' }); } - + await req.brainy.updateMetadata(id, metadata); res.status(200).json({ success: true }); } catch (error) { console.error('Error updating noun:', error); res.status(500).json({ error: 'Failed to update noun' }); } - }); + })); // Delete a noun - router.delete('/nouns/:id', async (req: BrainyRequest, res: Response) => { + router.delete('/nouns/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { const { id } = req.params; await req.brainy.delete(id); @@ -82,44 +88,44 @@ export function setupRoutes() { console.error('Error deleting noun:', error); res.status(500).json({ error: 'Failed to delete noun' }); } - }); + })); // Search for similar nouns - router.post('/search', async (req: BrainyRequest, res: Response) => { + router.post('/search', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { const { query, limit = 10 } = req.body; - + if (!query) { return res.status(400).json({ error: 'Query is required' }); } - + const results = await req.brainy.searchText(query, limit); res.status(200).json(results); } catch (error) { console.error('Error searching:', error); res.status(500).json({ error: 'Failed to search' }); } - }); + })); // Add a verb (relationship) - router.post('/verbs', async (req: BrainyRequest, res: Response) => { + router.post('/verbs', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { const { sourceId, targetId, metadata } = req.body; - + if (!sourceId || !targetId) { return res.status(400).json({ error: 'Source ID and Target ID are required' }); } - + await req.brainy.addVerb(sourceId, targetId, metadata || {}); res.status(201).json({ success: true }); } catch (error) { console.error('Error adding verb:', error); res.status(500).json({ error: 'Failed to add verb' }); } - }); + })); // Get all verbs - router.get('/verbs', async (req: BrainyRequest, res: Response) => { + router.get('/verbs', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { const verbs = await req.brainy.getAllVerbs(); res.status(200).json(verbs); @@ -127,10 +133,10 @@ export function setupRoutes() { console.error('Error getting verbs:', error); res.status(500).json({ error: 'Failed to get verbs' }); } - }); + })); // Get verbs by source - router.get('/verbs/source/:id', async (req: BrainyRequest, res: Response) => { + router.get('/verbs/source/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { const { id } = req.params; const verbs = await req.brainy.getVerbsBySource(id); @@ -139,10 +145,10 @@ export function setupRoutes() { console.error('Error getting verbs by source:', error); res.status(500).json({ error: 'Failed to get verbs by source' }); } - }); + })); // Get verbs by target - router.get('/verbs/target/:id', async (req: BrainyRequest, res: Response) => { + router.get('/verbs/target/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { const { id } = req.params; const verbs = await req.brainy.getVerbsByTarget(id); @@ -151,10 +157,10 @@ export function setupRoutes() { console.error('Error getting verbs by target:', error); res.status(500).json({ error: 'Failed to get verbs by target' }); } - }); + })); // Delete a verb - router.delete('/verbs/:id', async (req: BrainyRequest, res: Response) => { + router.delete('/verbs/:id', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { const { id } = req.params; await req.brainy.deleteVerb(id); @@ -163,10 +169,10 @@ export function setupRoutes() { console.error('Error deleting verb:', error); res.status(500).json({ error: 'Failed to delete verb' }); } - }); + })); // Clear all data - router.delete('/clear', async (req: BrainyRequest, res: Response) => { + router.delete('/clear', asyncHandler(async (req: BrainyRequest, res: Response, next: NextFunction) => { try { await req.brainy.clear(); res.status(200).json({ success: true }); @@ -174,7 +180,7 @@ export function setupRoutes() { console.error('Error clearing data:', error); res.status(500).json({ error: 'Failed to clear data' }); } - }); + })); return router; }