fix(RECIPE-0001): complete iteration 0 — automatic model loading and error display fix
This commit is contained in:
792
docs/CODE_STYLE.md
Normal file
792
docs/CODE_STYLE.md
Normal file
@@ -0,0 +1,792 @@
|
||||
# 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:**
|
||||
```typescript
|
||||
// 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:**
|
||||
```typescript
|
||||
// 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:**
|
||||
```typescript
|
||||
// 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:**
|
||||
```typescript
|
||||
// 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:**
|
||||
```typescript
|
||||
// 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
|
||||
```typescript
|
||||
// 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
|
||||
```typescript
|
||||
// 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
|
||||
```typescript
|
||||
// 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
|
||||
```typescript
|
||||
// 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:**
|
||||
```typescript
|
||||
// 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)
|
||||
```typescript
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { queueManager } from '$lib/server/queue/QueueManager';
|
||||
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';
|
||||
import path from 'path';
|
||||
```
|
||||
|
||||
### Export Patterns
|
||||
|
||||
#### Named Exports (Preferred)
|
||||
```typescript
|
||||
// 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
|
||||
```typescript
|
||||
// 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:**
|
||||
```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');
|
||||
* console.log('Queued with ID:', item.id);
|
||||
* ```
|
||||
*/
|
||||
enqueue(url: string): QueueItem {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
**Class Documentation:**
|
||||
```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
|
||||
}
|
||||
```
|
||||
|
||||
**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
|
||||
*/
|
||||
```
|
||||
|
||||
### 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');
|
||||
```
|
||||
|
||||
#### 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TypeScript Patterns
|
||||
|
||||
### Type Safety
|
||||
|
||||
#### Strict Mode Enabled
|
||||
```json
|
||||
// tsconfig.json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Type Annotations
|
||||
```typescript
|
||||
// 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
|
||||
```typescript
|
||||
export type QueueItemStatus =
|
||||
| 'pending'
|
||||
| 'in_progress'
|
||||
| 'completed'
|
||||
| 'failed';
|
||||
|
||||
export type ProcessingPhase =
|
||||
| 'extraction'
|
||||
| 'parsing'
|
||||
| 'uploading';
|
||||
|
||||
export type ProgressEventType =
|
||||
| 'status'
|
||||
| 'method'
|
||||
| 'retry'
|
||||
| 'error'
|
||||
| 'thumbnail'
|
||||
| 'complete';
|
||||
```
|
||||
|
||||
### Generics
|
||||
```typescript
|
||||
// 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)
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let count = $state(0);
|
||||
let items = $state<QueueItem[]>([]);
|
||||
</script>
|
||||
```
|
||||
|
||||
#### $props (Component Props)
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let {
|
||||
recipe = null,
|
||||
tandoorEnabled = false,
|
||||
onRetry,
|
||||
onImportToTandoor
|
||||
} = $props<{
|
||||
recipe: Recipe | null;
|
||||
tandoorEnabled: boolean;
|
||||
onRetry: () => void;
|
||||
onImportToTandoor: () => void;
|
||||
}>();
|
||||
</script>
|
||||
```
|
||||
|
||||
#### $derived (Computed Values)
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let count = $state(0);
|
||||
let double = $derived(count * 2);
|
||||
</script>
|
||||
```
|
||||
|
||||
#### $effect (Side Effects)
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
let url = $state('');
|
||||
|
||||
$effect(() => {
|
||||
console.log('URL changed:', url);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### 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
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Template -->
|
||||
<div>
|
||||
<!-- Markup -->
|
||||
</div>
|
||||
|
||||
<!-- Styles -->
|
||||
<style>
|
||||
/* Component-scoped styles */
|
||||
</style>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Custom Error Classes
|
||||
```typescript
|
||||
// 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
|
||||
```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);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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:**
|
||||
```javascript
|
||||
{
|
||||
rules: {
|
||||
"no-undef": 'off' // TypeScript handles this
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Prettier
|
||||
**Config:** `.prettierrc`
|
||||
|
||||
```json
|
||||
{
|
||||
"useTabs": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Mock Pattern
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```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
|
||||
*/
|
||||
```
|
||||
|
||||
**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
|
||||
*/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Additional Conventions
|
||||
|
||||
### Environment Variables
|
||||
```typescript
|
||||
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:
|
||||
```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;
|
||||
}
|
||||
|
||||
// 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
|
||||
Reference in New Issue
Block a user