16 KiB
Code Style Guide
Last Updated: 2026-02-15T00:00:00.000Z
JIRA: RECIPE-0001
Language & Version
Primary Language: TypeScript
Version: 5.9.3
Framework: SvelteKit 2.48.5 with Svelte 5.43.8
Node.js: 22+ (LTS)
Naming Conventions
Files & Directories
SvelteKit Route Files
- Route pages:
+page.svelte - Route servers:
+server.ts - Route layouts:
+layout.svelte - Type definitions:
$types.ts(auto-generated)
Example:
src/routes/api/queue/
├── [id]/
│ ├── +server.ts
│ └── retry/
│ └── +server.ts
├── stream/
│ └── +server.ts
└── +server.ts
Library Files
- PascalCase for classes and managers:
QueueManager.ts,PushNotificationService.ts - kebab-case for utilities and configs:
tandoor-config.ts,instagram-url.ts - lowercase for general modules:
browser.ts,extraction.ts,parser.ts
Examples from codebase:
src/lib/server/queue/QueueManager.tssrc/lib/server/tandoor-config.tssrc/lib/client/PushNotificationManager.ts
Test Files
Pattern: <name>.spec.ts or <name>.test.ts
Examples:
queue-manager.spec.tsinstagram-url-validation.spec.tspage.svelte.spec.ts
Variables & Functions
Variables
- camelCase for local variables and parameters
- SCREAMING_SNAKE_CASE for constants
Examples:
// From QueueManager.ts
private items: Map<string, QueueItem> = new Map();
private subscribers: Set<QueueUpdateCallback> = new Set();
// From parser.ts
const RECIPE_DETECTION_PROMPT = "...";
const RECIPE_EXTRACTION_PROMPT = "...";
// Local variables
const now = new Date().toISOString();
const unsubscribe = queueManager.subscribe(callback);
Functions
- camelCase for function names
- Descriptive action verbs:
enqueue,extractRecipe,uploadRecipeImage
Examples:
// From QueueManager.ts
enqueue(url: string): QueueItem { ... }
dequeue(): QueueItem | null { ... }
updateStatus(id: string, status: QueueItemStatus): void { ... }
// From extraction.ts
export async function extractTextAndThumbnail(
url: string,
onProgress?: ProgressCallback
): Promise<ExtractedContent> { ... }
// From parser.ts
export async function extractRecipe(text: string): Promise<Recipe> { ... }
Types & Interfaces
Interfaces & Types
- PascalCase for interface names
- Prefix with
Iis NOT used - Exported types use
export typeorexport interface
Examples:
// From queue/types.ts
export interface QueueItem {
id: string;
url: string;
status: QueueItemStatus;
enqueuedAt: string;
// ...
}
export interface QueueStatusUpdate {
type: string;
itemId: string;
status: QueueItemStatus;
// ...
}
export type QueueItemStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
// From extraction.ts
export interface ExtractedContent {
text: string;
thumbnailUrl?: string;
}
export type ProgressCallback = (event: ProgressEvent) => void;
Zod Schemas
- PascalCase with
Schemasuffix - Inferred types without suffix
Examples:
// From parser.ts
const RecipeSchema = z.object({
name: z.string(),
description: z.string(),
servings: z.number()
// ...
});
export type Recipe = z.infer<typeof RecipeSchema>;
// From tandoor.ts
const TandoorRecipeSchema = z.object({
// ...
});
export type TandoorRecipe = z.infer<typeof TandoorRecipeSchema>;
Classes
Class Names
- PascalCase for class names
- Descriptive suffixes:
Manager,Service,Processor,Handler
Examples:
// From QueueManager.ts
export class QueueManager {
private items: Map<string, QueueItem> = new Map();
// ...
}
// From QueueProcessor.ts
export class QueueProcessor {
private processing: Set<string> = new Set();
// ...
}
// From PushNotificationService.ts
class PushNotificationService {
private subscriptions: Map<string, PushSubscription> = new Map();
// ...
}
Singleton Export Pattern
// Class definition
export class QueueManager {
// Implementation
}
// Singleton instance export
export const queueManager = new QueueManager();
Indentation & Formatting
General Rules
- Indentation: 2 spaces (enforced by Prettier)
- No tabs
- Max line length: 100 characters (soft limit, not enforced)
- Trailing commas: Yes (multiline)
- Semicolons: Yes (always)
- Single quotes: Yes (enforced by Prettier)
Code Examples
Function Declarations
// From QueueManager.ts
enqueue(url: string): QueueItem {
const now = new Date().toISOString();
const item: QueueItem = {
id: uuidv4(),
url,
status: 'pending',
enqueuedAt: now,
createdAt: now,
updatedAt: now,
phases: [
{ name: 'extraction', status: 'pending' },
{ name: 'parsing', status: 'pending' },
{ name: 'uploading', status: 'pending' }
],
logs: [],
progressEvents: [],
retryCount: 0,
maxRetries: 3
};
this.items.set(item.id, item);
return item;
}
Async Functions
// From extraction.ts
export async function extractTextAndThumbnail(
url: string,
onProgress?: ProgressCallback
): Promise<ExtractedContent> {
const browser = await getBrowser();
const context = await createBrowserContext(browser);
const page = await context.newPage();
try {
await page.goto(url, { waitUntil: 'networkidle' });
// ...
} finally {
await context.close();
}
}
Object Destructuring
// From route handlers
export const POST: RequestHandler = async ({ request }) => {
const { url } = await request.json();
// ...
};
export const GET: RequestHandler = async ({ params }) => {
const { id } = params;
// ...
};
Import Patterns
Import Order
- External dependencies (Node.js built-ins, npm packages)
- SvelteKit imports (
$lib,$app,$env) - Relative imports (
./,../) - Type imports (separate from value imports when beneficial)
Example:
// From QueueProcessor.ts
// External dependencies
import { v4 as uuidv4 } from 'uuid';
// SvelteKit imports
import { queueManager } from './QueueManager';
import { extractTextAndThumbnail } from '$lib/server/extraction';
import { extractRecipe } from '$lib/server/parser';
import { uploadRecipeWithIngredientsDTO, uploadRecipeImage } from '$lib/server/tandoor';
import { pushNotificationService } from '$lib/server/notifications/PushNotificationService';
import { queueConfig } from './config';
// Type imports
import type { ProgressEvent } from '$lib/server/extraction';
import type { QueueItem } from './types';
Import Styles
Named Imports (Preferred)
import { json } from '@sveltejs/kit';
import { queueManager } from '$lib/server/queue/QueueManager';
import { validateInstagramUrl } from '$lib/server/validation/instagram-url';
Type-Only Imports
import type { RequestHandler } from './$types';
import type { QueueItem, QueueItemStatus } from './types';
Default Imports
import OpenAI from 'openai';
import fs from 'fs';
import path from 'path';
Export Patterns
Named Exports (Preferred)
// Export functions
export async function extractRecipe(text: string): Promise<Recipe> { ... }
// Export classes
export class QueueManager { ... }
// Export constants
export const queueConfig = { ... };
// Export types
export type Recipe = z.infer<typeof RecipeSchema>;
export interface QueueItem { ... }
Singleton Pattern Export
// Define class
export class QueueManager { ... }
// Export singleton instance
export const queueManager = new QueueManager();
Comments & Documentation
JSDoc Style
Used extensively for public APIs and exported functions.
Function Documentation:
/**
* Add URL to processing queue
*
* @param url - Instagram URL to process
* @returns Newly created queue item
*
* @example
* ```typescript
* const item = queueManager.enqueue('https://instagram.com/p/abc123');
* console.log('Queued with ID:', item.id);
* ```
*/
enqueue(url: string): QueueItem {
// Implementation
}
Class Documentation:
/**
* Singleton queue manager for processing Instagram URLs
*
* Features:
* - FIFO queue with unique IDs
* - Status tracking and updates
* - Progress event accumulation
* - Retry support for failed items
* - Pub/sub for real-time updates
*
* @example
* ```typescript
* import { queueManager } from './QueueManager';
*
* // Add item to queue
* const item = queueManager.enqueue('https://instagram.com/p/abc123');
* ```
*/
export class QueueManager {
// Implementation
}
Module-Level Documentation:
/**
* Queue Manager - Core queue operations and event management
*
* Manages an in-memory queue of Instagram URL processing jobs.
* Provides CRUD operations and pub/sub mechanism for queue updates.
*
* Architecture: Domain Layer (Hexagonal Architecture)
* - Port: Defines queue operations interface
* - Implementation: In-memory Map-based storage
*/
Inline Comments
Single-line Comments
// Set restrictive permissions
fs.chmodSync(authFile, 0o600);
// FIFO order - get oldest pending item
const pendingItems = Array.from(this.items.values()).filter((item) => item.status === 'pending');
Block Comments (Avoided)
Single-line comments preferred. Block comments used only for large comment blocks or temporarily disabling code during development.
TODO Comments
// TODO: Add retry logic with exponential backoff
// FIXME: Handle race condition when multiple workers dequeue
TypeScript Patterns
Type Safety
Strict Mode Enabled
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"forceConsistentCasingInFileNames": true
}
}
Type Annotations
// Explicit return types for public functions
export async function extractRecipe(text: string): Promise<Recipe> { ... }
// Explicit parameter types
function processItem(item: QueueItem, config: QueueConfig): void { ... }
// Type inference for local variables (acceptable)
const items = queueManager.getAll(); // Type inferred
Union Types
export type QueueItemStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
export type ProcessingPhase = 'extraction' | 'parsing' | 'uploading';
export type ProgressEventType = 'status' | 'method' | 'retry' | 'error' | 'thumbnail' | 'complete';
Generics
// Generic function
async function fetchFromTandoor<T>(
url: string,
options: Partial<RequestInit> = { method: 'GET' }
): Promise<{ ok: boolean; data?: T; error?: string }> {
// Implementation
}
Svelte 5 Patterns
Runes (Reactivity)
$state (Reactive Variables)
<script lang="ts">
let count = $state(0);
let items = $state<QueueItem[]>([]);
</script>
$props (Component Props)
<script lang="ts">
let {
recipe = null,
tandoorEnabled = false,
onRetry,
onImportToTandoor
} = $props<{
recipe: Recipe | null;
tandoorEnabled: boolean;
onRetry: () => void;
onImportToTandoor: () => void;
}>();
</script>
$derived (Computed Values)
<script lang="ts">
let count = $state(0);
let double = $derived(count * 2);
</script>
$effect (Side Effects)
<script lang="ts">
let url = $state('');
$effect(() => {
console.log('URL changed:', url);
});
</script>
Component Structure
<script lang="ts">
// Imports
import { onMount } from 'svelte';
// Props
let { items } = $props<{ items: Item[] }>();
// State
let loading = $state(false);
// Derived state
let count = $derived(items.length);
// Functions
function handleClick() {
// ...
}
// Effects
$effect(() => {
// Side effects
});
</script>
<!-- Template -->
<div>
<!-- Markup -->
</div>
<!-- Styles -->
<style>
/* Component-scoped styles */
</style>
Error Handling
Custom Error Classes
// From api/errors.ts
export class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = 'ValidationError';
}
}
export class NotFoundError extends Error {
constructor(resource: string) {
super(`${resource} not found`);
this.name = 'NotFoundError';
}
}
export class ConflictError extends Error {
constructor(message: string) {
super(message);
this.name = 'ConflictError';
}
}
Try-Catch Pattern
export const POST: RequestHandler = async ({ request }) => {
try {
const { url } = await request.json();
if (!url) {
throw new ValidationError('URL is required');
}
const item = queueManager.enqueue(url);
return json(item, { status: 201 });
} catch (error) {
return handleApiError(error);
}
};
Linting Configuration
ESLint
Config: eslint.config.js
- Base:
@eslint/jsrecommended - TypeScript:
typescript-eslintrecommended - Svelte:
eslint-plugin-svelterecommended - Formatting:
eslint-config-prettier
Rules:
{
rules: {
"no-undef": 'off' // TypeScript handles this
}
}
Prettier
Config: .prettierrc
{
"useTabs": false,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"]
}
Testing Conventions
Test Structure
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
describe('QueueManager', () => {
let manager: QueueManager;
beforeEach(() => {
manager = new QueueManager();
});
it('should enqueue items', () => {
const item = manager.enqueue('https://instagram.com/p/test');
expect(item.status).toBe('pending');
});
it('should dequeue items in FIFO order', () => {
manager.enqueue('url1');
manager.enqueue('url2');
const first = manager.dequeue();
expect(first?.url).toBe('url1');
});
});
Mock Pattern
vi.mock('$lib/server/extraction', () => ({
extractTextAndThumbnail: vi.fn().mockResolvedValue({
text: 'Mock text',
thumbnailUrl: 'https://example.com/thumb.jpg'
})
}));
File Headers
Module Documentation Pattern
Every major module includes a header comment:
/**
* Module Name - Brief Description
*
* Detailed description of the module's purpose and functionality.
*
* Architecture: Layer Name (Hexagonal Architecture)
* - Port: Description of port interface
* - Implementation: Description of concrete implementation
*/
Example:
/**
* Queue Manager - Core queue operations and event management
*
* Manages an in-memory queue of Instagram URL processing jobs.
* Provides CRUD operations and pub/sub mechanism for queue updates.
*
* Architecture: Domain Layer (Hexagonal Architecture)
* - Port: Defines queue operations interface
* - Implementation: In-memory Map-based storage
*/
Additional Conventions
Environment Variables
import { env } from '$env/dynamic/private';
const apiKey = env.OPENAI_API_KEY;
const tandoorUrl = env.TANDOOR_URL || null;
Date Handling
ISO8601 strings throughout the application:
const now = new Date().toISOString();
// Output: "2026-02-15T12:30:45.123Z"
Null vs Undefined
null: Intentional absence of valueundefined: Not yet initialized or optional parameters- Prefer
nullfor API responses and data structures
Async/Await
Always preferred over Promise chains:
// Preferred
async function fetchData() {
const response = await fetch(url);
const data = await response.json();
return data;
}
// Avoid
function fetchData() {
return fetch(url)
.then((response) => response.json())
.then((data) => data);
}
Document Version: 1.0
Enforced By: ESLint, Prettier, TypeScript
Generated by: Initializer Agent