122 lines
6.2 KiB
Markdown
122 lines
6.2 KiB
Markdown
# MeshiTrack Coding Style & Engineering Guidelines
|
|
|
|
This document establishes the official coding standards, architectural paradigms, and TypeScript guidelines for the MeshiTrack monorepo. These guidelines are designed to enforce clean, testable, object-oriented codebases with pristine type-safety under strict quality gates.
|
|
|
|
---
|
|
|
|
## 🏛️ 1. Encapsulation & Class Member Accessibility
|
|
|
|
To ensure clear visibility boundaries, clean interfaces, and easy maintainability:
|
|
|
|
1. **Mandatory Accessibility Modifiers**: Every field, constructor, and method in a class must explicitly declare its accessibility modifier (`public`, `protected`, or `private`).
|
|
2. **Mandate Explicit Fields**: Classes that participate in dependency injection (e.g. Services, Repositories, Providers) must explicitly declare their properties at the class level rather than relying on TypeScript constructor parameter properties.
|
|
3. **Class Member Ordering**: Group class members in a predictable sequence:
|
|
* Static Fields (Public -> Protected -> Private)
|
|
* Instance Fields (Public -> Protected -> Private)
|
|
* Constructor
|
|
* Public Methods
|
|
* Protected Methods
|
|
* Private Methods
|
|
|
|
### Example:
|
|
```typescript
|
|
export class ShoppingListsService {
|
|
// 1. Explicit, readonly instance properties
|
|
private readonly shoppingListsRepository: ShoppingListsRepository;
|
|
|
|
// 2. Explicit constructor receiving DI payload
|
|
public constructor({ shoppingListsRepository }: Deps) {
|
|
this.shoppingListsRepository = shoppingListsRepository;
|
|
}
|
|
|
|
// 3. Public class methods
|
|
public async list(householdId: string): Promise<ShoppingListDocument[]> {
|
|
return this.shoppingListsRepository.list(householdId);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🛡️ 2. Boundary Types & Return Types
|
|
|
|
To prevent type-drift across software boundaries:
|
|
|
|
1. **Explicit Return Types on Boundary Functions**: All public methods of class instances (Services, Repositories, Providers) and all exported module functions must explicitly declare their return types.
|
|
2. **Relegate Inference to Callbacks**: Local, inline variables and anonymous arrow callbacks (e.g. within array map/filter functions) may utilize automatic type inference for conciseness.
|
|
|
|
### Example:
|
|
```typescript
|
|
// 🟢 Good: Explicitly defined return type on the boundary method
|
|
public async list(householdId: string): Promise<ShoppingList[]> {
|
|
const lists = await this.shoppingListsRepository.list(householdId);
|
|
return lists.filter(item => item.status === 'active'); // Inline inference is okay
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🚫 3. Eradicating `any` & Enforcing Type-Safety
|
|
|
|
TypeScript's `any` type compromises the integrity of our codebase and makes refactoring fragile.
|
|
|
|
1. **No Explicit `any`**: The use of explicit `any` is strictly prohibited throughout the codebase, including in test files.
|
|
2. **Restrict Unsafe Operations**: We enforce strict type-checked rules to prevent the propagation of `any`:
|
|
* Assigning an `any` to a variable/property is blocked (`@typescript-eslint/no-unsafe-assignment`).
|
|
* Reading a property off of an `any` is blocked (`@typescript-eslint/no-unsafe-member-access`).
|
|
* Invoking functions typed as `any` is blocked (`@typescript-eslint/no-unsafe-call`).
|
|
* Returning an `any` from a function is blocked (`@typescript-eslint/no-unsafe-return`).
|
|
3. **Prefer `unknown` for Dynamic Inputs**: Any unchecked external input (e.g., API payloads, database responses, dynamic JSON files) must be typed as `unknown` and validated at the boundary using **Zod schemas** or type guards.
|
|
4. **Type-Safe Mocking**: Instead of casting mocks with `as any`, use highly targeted partial types or precise mocks.
|
|
|
|
---
|
|
|
|
## 🏷️ 4. Naming Conventions & File Structures
|
|
|
|
Uniform naming conventions prevent conflicts and reduce cognitive load:
|
|
|
|
1. **Directories**: Lowercase `kebab-case` (e.g., `shopping-lists/`).
|
|
2. **Files**: `PascalCase` matching the primary class or component exported in the file (e.g., `ShoppingListsService.ts`), ensuring class name and file name are aligned.
|
|
3. **Identifiers**:
|
|
* **PascalCase**: Classes, Interfaces, Type Aliases, Enums.
|
|
* **camelCase**: Properties, Methods, Variables, Parameters, Functions.
|
|
* **UPPER_CASE**: Top-level static read-only constants.
|
|
4. **Prohibit Hungarian Notation**: Do not prefix interfaces with `I` (e.g. write `ShoppingListsRepository`, NOT `IShoppingListsRepository`).
|
|
|
|
---
|
|
|
|
## 🛑 5. Structured Domain Exceptions
|
|
|
|
Exception mapping must be uniform, semantic, and highly testable:
|
|
|
|
1. **Prohibit Generic `Error` Throwing**: The service and business layers must never throw generic `new Error('message')` for operational domain failures.
|
|
2. **Domain-Specific Exceptions**: Throw subclasses of `AppError` (e.g. `NotFoundError`, `ConflictError`, `BadRequestError`) containing semantic properties like status codes and optional structured sub-details.
|
|
3. **Prevent Stack Leakage**: Our custom exceptions map directly to HTTP responses in central middleware, ensuring internal database schemas or runtime details are never leaked to external clients.
|
|
|
|
### Example:
|
|
```typescript
|
|
// 🟢 Good: Highly testable, structured domain-level exception
|
|
if (!updated) {
|
|
throw new NotFoundError('Shopping list not found');
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🧱 6. OOP vs. Functional Boundaries
|
|
|
|
We keep class-based OOP strictly segregated from pure stateless helpers:
|
|
|
|
1. **Stateful Layers -> Classes**: Services, Repositories, and Providers must be class-based to properly encapsulate state, DI, and persistence hooks.
|
|
2. **Stateless Operations -> Standalone Pure Functions**: Math utilities, date/string formatters, and mapping helpers must be written as standalone, exported functions in pure modules.
|
|
3. **No Utility Classes**: Do not write static utility classes (classes composed solely of static helper methods). Use direct ES module exports.
|
|
|
|
---
|
|
|
|
## ⚡ 7. Asynchronous Programming Integrity
|
|
|
|
To prevent background crashes, memory leaks, and silent failures:
|
|
|
|
1. **Mandate `async/await`**: Use `async/await` exclusively instead of Promise chains (`.then()`, `.catch()`).
|
|
2. **Block Floating Promises**: Every Promise must be `await`ed or returned.
|
|
3. **Explicit Voiding for Intentional Background Tasks**: If a task must run in the background (fire-and-forget), it must be explicitly voided (e.g. `void this.trackEvent()`) and handle its exceptions internally to prevent uncaught runtime rejections.
|