Code Style
Coding conventions and best practices for Go Bananas!.
TypeScript Guidelines
Strict Mode
TypeScript strict mode is enabled. All code must:
- Have explicit types for function parameters
- Handle
nullandundefinedproperly - Avoid
anytype
typescript
// ✅ Good - explicit types
function processImage(buffer: ArrayBuffer, options: ProcessOptions): Promise<ImageResult> {
// Implementation
}
// ❌ Bad - implicit any
function processImage(buffer, options) {
// Implementation
}Type Definitions
Use interface for object shapes, type for unions/intersections:
typescript
// Objects - use interface
interface ImageMetadata {
id: number;
width: number;
height: number;
format: 'png' | 'jpeg' | 'webp';
}
// Unions - use type
type ImageFormat = 'png' | 'jpeg' | 'webp';
type Result<T> = { success: true; data: T } | { success: false; error: Error };Null Handling
Always handle nullable values explicitly:
typescript
// ✅ Good - null coalescing
const limit = input.limit ?? 50;
// ✅ Good - optional chaining
const name = user?.profile?.displayName ?? 'Anonymous';
// ❌ Bad - implicit null check
const limit = input.limit || 50; // Breaks for 0
// ❌ Bad - ignoring nullability
const name = user.profile.displayName; // Might be nullAsync/Await
Always use async/await over raw promises:
typescript
// ✅ Good
async function fetchData() {
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch (error) {
throw new Error('Fetch failed');
}
}
// ❌ Bad - nested promises
function fetchData() {
return fetch(url)
.then(response => response.json())
.then(data => data)
.catch(error => { throw new Error('Fetch failed'); });
}Naming Conventions
Files
kebab-case.ts
my-component.tsx
image-storage.tsVariables and Functions
typescript
// camelCase for variables and functions
const imageBuffer = await fetchImage();
function processImageData() {}
// UPPER_SNAKE_CASE for constants
const MAX_IMAGE_SIZE = 20 * 1024 * 1024;
const DEFAULT_ASPECT_RATIO = 'square';Classes and Interfaces
typescript
// PascalCase for classes and interfaces
class ImageStorageService {}
interface ImageMetadata {}
type ProcessingOptions = {};Database Fields
typescript
// snake_case for database columns
const query = `
SELECT
tenant_id,
created_at,
image_count
FROM tenants
`;
// camelCase when mapping to TypeScript
interface Tenant {
tenantId: string;
createdAt: Date;
imageCount: number;
}Error Handling
Custom Error Classes
typescript
// Define specific error types
export class ValidationError extends Error {
constructor(message: string, public field: string) {
super(message);
this.name = 'ValidationError';
}
}
export class AuthenticationError extends Error {
constructor(message: string = 'Authentication required') {
super(message);
this.name = 'AuthenticationError';
}
}Error Pattern
typescript
async function processOperation() {
const startTime = Date.now();
try {
// Main logic
const result = await performAction();
return result;
} catch (error) {
// Log for debugging
console.error('Operation failed:', error);
// Track failure
await trackUsage({ operation: 'operation_failed' });
// Rethrow with context
throw new Error(
`Operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}API Error Responses
typescript
function handleError(error: unknown): Response {
if (error instanceof ValidationError) {
return jsonResponse({
success: false,
error: { code: 'VALIDATION_ERROR', message: error.message }
}, 400);
}
if (error instanceof AuthenticationError) {
return jsonResponse({
success: false,
error: { code: 'AUTHENTICATION_ERROR', message: error.message }
}, 401);
}
// Generic error - don't leak internals
console.error('Unhandled error:', error);
return jsonResponse({
success: false,
error: { code: 'INTERNAL_ERROR', message: 'An error occurred' }
}, 500);
}Zod Schemas
Schema Definition
typescript
import { z } from 'zod';
// Define schema with descriptions
export const CreateCharacterSchema = z.object({
character_name: z
.string()
.min(1, 'Name is required')
.max(100, 'Name too long')
.describe('Character name'),
base_prompt: z
.string()
.min(10, 'Prompt too short')
.max(2000, 'Prompt too long')
.describe('Character appearance description'),
reference_image_ids: z
.array(z.number().int().positive())
.max(10)
.optional()
.describe('Reference image IDs'),
});
// Export inferred type
export type CreateCharacterInput = z.infer<typeof CreateCharacterSchema>;Validation Pattern
typescript
async function handleRequest(params: unknown) {
// Validate at entry point
const input = CreateCharacterSchema.parse(params);
// Use validated input
return createCharacter(input);
}Database Queries
Prepared Statements
Always use prepared statements:
typescript
// ✅ Good - prepared statement
const result = await db.prepare(
'SELECT * FROM images WHERE tenant_id = ? AND id = ?'
).bind(tenantId, imageId).first();
// ❌ Bad - string interpolation (SQL injection risk)
const result = await db.prepare(
`SELECT * FROM images WHERE tenant_id = '${tenantId}'`
).first();Null Coalescing for Bindings
D1 doesn't accept undefined:
typescript
// ✅ Good
bindings.push(
input.limit ?? 50,
input.offset ?? 0,
input.search ?? null
);
// ❌ Bad - undefined causes D1_TYPE_ERROR
bindings.push(input.limit, input.offset, input.search);Query Building
typescript
function buildImageQuery(filters: ImageFilters) {
const conditions: string[] = ['tenant_id = ?'];
const bindings: (string | number | null)[] = [filters.tenantId];
if (filters.search) {
conditions.push('prompt LIKE ?');
bindings.push(`%${filters.search}%`);
}
if (filters.operationType) {
conditions.push('operation_type = ?');
bindings.push(filters.operationType);
}
const query = `
SELECT * FROM images
WHERE ${conditions.join(' AND ')}
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`;
bindings.push(filters.limit ?? 50, filters.offset ?? 0);
return { query, bindings };
}Comments
When to Comment
- Complex algorithms
- Non-obvious business logic
- Workarounds for known issues
- API documentation
typescript
// ✅ Good - explains why
// Using setTimeout to avoid rate limiting from Gemini API
await new Promise(resolve => setTimeout(resolve, 100));
// ✅ Good - documents public API
/**
* Generate an image from a text prompt.
* @param prompt - Text description of the image
* @param options - Generation options
* @returns Generated image metadata
*/
export async function generateImage(prompt: string, options: GenerateOptions) {}
// ❌ Bad - obvious from code
// Increment counter
counter++;
// ❌ Bad - outdated comment
// Returns user name (actually returns email now)
function getUser() { return user.email; }JSDoc for APIs
typescript
/**
* Create a new character for multi-scene generation.
*
* @param env - Cloudflare environment bindings
* @param tenantId - Tenant identifier
* @param input - Character creation input
* @returns Created character record
* @throws ValidationError if input is invalid
* @throws DuplicateError if character name exists
*
* @example
* ```typescript
* const character = await createCharacter(env, 'acme-corp', {
* character_name: 'Luna',
* base_prompt: 'A young woman with red hair...'
* });
* ```
*/
export async function createCharacter(
env: Env,
tenantId: string,
input: CreateCharacterInput
): Promise<Character> {
// Implementation
}File Organization
Imports
Order imports:
- Node built-ins
- External packages
- Internal modules
- Types
typescript
// 1. External
import { z } from 'zod';
// 2. Internal modules
import { trackUsage } from '../services/usage';
import { ImageStorageService } from '../services/image-storage';
// 3. Types
import type { Env, ImageMetadata } from '../types';Exports
Prefer named exports:
typescript
// ✅ Good - named exports
export function createCharacter() {}
export const CreateCharacterSchema = z.object({});
export type CreateCharacterInput = z.infer<typeof CreateCharacterSchema>;
// Avoid - default exports (harder to refactor)
export default function createCharacter() {}Testing Style
Test Structure
typescript
describe('createCharacter', () => {
// Setup shared fixtures
const mockEnv = createMockEnv();
const tenantId = 'test-tenant';
// Group related tests
describe('validation', () => {
it('rejects empty character name', () => {
expect(() => CreateCharacterSchema.parse({ character_name: '' }))
.toThrow('Name is required');
});
it('rejects name over 100 characters', () => {
const longName = 'a'.repeat(101);
expect(() => CreateCharacterSchema.parse({ character_name: longName }))
.toThrow('Name too long');
});
});
describe('success cases', () => {
it('creates character with required fields', async () => {
const result = await createCharacter(mockEnv, tenantId, {
character_name: 'Luna',
base_prompt: 'A young woman with red hair',
});
expect(result.character_name).toBe('Luna');
expect(result.id).toBeDefined();
});
});
});