281 lines
11 KiB
Markdown
281 lines
11 KiB
Markdown
# Handy Server - Development Guidelines
|
|
|
|
This document contains the development guidelines and instructions for the Happy Server project. This guide OVERRIDES any default behaviors and MUST be followed exactly.
|
|
|
|
## Project Overview
|
|
|
|
**Name**: happy-server
|
|
**Repository**: https://github.com/slopus/happy-server.git
|
|
**License**: MIT
|
|
**Language**: TypeScript
|
|
**Runtime**: Node.js 20
|
|
**Framework**: Fastify with opinionated architecture
|
|
|
|
## Core Technology Stack
|
|
|
|
- **Runtime**: Node.js 20
|
|
- **Language**: TypeScript (strict mode enabled)
|
|
- **Web Framework**: Fastify 5
|
|
- **Database**: PostgreSQL with Prisma ORM
|
|
- **Validation**: Zod
|
|
- **HTTP Client**: Axios
|
|
- **Real-time**: Socket.io
|
|
- **Cache/Pub-Sub**: Redis (via ioredis)
|
|
- **Testing**: Vitest
|
|
- **Package Manager**: Yarn (not npm)
|
|
|
|
## Development Environment
|
|
|
|
### Commands
|
|
- `yarn build` - TypeScript type checking
|
|
- `yarn start` - Start the server
|
|
- `yarn test` - Run tests
|
|
- `yarn migrate` - Run Prisma migrations
|
|
- `yarn generate` - Generate Prisma client
|
|
- `yarn db` - Start local PostgreSQL in Docker
|
|
|
|
### Environment Requirements
|
|
- FFmpeg installed (for media processing)
|
|
- Python3 installed
|
|
- PostgreSQL database
|
|
- Redis (for event bus and caching)
|
|
|
|
## Code Style and Structure
|
|
|
|
### General Principles
|
|
- Use 4 spaces for tabs (not 2 spaces)
|
|
- Write concise, technical TypeScript code with accurate examples
|
|
- Use functional and declarative programming patterns; avoid classes
|
|
- Prefer iteration and modularization over code duplication
|
|
- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
|
|
- All sources must be imported using "@/" prefix (e.g., `import "@/utils/log"`)
|
|
- Always use absolute imports
|
|
- Prefer interfaces over types
|
|
- Avoid enums; use maps instead
|
|
- Use strict mode in TypeScript for better type safety
|
|
|
|
### Folder Structure
|
|
```
|
|
/sources # Root of the sources
|
|
├── /app # Application entry points
|
|
│ ├── api.ts # API server setup
|
|
│ └── timeout.ts # Timeout handling
|
|
├── /apps # Applications directory
|
|
│ └── /api # API server application
|
|
│ └── /routes # API routes
|
|
├── /modules # Reusable modules (non-application logic)
|
|
├── /utils # Low level or abstract utilities
|
|
├── /recipes # Scripts to run outside of the server
|
|
├── /services # Core services
|
|
│ └── pubsub.ts # Pub/sub service
|
|
├── /storage # Database and storage utilities
|
|
│ ├── db.ts # Database client
|
|
│ ├── inTx.ts # Transaction wrapper
|
|
│ ├── repeatKey.ts # Key utilities
|
|
│ ├── simpleCache.ts # Caching utility
|
|
│ └── types.ts # Storage types
|
|
└── main.ts # Main entry point
|
|
```
|
|
|
|
### Naming Conventions
|
|
- Use lowercase with dashes for directories (e.g., components/auth-wizard)
|
|
- When writing utility functions, always name file and function the same way
|
|
- Test files should have ".spec.ts" suffix
|
|
|
|
## Tool Usage
|
|
|
|
### Web Search and Fetching
|
|
- When in doubt, use web tool to get answers from the web
|
|
- Search web when you have some failures
|
|
|
|
### File Operations
|
|
- NEVER create files unless they're absolutely necessary
|
|
- ALWAYS prefer editing existing files to creating new ones
|
|
- NEVER proactively create documentation files (*.md) or README files unless explicitly requested
|
|
|
|
## Utilities
|
|
|
|
### Writing Utility Functions
|
|
1. Always name file and function the same way for easy discovery
|
|
2. Utility functions should be modular and not too complex
|
|
3. Always write tests for utility functions BEFORE writing the code
|
|
4. Iterate implementation and tests until the function works as expected
|
|
5. Always write documentation for utility functions
|
|
|
|
## Modules
|
|
|
|
### Module Guidelines
|
|
- Modules are bigger than utility functions and abstract away complexity
|
|
- Each module should have a dedicated directory
|
|
- Modules usually don't have application-specific logic
|
|
- Modules can depend on other modules, but not on application-specific logic
|
|
- Prefer to write code as modules instead of application-specific code
|
|
|
|
### When to Use Modules
|
|
- When integrating with external services
|
|
- When abstracting complexity of some library
|
|
- When implementing related groups of functions (math, date, etc.)
|
|
|
|
### Known Modules
|
|
- **ai**: AI wrappers to interact with AI services
|
|
- **eventbus**: Event bus to send and receive events between modules and applications
|
|
- **lock**: Simple lock to synchronize access to resources in the whole cluster
|
|
- **media**: Tools to work with media files
|
|
|
|
## Applications
|
|
|
|
- Applications contain application-specific logic
|
|
- Applications have the most complexity; other parts should assist by reducing complexity
|
|
- When using prompts, write them to "_prompts.ts" file relative to the application
|
|
|
|
## Database
|
|
|
|
### Prisma Usage
|
|
- Prisma is used as ORM
|
|
- Use "inTx" to wrap database operations in transactions
|
|
- Do not update schema without absolute necessity
|
|
- For complex fields, use "Json" type
|
|
- NEVER DO MIGRATION YOURSELF. Only run yarn generate when new types needed
|
|
|
|
### Current Schema Status
|
|
The project has pending Prisma migrations that need to be applied:
|
|
- Migration: `20250715012822_add_metadata_version_agent_state`
|
|
|
|
## Events
|
|
|
|
### Event Bus
|
|
- eventbus allows sending and receiving events inside the process and between different processes
|
|
- eventbus is local or redis based
|
|
- Use "afterTx" to send events after transaction is committed successfully instead of directly emitting events
|
|
|
|
## Testing
|
|
|
|
- Write tests using Vitest
|
|
- Test files should be named the same as source files with ".spec.ts" suffix
|
|
- For utility functions, write tests BEFORE implementation
|
|
|
|
## API Development
|
|
|
|
- API server is in `/sources/apps/api`
|
|
- Routes are in `/sources/apps/api/routes`
|
|
- Use Fastify with Zod for type-safe route definitions
|
|
- Always validate inputs using Zod
|
|
- **Idempotency**: Design all operations to be idempotent - clients may retry requests automatically and the backend must handle multiple invocations of the same operation gracefully, producing the same result as a single invocation
|
|
|
|
## Docker Deployment
|
|
|
|
The project includes a multi-stage Dockerfile:
|
|
1. Builder stage: Installs dependencies and builds the application
|
|
2. Runner stage: Minimal runtime with only necessary files
|
|
3. Exposes port 3000
|
|
4. Requires FFmpeg and Python3 in the runtime
|
|
|
|
## Important Reminders
|
|
|
|
1. Do what has been asked; nothing more, nothing less
|
|
2. NEVER create files unless they're absolutely necessary for achieving your goal
|
|
3. ALWAYS prefer editing an existing file to creating a new one
|
|
4. NEVER proactively create documentation files (*.md) or README files unless explicitly requested
|
|
5. Use 4 spaces for tabs (not 2 spaces)
|
|
6. Use yarn instead of npm for all package management
|
|
|
|
## Debugging Notes
|
|
|
|
### Remote Logging Setup
|
|
- Use `DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING=true` env var to enable
|
|
- Server logs to `.logs/` directory with timestamped files (format: `MM-DD-HH-MM-SS.log`)
|
|
- Mobile and CLI send logs to `/logs-combined-from-cli-and-mobile-for-simple-ai-debugging` endpoint
|
|
|
|
### Common Issues & Tells
|
|
|
|
#### Socket/Connection Issues
|
|
- **Tell**: "Sending update to user-scoped connection" but mobile not updating
|
|
- **Tell**: Multiple "User disconnected" messages indicate socket instability
|
|
- **Tell**: "Response from the Engine was empty" = Prisma database connection lost
|
|
|
|
#### Auth Flow Debugging
|
|
- CLI must hit `/v1/auth/request` to create auth request
|
|
- Mobile scans QR and hits `/v1/auth/response` to approve
|
|
- **Tell**: 404 on `/v1/auth/response` = server likely restarted/crashed
|
|
- **Tell**: "Auth failed - user not found" = token issue or user doesn't exist
|
|
|
|
#### Session Creation Flow
|
|
- Sessions created via POST `/v1/sessions` with tag-based deduplication
|
|
- Server emits "new-session" update to all user connections
|
|
- **Tell**: Sessions created but not showing = mobile app not processing updates
|
|
- **Tell**: "pathname /" in mobile logs = app stuck at root screen
|
|
|
|
#### Environment Variables
|
|
- CLI: Use `yarn dev:local-server` (NOT `yarn dev`) to load `.env.dev-local-server`
|
|
- Server: Use `yarn dev` to start with proper env files
|
|
- **Tell**: Wrong server URL = check `HAPPY_SERVER_URL` env var
|
|
- **Tell**: Wrong home dir = check `HAPPY_HOME_DIR` (should be `~/.happy-dev` for local)
|
|
|
|
### Quick Diagnostic Commands
|
|
|
|
#### IMPORTANT: Always Start Debugging With These
|
|
```bash
|
|
# 1. CHECK CURRENT TIME - Logs use local time, know what's current!
|
|
date
|
|
|
|
# 2. CHECK LATEST LOG FILES - Server creates new logs on restart
|
|
ls -la .logs/*.log | tail -5
|
|
|
|
# 3. VERIFY YOU'RE LOOKING AT CURRENT LOGS
|
|
# Server logs are named: MM-DD-HH-MM-SS.log (month-day-hour-min-sec)
|
|
# If current time is 13:45 and latest log is 08-15-10-57-02.log from 10:57,
|
|
# that log started 3 hours ago but may still be active!
|
|
tail -1 .logs/[LATEST_LOG_FILE] # Check last entry timestamp
|
|
```
|
|
|
|
#### Common Debugging Patterns
|
|
```bash
|
|
# Check server logs for errors
|
|
tail -100 .logs/*.log | grep -E "(error|Error|ERROR|failed|Failed)"
|
|
|
|
# Monitor session creation
|
|
tail -f .logs/*.log | grep -E "(new-session|Session created)"
|
|
|
|
# Check active connections
|
|
tail -100 .logs/*.log | grep -E "(Token verified|User connected|User disconnected)"
|
|
|
|
# See what endpoints are being hit
|
|
tail -100 .logs/*.log | grep "incoming request"
|
|
|
|
# Debug socket real-time updates
|
|
tail -500 .logs/*.log | grep -A 2 -B 2 "new-session" | tail -30
|
|
tail -200 .logs/*.log | grep -E "(websocket|Socket.*connected|Sending update)" | tail -30
|
|
|
|
# Track socket events from mobile client
|
|
tail -300 .logs/*.log | grep "remote-log.*mobile" | grep -E "(SyncSocket|handleUpdate)" | tail -20
|
|
|
|
# Monitor session creation flow end-to-end
|
|
tail -500 .logs/*.log | grep "session-create" | tail -20
|
|
tail -500 .logs/*.log | grep "cmed556s4002bvb2020igg8jf" -A 3 -B 3 # Replace with actual session ID
|
|
|
|
# Check auth flow for sessions API
|
|
tail -300 .logs/*.log | grep "auth-decorator.*sessions" | tail -10
|
|
|
|
# Debug machine registration and online status
|
|
tail -500 .logs/*.log | grep -E "(machine-alive|machine-register|update-machine)" | tail -20
|
|
tail -500 .logs/*.log | grep "GET /v1/machines" | tail -10
|
|
tail -500 .logs/*.log | grep "POST /v1/machines" | tail -10
|
|
|
|
# Check what mobile app is seeing
|
|
tail -500 .logs/*.log | grep "📊 Storage" | tail -20
|
|
tail -500 .logs/*.log | grep "applySessions.*active" | tail -10
|
|
```
|
|
|
|
#### Time Format Reference
|
|
- **CLI logs**: `[HH:MM:SS.mmm]` in local time (e.g., `[13:45:23.738]`)
|
|
- **Server logs**: Include both `time` (Unix ms) and `localTime` (HH:MM:ss.mmm)
|
|
- **Mobile logs**: Sent with `timestamp` in UTC, converted to `localTime` on server
|
|
- **All consolidated logs**: Have `localTime` field for easy correlation
|
|
- When writing a some operations on db, like adding friend, sending a notification - always create a dedicated file in relevant subfolder of the @sources/app/ folder. Good example is "friendAdd", always prefix with an entity type, then action that should be performed.
|
|
- Never create migrations yourself, it is can be done only by human
|
|
- Do not return stuff from action functions "just in case", only essential
|
|
- Do not add logging when not asked
|
|
- do not run non-transactional things (like uploadign files) in transactions
|
|
- After writing an action - add a documentation comment that explains logic, also keep it in sync.
|
|
- always use github usernames
|
|
- Always use privacyKit.decodeBase64 and privacyKit.encodeBase64 from privacy-kit instead of using buffer |