simplify
This commit is contained in:
@@ -19,12 +19,14 @@
|
||||
### 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]/
|
||||
@@ -37,19 +39,23 @@ src/routes/api/queue/
|
||||
```
|
||||
|
||||
#### 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`
|
||||
@@ -57,10 +63,12 @@ Pattern: `<name>.spec.ts` or `<name>.test.ts`
|
||||
### Variables & Functions
|
||||
|
||||
#### Variables
|
||||
|
||||
- **camelCase** for local variables and parameters
|
||||
- **SCREAMING_SNAKE_CASE** for constants
|
||||
|
||||
**Examples:**
|
||||
|
||||
```typescript
|
||||
// From QueueManager.ts
|
||||
private items: Map<string, QueueItem> = new Map();
|
||||
@@ -76,10 +84,12 @@ const unsubscribe = queueManager.subscribe(callback);
|
||||
```
|
||||
|
||||
#### Functions
|
||||
|
||||
- **camelCase** for function names
|
||||
- **Descriptive action verbs**: `enqueue`, `extractRecipe`, `uploadRecipeImage`
|
||||
|
||||
**Examples:**
|
||||
|
||||
```typescript
|
||||
// From QueueManager.ts
|
||||
enqueue(url: string): QueueItem { ... }
|
||||
@@ -99,62 +109,62 @@ 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:**
|
||||
|
||||
```typescript
|
||||
// From queue/types.ts
|
||||
export interface QueueItem {
|
||||
id: string;
|
||||
url: string;
|
||||
status: QueueItemStatus;
|
||||
enqueuedAt: string;
|
||||
// ...
|
||||
id: string;
|
||||
url: string;
|
||||
status: QueueItemStatus;
|
||||
enqueuedAt: string;
|
||||
// ...
|
||||
}
|
||||
|
||||
export interface QueueStatusUpdate {
|
||||
type: string;
|
||||
itemId: string;
|
||||
status: QueueItemStatus;
|
||||
// ...
|
||||
type: string;
|
||||
itemId: string;
|
||||
status: QueueItemStatus;
|
||||
// ...
|
||||
}
|
||||
|
||||
export type QueueItemStatus =
|
||||
| 'pending'
|
||||
| 'in_progress'
|
||||
| 'completed'
|
||||
| 'failed';
|
||||
export type QueueItemStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
|
||||
|
||||
// From extraction.ts
|
||||
export interface ExtractedContent {
|
||||
text: string;
|
||||
thumbnailUrl?: string;
|
||||
text: string;
|
||||
thumbnailUrl?: string;
|
||||
}
|
||||
|
||||
export type ProgressCallback = (event: ProgressEvent) => void;
|
||||
```
|
||||
|
||||
#### Zod Schemas
|
||||
|
||||
- **PascalCase** with `Schema` suffix
|
||||
- Inferred types without suffix
|
||||
|
||||
**Examples:**
|
||||
|
||||
```typescript
|
||||
// From parser.ts
|
||||
const RecipeSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
servings: z.number(),
|
||||
// ...
|
||||
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>;
|
||||
@@ -163,35 +173,38 @@ export type TandoorRecipe = z.infer<typeof TandoorRecipeSchema>;
|
||||
### Classes
|
||||
|
||||
#### Class Names
|
||||
|
||||
- **PascalCase** for class names
|
||||
- Descriptive suffixes: `Manager`, `Service`, `Processor`, `Handler`
|
||||
|
||||
**Examples:**
|
||||
|
||||
```typescript
|
||||
// From QueueManager.ts
|
||||
export class QueueManager {
|
||||
private items: Map<string, QueueItem> = new Map();
|
||||
// ...
|
||||
private items: Map<string, QueueItem> = new Map();
|
||||
// ...
|
||||
}
|
||||
|
||||
// From QueueProcessor.ts
|
||||
export class QueueProcessor {
|
||||
private processing: Set<string> = new Set();
|
||||
// ...
|
||||
private processing: Set<string> = new Set();
|
||||
// ...
|
||||
}
|
||||
|
||||
// From PushNotificationService.ts
|
||||
class PushNotificationService {
|
||||
private subscriptions: Map<string, PushSubscription> = new Map();
|
||||
// ...
|
||||
private subscriptions: Map<string, PushSubscription> = new Map();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Singleton Export Pattern
|
||||
|
||||
```typescript
|
||||
// Class definition
|
||||
export class QueueManager {
|
||||
// Implementation
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// Singleton instance export
|
||||
@@ -203,6 +216,7 @@ 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)
|
||||
@@ -213,6 +227,7 @@ export const queueManager = new QueueManager();
|
||||
### Code Examples
|
||||
|
||||
#### Function Declarations
|
||||
|
||||
```typescript
|
||||
// From QueueManager.ts
|
||||
enqueue(url: string): QueueItem {
|
||||
@@ -234,43 +249,45 @@ enqueue(url: string): QueueItem {
|
||||
retryCount: 0,
|
||||
maxRetries: 3
|
||||
};
|
||||
|
||||
|
||||
this.items.set(item.id, item);
|
||||
return item;
|
||||
}
|
||||
```
|
||||
|
||||
#### Async Functions
|
||||
|
||||
```typescript
|
||||
// From extraction.ts
|
||||
export async function extractTextAndThumbnail(
|
||||
url: string,
|
||||
onProgress?: ProgressCallback
|
||||
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();
|
||||
}
|
||||
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
|
||||
|
||||
```typescript
|
||||
// From route handlers
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
const { url } = await request.json();
|
||||
// ...
|
||||
const { url } = await request.json();
|
||||
// ...
|
||||
};
|
||||
|
||||
export const GET: RequestHandler = async ({ params }) => {
|
||||
const { id } = params;
|
||||
// ...
|
||||
const { id } = params;
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
@@ -279,12 +296,14 @@ export const GET: RequestHandler = async ({ 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:**
|
||||
|
||||
```typescript
|
||||
// From QueueProcessor.ts
|
||||
|
||||
@@ -307,6 +326,7 @@ import type { QueueItem } from './types';
|
||||
### Import Styles
|
||||
|
||||
#### Named Imports (Preferred)
|
||||
|
||||
```typescript
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { queueManager } from '$lib/server/queue/QueueManager';
|
||||
@@ -314,12 +334,14 @@ import { validateInstagramUrl } from '$lib/server/validation/instagram-url';
|
||||
```
|
||||
|
||||
#### Type-Only Imports
|
||||
|
||||
```typescript
|
||||
import type { RequestHandler } from './$types';
|
||||
import type { QueueItem, QueueItemStatus } from './types';
|
||||
```
|
||||
|
||||
#### Default Imports
|
||||
|
||||
```typescript
|
||||
import OpenAI from 'openai';
|
||||
import fs from 'fs';
|
||||
@@ -329,6 +351,7 @@ import path from 'path';
|
||||
### Export Patterns
|
||||
|
||||
#### Named Exports (Preferred)
|
||||
|
||||
```typescript
|
||||
// Export functions
|
||||
export async function extractRecipe(text: string): Promise<Recipe> { ... }
|
||||
@@ -345,6 +368,7 @@ export interface QueueItem { ... }
|
||||
```
|
||||
|
||||
#### Singleton Pattern Export
|
||||
|
||||
```typescript
|
||||
// Define class
|
||||
export class QueueManager { ... }
|
||||
@@ -358,16 +382,18 @@ export const queueManager = new QueueManager();
|
||||
## Comments & Documentation
|
||||
|
||||
### JSDoc Style
|
||||
|
||||
Used extensively for public APIs and exported functions.
|
||||
|
||||
**Function Documentation:**
|
||||
```typescript
|
||||
|
||||
````typescript
|
||||
/**
|
||||
* 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');
|
||||
@@ -377,41 +403,43 @@ Used extensively for public APIs and exported functions.
|
||||
enqueue(url: string): QueueItem {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
**Class Documentation:**
|
||||
```typescript
|
||||
|
||||
````typescript
|
||||
/**
|
||||
* 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
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
````
|
||||
|
||||
**Module-Level Documentation:**
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 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
|
||||
@@ -421,19 +449,21 @@ export class QueueManager {
|
||||
### Inline Comments
|
||||
|
||||
#### Single-line Comments
|
||||
|
||||
```typescript
|
||||
// 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');
|
||||
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
|
||||
|
||||
```typescript
|
||||
// TODO: Add retry logic with exponential backoff
|
||||
// FIXME: Handle race condition when multiple workers dequeue
|
||||
@@ -446,17 +476,19 @@ Single-line comments preferred. Block comments used only for large comment block
|
||||
### Type Safety
|
||||
|
||||
#### Strict Mode Enabled
|
||||
|
||||
```json
|
||||
// tsconfig.json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Type Annotations
|
||||
|
||||
```typescript
|
||||
// Explicit return types for public functions
|
||||
export async function extractRecipe(text: string): Promise<Recipe> { ... }
|
||||
@@ -469,35 +501,24 @@ const items = queueManager.getAll(); // Type inferred
|
||||
```
|
||||
|
||||
### Union Types
|
||||
|
||||
```typescript
|
||||
export type QueueItemStatus =
|
||||
| 'pending'
|
||||
| 'in_progress'
|
||||
| 'completed'
|
||||
| 'failed';
|
||||
export type QueueItemStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
|
||||
|
||||
export type ProcessingPhase =
|
||||
| 'extraction'
|
||||
| 'parsing'
|
||||
| 'uploading';
|
||||
export type ProcessingPhase = 'extraction' | 'parsing' | 'uploading';
|
||||
|
||||
export type ProgressEventType =
|
||||
| 'status'
|
||||
| 'method'
|
||||
| 'retry'
|
||||
| 'error'
|
||||
| 'thumbnail'
|
||||
| 'complete';
|
||||
export type ProgressEventType = 'status' | 'method' | 'retry' | 'error' | 'thumbnail' | 'complete';
|
||||
```
|
||||
|
||||
### Generics
|
||||
|
||||
```typescript
|
||||
// Generic function
|
||||
async function fetchFromTandoor<T>(
|
||||
url: string,
|
||||
options: Partial<RequestInit> = { method: 'GET' }
|
||||
url: string,
|
||||
options: Partial<RequestInit> = { method: 'GET' }
|
||||
): Promise<{ ok: boolean; data?: T; error?: string }> {
|
||||
// Implementation
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
@@ -508,6 +529,7 @@ async function fetchFromTandoor<T>(
|
||||
### Runes (Reactivity)
|
||||
|
||||
#### $state (Reactive Variables)
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let count = $state(0);
|
||||
@@ -516,13 +538,14 @@ async function fetchFromTandoor<T>(
|
||||
```
|
||||
|
||||
#### $props (Component Props)
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let {
|
||||
let {
|
||||
recipe = null,
|
||||
tandoorEnabled = false,
|
||||
onRetry,
|
||||
onImportToTandoor
|
||||
onImportToTandoor
|
||||
} = $props<{
|
||||
recipe: Recipe | null;
|
||||
tandoorEnabled: boolean;
|
||||
@@ -533,6 +556,7 @@ async function fetchFromTandoor<T>(
|
||||
```
|
||||
|
||||
#### $derived (Computed Values)
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let count = $state(0);
|
||||
@@ -541,10 +565,11 @@ async function fetchFromTandoor<T>(
|
||||
```
|
||||
|
||||
#### $effect (Side Effects)
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let url = $state('');
|
||||
|
||||
|
||||
$effect(() => {
|
||||
console.log('URL changed:', url);
|
||||
});
|
||||
@@ -552,25 +577,26 @@ async function fetchFromTandoor<T>(
|
||||
```
|
||||
|
||||
### Component Structure
|
||||
|
||||
```svelte
|
||||
<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
|
||||
@@ -593,46 +619,47 @@ async function fetchFromTandoor<T>(
|
||||
## Error Handling
|
||||
|
||||
### Custom Error Classes
|
||||
|
||||
```typescript
|
||||
// From api/errors.ts
|
||||
export class ValidationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ValidationError';
|
||||
}
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundError extends Error {
|
||||
constructor(resource: string) {
|
||||
super(`${resource} not found`);
|
||||
this.name = 'NotFoundError';
|
||||
}
|
||||
constructor(resource: string) {
|
||||
super(`${resource} not found`);
|
||||
this.name = 'NotFoundError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ConflictError';
|
||||
}
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ConflictError';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Try-Catch Pattern
|
||||
|
||||
```typescript
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@@ -641,14 +668,16 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
## Linting Configuration
|
||||
|
||||
### ESLint
|
||||
|
||||
**Config:** `eslint.config.js`
|
||||
|
||||
- Base: `@eslint/js` recommended
|
||||
- TypeScript: `typescript-eslint` recommended
|
||||
- Svelte: `eslint-plugin-svelte` recommended
|
||||
- Svelte: `eslint-plugin-svelte` recommended
|
||||
- Formatting: `eslint-config-prettier`
|
||||
|
||||
**Rules:**
|
||||
|
||||
```javascript
|
||||
{
|
||||
rules: {
|
||||
@@ -658,15 +687,16 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
```
|
||||
|
||||
### Prettier
|
||||
|
||||
**Config:** `.prettierrc`
|
||||
|
||||
```json
|
||||
{
|
||||
"useTabs": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"]
|
||||
"useTabs": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -675,38 +705,40 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
## Testing Conventions
|
||||
|
||||
### Test Structure
|
||||
|
||||
```typescript
|
||||
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');
|
||||
});
|
||||
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
|
||||
|
||||
```typescript
|
||||
vi.mock('$lib/server/extraction', () => ({
|
||||
extractTextAndThumbnail: vi.fn().mockResolvedValue({
|
||||
text: 'Mock text',
|
||||
thumbnailUrl: 'https://example.com/thumb.jpg'
|
||||
})
|
||||
extractTextAndThumbnail: vi.fn().mockResolvedValue({
|
||||
text: 'Mock text',
|
||||
thumbnailUrl: 'https://example.com/thumb.jpg'
|
||||
})
|
||||
}));
|
||||
```
|
||||
|
||||
@@ -715,14 +747,15 @@ vi.mock('$lib/server/extraction', () => ({
|
||||
## File Headers
|
||||
|
||||
### Module Documentation Pattern
|
||||
|
||||
Every major module includes a header comment:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 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
|
||||
@@ -730,13 +763,14 @@ Every major module includes a header comment:
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 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
|
||||
@@ -748,6 +782,7 @@ Every major module includes a header comment:
|
||||
## Additional Conventions
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```typescript
|
||||
import { env } from '$env/dynamic/private';
|
||||
|
||||
@@ -756,32 +791,37 @@ const tandoorUrl = env.TANDOOR_URL || null;
|
||||
```
|
||||
|
||||
### Date Handling
|
||||
|
||||
ISO8601 strings throughout the application:
|
||||
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```typescript
|
||||
// Preferred
|
||||
async function fetchData() {
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
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);
|
||||
return fetch(url)
|
||||
.then((response) => response.json())
|
||||
.then((data) => data);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user