10 KiB
10 KiB
Coder Subagent Reference
Quick Navigation
| Category | Key Files |
|---|---|
| Routes | src/routes/*.routes.ts |
| Services | src/services/*.server.ts (backend), src/services/*.ts (shared) |
| Repositories | src/services/db/*.db.ts |
| Types | src/types.ts, src/types/*.ts |
| Schemas | src/schemas/*.schemas.ts |
| Config | src/config/env.ts |
| Utils | src/utils/*.ts |
Architecture Patterns (ADR Summary)
Layer Flow
Route → validateRequest(schema) → Service → Repository → Database
↓
External APIs
Repository Naming Convention (ADR-034)
| Prefix | Behavior | Return |
|---|---|---|
get* |
Throws NotFoundError if missing |
Entity |
find* |
Returns null if missing |
Entity | null |
list* |
Returns empty array if none | Entity[] |
create* |
Creates new record | Entity |
update* |
Updates existing | Entity |
delete* |
Removes record | void |
Error Handling (ADR-001)
import { handleDbError, NotFoundError } from '../services/db/errors.db';
import { logger } from '../services/logger.server';
// Repository pattern
async function getById(id: string): Promise<Entity> {
try {
const result = await pool.query('SELECT * FROM table WHERE id = $1', [id]);
if (result.rows.length === 0) throw new NotFoundError('Entity not found');
return result.rows[0];
} catch (error) {
handleDbError(error, logger, 'Failed to get entity', { id });
}
}
API Response Helpers (ADR-028)
import {
sendSuccess,
sendPaginated,
sendError,
sendNoContent,
sendMessage,
ErrorCode,
} from '../utils/apiResponse';
// Success with data
sendSuccess(res, data); // 200
sendSuccess(res, data, 201); // 201 Created
// Paginated
sendPaginated(res, items, { page, limit, total });
// Error
sendError(res, ErrorCode.NOT_FOUND, 'User not found', 404);
sendError(res, ErrorCode.VALIDATION_ERROR, 'Invalid input', 400, validationErrors);
// No content / Message
sendNoContent(res); // 204
sendMessage(res, 'Password updated');
Transaction Pattern (ADR-002)
import { withTransaction } from '../services/db/connection.db';
const result = await withTransaction(async (client) => {
await client.query('INSERT INTO a ...');
await client.query('INSERT INTO b ...');
return { success: true };
});
Adding New Features
New API Endpoint Checklist
-
Schema (
src/schemas/{domain}.schemas.ts)import { z } from 'zod'; export const createEntitySchema = z.object({ body: z.object({ name: z.string().min(1) }), }); -
Route (
src/routes/{domain}.routes.ts)import { validateRequest } from '../middleware/validation.middleware'; import { createEntitySchema } from '../schemas/{domain}.schemas'; router.post('/', validateRequest(createEntitySchema), async (req, res, next) => { try { const result = await entityService.create(req.body); sendSuccess(res, result, 201); } catch (error) { next(error); } }); -
Service (
src/services/{domain}Service.server.ts)export async function create(data: CreateInput): Promise<Entity> { // Business logic here return repository.create(data); } -
Repository (
src/services/db/{domain}.db.ts)export async function create(data: CreateInput, client?: PoolClient): Promise<Entity> { const pool = client || getPool(); try { const result = await pool.query('INSERT INTO ...', [data.name]); return result.rows[0]; } catch (error) { handleDbError(error, logger, 'Failed to create entity', { data }); } }
New Background Job Checklist
-
Queue (
src/services/queues.server.ts)export const myQueue = new Queue('my-queue', { connection: redisConnection }); -
Worker (
src/services/workers.server.ts)new Worker( 'my-queue', async (job) => { // Process job }, { connection: redisConnection }, ); -
Trigger (in service)
await myQueue.add('job-name', { data });
Key Files Reference
Database Repositories
| Repository | Purpose | Path |
|---|---|---|
flyer.db.ts |
Flyer CRUD, processing status | src/services/db/flyer.db.ts |
store.db.ts |
Store management | src/services/db/store.db.ts |
user.db.ts |
User accounts | src/services/db/user.db.ts |
shopping.db.ts |
Shopping lists, watchlists | src/services/db/shopping.db.ts |
gamification.db.ts |
Achievements, points | src/services/db/gamification.db.ts |
category.db.ts |
Item categories | src/services/db/category.db.ts |
price.db.ts |
Price history, comparisons | src/services/db/price.db.ts |
recipe.db.ts |
Recipe management | src/services/db/recipe.db.ts |
Services
| Service | Purpose | Path |
|---|---|---|
flyerProcessingService.server.ts |
Orchestrates flyer AI extraction | src/services/flyerProcessingService.server.ts |
flyerAiProcessor.server.ts |
Gemini AI integration | src/services/flyerAiProcessor.server.ts |
cacheService.server.ts |
Redis caching | src/services/cacheService.server.ts |
queues.server.ts |
BullMQ queue definitions | src/services/queues.server.ts |
workers.server.ts |
BullMQ workers | src/services/workers.server.ts |
emailService.server.ts |
Nodemailer integration | src/services/emailService.server.ts |
geocodingService.server.ts |
Address geocoding | src/services/geocodingService.server.ts |
Routes
| Route | Base Path | Auth Required |
|---|---|---|
flyer.routes.ts |
/api/flyers |
Mixed |
store.routes.ts |
/api/stores |
Mixed |
user.routes.ts |
/api/users |
Yes |
auth.routes.ts |
/api/auth |
No |
admin.routes.ts |
/api/admin |
Admin only |
deals.routes.ts |
/api/deals |
No |
health.routes.ts |
/api/health |
No |
Error Types
| Error Class | HTTP Status | Use Case |
|---|---|---|
NotFoundError |
404 | Resource not found |
ForbiddenError |
403 | Access denied |
ValidationError |
400 | Input validation failed |
UniqueConstraintError |
409 | Duplicate record |
ForeignKeyConstraintError |
400 | Referenced record missing |
NotNullConstraintError |
400 | Required field null |
Import: import { NotFoundError, ... } from '../services/db/errors.db'
Middleware
| Middleware | Purpose | Usage |
|---|---|---|
validateRequest(schema) |
Zod validation | router.post('/', validateRequest(schema), handler) |
requireAuth |
JWT authentication | router.get('/', requireAuth, handler) |
requireAdmin |
Admin role check | router.delete('/', requireAuth, requireAdmin, handler) |
fileUpload |
Multer file handling | router.post('/upload', fileUpload.single('file'), handler) |
Type Definitions
| File | Contains |
|---|---|
src/types.ts |
Main types: User, Flyer, FlyerItem, Store, etc. |
src/types/api.ts |
API response envelopes, pagination |
src/types/auth.ts |
Auth-related types |
src/types/gamification.ts |
Achievement types |
Naming Conventions (ADR-027)
| Context | Convention | Example |
|---|---|---|
| AI output types | Ai* prefix |
AiFlyerItem, AiExtractionResult |
| Database types | Db* prefix |
DbFlyer, DbUser |
| API types | No prefix | Flyer, User |
| Schema validation | *Schema suffix |
createFlyerSchema |
| Routes | *.routes.ts |
flyer.routes.ts |
| Repositories | *.db.ts |
flyer.db.ts |
| Server services | *.server.ts |
aiService.server.ts |
| Client services | *.client.ts |
logger.client.ts |