Files
insta-recipe/docs/CODE_STYLE.md
Giancarmine Salucci 49bccf8f15 simplify
2026-02-18 01:21:44 +01:00

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