Files
insta-recipe/docs/CODE_STYLE.md

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.ts
  • src/lib/server/tandoor-config.ts
  • src/lib/client/PushNotificationManager.ts

Test Files

Pattern: <name>.spec.ts or <name>.test.ts

Examples:

  • queue-manager.spec.ts
  • instagram-url-validation.spec.ts
  • page.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 I is NOT used
  • Exported types use export type or export 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 Schema suffix
  • 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

  1. External dependencies (Node.js built-ins, npm packages)
  2. SvelteKit imports ($lib, $app, $env)
  3. Relative imports (./ , ../)
  4. 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/js recommended
  • TypeScript: typescript-eslint recommended
  • Svelte: eslint-plugin-svelte recommended
  • 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 value
  • undefined: Not yet initialized or optional parameters
  • Prefer null for 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