# Core Architecture Standards for Deno
This document outlines the core architectural standards and best practices for Deno projects. These standards are designed to promote maintainability, scalability, performance, and security. They focus on fundamental architectural patterns, project structure, and organizational principles within the Deno runtime environment.
## 1. Architectural Patterns
Choosing the right architectural pattern is crucial for building robust and scalable Deno applications. Here are some recommended patterns:
### 1.1 Microservices
Microservices offer independent deployability, scalability, and technology diversity.
**Do This:**
* Design services around specific business capabilities.
* Use lightweight communication protocols such as HTTP/REST or gRPC.
* Implement proper service discovery and registration mechanisms.
* Ensure each service has its own database and data model.
* Utilize Deno's built-in testing framework for thorough unit and integration testing of each service.
**Don't Do This:**
* Create a monolithic application disguised as microservices.
* Share databases between services.
* Introduce tight coupling between services.
* Neglect proper monitoring and logging across all services.
**Why This Matters:**
Microservices enhance agility and allow for independent scaling and deployment. Proper implementation prevents cascading failures and ensures resilience.
**Example:**
"""typescript
// Service A: User service
import { serve } from "https://deno.land/std@0.212.0/http/server.ts";
async function handler(request: Request): Promise {
const url = new URL(request.url);
if (url.pathname === "/users") {
return new Response(JSON.stringify([{ id: 1, name: "Alice" }, { id: 2, name: "Bob"}]), {
headers: { "content-type": "application/json" },
});
}
return new Response("Not Found", { status: 404 });
}
serve(handler, { port: 3000 });
console.log("User service listening on port 3000");
"""
"""typescript
// Service B: Order service
import { serve } from "https://deno.land/std@0.212.0/http/server.ts";
async function handler(request: Request): Promise {
const url = new URL(request.url);
if (url.pathname === "/orders") {
return new Response(JSON.stringify([{ userId: 1, orderId: "A123" }, { userId: 2, orderId: "B456"}]), {
headers: { "content-type": "application/json" },
});
}
return new Response("Not Found", { status: 404 });
}
serve(handler, { port: 3001 });
console.log("Order service listening on port 3001");
"""
### 1.2 Layered Architecture
A layered architecture organizes code into distinct layers with specific responsibilities (e.g., presentation, business logic, data access).
**Do This:**
* Define clear boundaries between layers.
* Enforce the dependency rule: layers can only depend on layers directly below them.
* Use dependency injection to manage dependencies between layers.
* Employ interfaces to decouple layers, promoting testability and flexibility.
**Don't Do This:**
* Create tight coupling between layers.
* Allow layers to directly access data or functionality in other layers without going through a defined interface.
* Implement circular dependencies between layers.
**Why This Matters:**
Layered architecture improves code organization, maintainability, and testability by separating concerns.
**Example:**
"""typescript
// data_access.ts
export interface UserRepository {
getUser(id: number): Promise<{ id: number; name: string } | undefined>;
}
export class InMemoryUserRepository implements UserRepository {
private users: { id: number; name: string }[] = [{ id: 1, name: "John" }, { id: 2, name: "Jane" }];
async getUser(id: number): Promise<{ id: number; name: string } | undefined> {
return this.users.find(user => user.id === id);
}
}
"""
"""typescript
// business_logic.ts
import { UserRepository } from "./data_access.ts";
export class UserService {
private userRepository: UserRepository;
constructor(userRepository: UserRepository) {
this.userRepository = userRepository;
}
async getUserName(id: number): Promise {
const user = await this.userRepository.getUser(id);
return user?.name;
}
}
"""
"""typescript
// presentation.ts
import { UserService, } from "./business_logic.ts";
import { InMemoryUserRepository } from "./data_access.ts";
const userRepository = new InMemoryUserRepository();
const userService = new UserService(userRepository);
async function main() {
const userName = await userService.getUserName(1);
console.log(userName); // Output: John
}
main();
"""
### 1.3 Event-Driven Architecture
Event-Driven Architecture (EDA) facilitates asynchronous communication between services, enabling decoupled and scalable systems.
**Do This:**
* Use message queues (e.g., RabbitMQ, Apache Kafka) for reliable message delivery.
* Define clear event schemas (e.g., using JSON Schema).
* Implement idempotent consumers to handle duplicate messages.
* Monitor event flows and handle potential errors gracefully.
**Don't Do This:**
* Create tight coupling between event producers and consumers.
* Rely on synchronous communication for critical operations.
* Ignore event sequencing and ordering requirements.
**Why This Matters:**
EDA promotes scalability, resilience, and responsiveness by decoupling services and enabling asynchronous communication.
**Example (using an in-memory event bus for simplicity – not production-ready):**
"""typescript
// event_bus.ts
type EventHandler = (event: T) => void;
class EventBus {
private handlers: { [key: string]: EventHandler[] } = {};
subscribe(event: string, handler: EventHandler) {
if (!this.handlers[event]) {
this.handlers[event] = [];
}
this.handlers[event].push(handler);
}
publish(event: string, data: T) {
const handlers = this.handlers[event];
if (handlers) {
handlers.forEach(handler => handler(data));
}
}
}
export const eventBus = new EventBus();
"""
"""typescript
// user_service.ts
import { eventBus } from "./event_bus.ts";
interface UserCreatedEvent {
userId: number;
userName: string;
}
class UserService {
createUser(userId: number, userName: string) {
// ... create user logic ...
const event: UserCreatedEvent = { userId, userName };
eventBus.publish("user.created", event);
console.log("User created event published for user ${userId}");
}
}
export const userService = new UserService();
"""
"""typescript
// email_service.ts
import { eventBus } from "./event_bus.ts";
interface UserCreatedEvent {
userId: number;
userName: string;
}
class EmailService {
constructor() {
eventBus.subscribe("user.created", (event) => {
console.log("Sending welcome email to ${event.userName} (user ID: ${event.userId})");
// ... email sending logic ...
});
}
}
export const emailService = new EmailService();
"""
"""typescript
// main.ts
import { userService } from "./user_service.ts";
import { emailService } from "./email_service.ts"; // Ensure email service subscribes
userService.createUser(123, "Alice"); // This will trigger the email service.
"""
## 2. Project Structure
A well-defined project structure is essential for code discoverability and maintainability.
### 2.1 Recommended Structure
"""
my-deno-project/
├── src/ # Source code
│ ├── controllers/ # Handles HTTP requests and responses
│ ├── services/ # Business logic
│ ├── repositories/ # Data access layer
│ ├── models/ # Data models and interfaces
│ ├── utils/ # Reusable utility functions
│ ├── middleware/ # Middleware functions
│ ├── config/ # Configuration files
│ ├── app.ts # Main application entry point
│ └── routes.ts # HTTP route definitions
├── tests/ # Unit and integration tests
│ ├── controllers/
│ ├── services/
│ └── ...
├── .env # Environment variables
├── deno.jsonc # Deno configuration file
├── README.md # Project documentation
└── .gitignore # Ignored files
"""
**Do This:**
* Organize code into logical directories based on functionality.
* Use clear and descriptive names for files and directories.
* Keep related files together.
* Use "src" directory for the application source code.
* Include a "tests" directory alongside the "src" directory.
**Don't Do This:**
* Place all files in a single directory.
* Use cryptic or ambiguous names.
* Mix different types of files in the same directory.
* Commit sensitive information (like API keys) to the code repository. Use ".env" and load environment variables.
**Why This Matters:**
A consistent and well-structured project makes it easier for developers to navigate, understand, and maintain the codebase.
**Example:**
"deno.jsonc":
"""jsonc
{
"fmt": {
"files": [
"src/",
"tests/"
],
"options": {
"useTabs": false,
"lineWidth": 120,
"indentWidth": 2,
"singleQuote": true,
"proseWrap": "always"
}
},
"lint": {
"files": [
"src/",
"tests/"
],
"rules": {
"tags": [
"recommended"
]
}
},
"tasks": {
"start": "deno run --allow-net --allow-read src/app.ts",
"test": "deno test --allow-read --allow-net tests/"
},
"importMap": "import_map.json"
}
"""
"import_map.json":
"""json
{
"imports": {
"oak/": "https://deno.land/x/oak@v12.6.1/",
"std/": "https://deno.land/std@0.212.0/"
}
}
"""
### 2.2 Modularization
Splitting code into modules improves reusability and maintainability.
**Do This:**
* Break down complex functionalities into smaller, self-contained modules.
* Export only the necessary symbols from each module.
* Use descriptive names for modules.
* Employ Deno's module import syntax (e.g., "import { ... } from "./module.ts";").
* Favor explicit imports over wildcard imports ("import * as ...").
**Don't Do This:**
* Create large, monolithic modules.
* Export unnecessary symbols.
* Create circular dependencies between modules.
**Why This Matters:**
Modularization promotes code reusability, testability, and maintainability by isolating functionality and reducing dependencies.
**Example:**
"""typescript
// src/utils/string_utils.ts
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function reverse(str: string): string {
return str.split("").reverse().join("");
}
"""
"""typescript
// src/services/greeting_service.ts
import { capitalize } from "../utils/string_utils.ts";
export function greet(name: string): string {
return "Hello, ${ capitalize(name) }!";
}
"""
"""typescript
// src/controllers/greeting_controller.ts
import { greet } from "../services/greeting_service.ts";
import { Context } from "oak/mod.ts";
export const greetingController = async (ctx: Context) => {
const name = ctx.params.name || "World";
ctx.response.body = greet(name);
};
"""
## 3. Dependency Management
Managing dependencies effectively is crucial for project stability and security.
### 3.1 Import Maps
Use import maps to manage external dependencies and improve code readability. Deno promotes the use of explicit versions for dependencies.
**Do This:**
* Define dependencies in an "import_map.json" file.
* Use logical names for dependencies (e.g., "oak/" instead of the full URL).
* Update dependencies regularly to address security vulnerabilities and bug fixes.
* Pin dependency versions for reproducible builds.
**Don't Do This:**
* Use bare import specifiers.
* Mix relative and absolute imports.
* Ignore security updates. Don't use wildcard versions (e.g. "*", "latest").
**Why This Matters:**
Import maps enhance code readability, simplify dependency management, and improve security by controlling the versions of imported modules.
**Example:**
See the "deno.jsonc" example above for how "import_map.json" is referenced. See the "import_map.json" example above for its basic structure.
### 3.2 Dependency Injection
Use dependency injection to decouple components and improve testability.
**Do This:**
* Use constructor injection or setter injection.
* Define interfaces for dependencies.
* Use a dependency injection framework (e.g., "dIContainer") for complex applications.
**Don't Do This:**
* Create tight coupling between components.
* Use global state or singletons excessively.
* Instantiate dependencies directly within components.
**Why This Matters:**
Dependency injection promotes loose coupling, improves testability, and allows for easier component substitution.
**Example:**
"""typescript
// logger.ts
export interface Logger {
log(message: string): void;
}
export class ConsoleLogger implements Logger {
log(message: string): void {
console.log(message);
}
}
"""
"""typescript
// app.ts
import { Logger, ConsoleLogger } from "./logger.ts";
class MyApp {
private logger: Logger;
constructor(logger: Logger) {
this.logger = logger;
}
run() {
this.logger.log("Application started");
}
}
const logger = new ConsoleLogger();
const app = new MyApp(logger);
app.run();
"""
## 4. Error Handling
Robust error handling is critical for application stability.
### 4.1 Explicit Error Handling
Handle errors explicitly using "try...catch" blocks and "Result" types.
**Do This:**
* Use "try...catch" blocks to handle potential errors.
* Wrap asynchronous operations in "try...catch" blocks.
* Consider using a "Result" type (either a custom implementation or a library like "neverthrow") to represent success or failure.
* Log errors with sufficient context (e.g., timestamp, error message, stack trace).
* Implement graceful error handling at the application level (e.g., display a user-friendly error message).
**Don't Do This:**
* Ignore errors or swallow exceptions.
* Rely solely on unhandled promise rejections.
* Expose sensitive error information to the client.
**Why This Matters:**
Explicit error handling prevents application crashes, provides valuable debugging information, and ensures a better user experience.
**Example:**
"""typescript
async function fetchData(url: string): Promise {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("HTTP error! Status: ${response.status}");
}
const data = await response.text();
return data;
} catch (error) {
console.error("Error fetching data:", error);
return error instanceof Error ? error : new Error(String(error));
}
}
async function main() {
const result = await fetchData("https://example.com");
if (result instanceof Error) {
console.error("Failed to fetch data:", result.message);
} else {
console.log("Data:", result);
}
}
main();
"""
### 4.2 Custom Error Types
Define custom error types to provide more specific error information.
**Do This:**
* Create custom error classes that extend the built-in "Error" class.
* Include additional properties in custom error types to provide more context (e.g., error code, resource ID).
* Use discriminated unions for error types to simplify error handling.
**Don't Do This:**
* Rely solely on generic "Error" objects.
* Throw primitive values as errors.
**Why This Matters:**
Custom error types improve code readability, simplify error handling, and provide more context for debugging.
**Example:**
"""typescript
class UserNotFoundError extends Error {
constructor(public userId: number, message: string = "User with ID ${userId} not found") {
super(message);
this.name = "UserNotFoundError";
}
}
async function getUser(id: number): Promise<{ id: number; name: string } | UserNotFoundError> {
// Simulate user lookup
if (id !== 1) {
return new UserNotFoundError(id);
}
return { id: 1, name: "John" };
}
async function main() {
const user = await getUser(2);
if (user instanceof UserNotFoundError) {
console.error("Error: ${user.message}");
} else {
console.log("User:", user);
}
}
main();
"""
## 5. Security
Security is paramount in any application.
### 5.1 Secure Coding Practices
Follow secure coding practices to prevent common vulnerabilities.
**Do This:**
* Sanitize user inputs to prevent injection attacks (e.g., SQL injection, XSS).
* Use prepared statements or parameterized queries for database interactions.
* Implement proper authentication and authorization mechanisms.
* Enforce the principle of least privilege.
* Use HTTPS for all communication to protect data in transit.
* Keep dependencies up to date to address security vulnerabilities.
* Use a linter with security rules enabled.
**Don't Do This:**
* Trust user inputs without validation.
* Store sensitive information in plain text.
* Expose sensitive information in error messages.
* Disable security features or ignore security warnings.
**Why This Matters:**
Secure coding practices protect applications from common vulnerabilities and prevent data breaches.
**Example (input sanitization using a library):**
"""typescript
import { escape } from "https://deno.land/x/escape@v2.0.0/mod.ts";
function sanitizeInput(input: string): string {
return escape(input);
}
const userInput = "";
const sanitizedInput = sanitizeInput(userInput);
console.log("Sanitized input:", sanitizedInput); // Output: <script>alert('XSS');</script>
"""
### 5.2 Deno Permissions
Leverage Deno's permission system to restrict access to system resources.
**Do This:**
* Grant only the necessary permissions to each application.
* Use specific permissions instead of broad permissions (e.g., "--allow-read=/path/to/file" instead of "--allow-read").
* Review permissions regularly.
* Use "--deny" flags during development to identify missing permissions.
**Don't Do This:**
* Grant all permissions ("--allow-all").
* Ignore permission warnings.
* Run untrusted code with elevated permissions.
**Why This Matters:**
Deno's permission system limits the impact of security vulnerabilities by restricting access to sensitive system resources.
**Example:**
Running a Deno script with the "--allow-net" flag:
"""bash
deno run --allow-net=example.com src/app.ts
"""
This allows the script to make network requests only to "example.com". Any attempt to make requests to other domains will be blocked.
## 6. Testing
Comprehensive testing is essential for ensuring code quality.
### 6.1 Unit Tests
Write unit tests to verify the functionality of individual components.
**Do This:**
* Use Deno's built-in testing framework ("deno test").
* Write tests for all critical code paths.
* Use mocking and stubbing to isolate components during testing.
* Follow the Arrange-Act-Assert pattern.
* Aim for high test coverage.
**Don't Do This:**
* Write tests that are too complex or tightly coupled to the implementation.
* Skip testing error handling logic.
* Ignore failing tests.
**Why This Matters:**
Unit tests provide early feedback on code quality, prevent regressions, and improve code maintainability.
**Example:**
"""typescript
// src/utils/math.ts
export function add(a: number, b: number): number {
return a + b;
}
"""
"""typescript
// tests/utils/math_test.ts
import { add } from "../../src/utils/math.ts";
import { assertEquals } from "https://deno.land/std@0.212.0/assert/mod.ts";
Deno.test("add() should return the sum of two numbers", () => {
assertEquals(add(2, 3), 5);
assertEquals(add(-1, 1), 0);
assertEquals(add(0, 0), 0);
});
"""
### 6.2 Integration Tests
Write integration tests to verify the interaction between multiple components.
**Do This:**
* Test the integration between different modules, services, or systems.
* Use real or mock dependencies for integration testing.
* Verify data flow and communication between components.
* Test error handling and edge cases.
**Don't Do This:**
* Skip integration tests.
* Rely solely on unit tests.
* Make integration tests too brittle or dependent on external factors.
**Why This Matters:**
Integration tests verify that components work together correctly, ensuring that the system functions as a whole.
## 7. Documentation
Comprehensive documentation is crucial for code maintainability and onboarding new developers.
### 7.1 Code Comments
Write clear and concise code comments to explain complex logic and implementation details.
**Do This:**
* Use JSDoc-style comments for documenting functions, classes, and interfaces.
* Explain the purpose, parameters, and return values of functions.
* Document complex algorithms and data structures.
* Update comments when the code changes.
**Don't Do This:**
* Write redundant or obvious comments.
* Use comments to explain poorly written code. Refactor instead.
* Let comments become outdated.
**Why This Matters:**
Code comments improve code readability and make it easier for developers to understand and maintain the codebase. As the linked Deno Style Guide states: "We strive for complete documentation. Every exported symbol ideally should have a JSDoc comment."
**Example:**
"""typescript
/**
* Adds two numbers together.
*
* @param a - The first number.
* @param b - The second number.
* @returns The sum of the two numbers.
*/
export function add(a: number, b: number): number {
return a + b;
}
"""
### 7.2 Project Documentation
Create project-level documentation to provide an overview of the application architecture, design, and usage.
**Do This:**
* Include a README file with a description of the project, setup instructions, and usage examples.
* Use a documentation generator (e.g., Deno doc) to generate API documentation from JSDoc comments.
* Document the application architecture, design patterns, and key decisions.
* Provide examples of how to use the application or library.
**Don't Do This:**
* Skip project-level documentation.
* Let documentation become outdated.
* Assume that developers can understand the application without documentation.
**Why This Matters:**
Project documentation provides a comprehensive overview of the application, making it easier for developers to understand, use, and maintain the codebase.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Security Best Practices Standards for Deno This document outlines security best practices for developing and maintaining Deno applications. It is intended to guide developers in writing secure, maintainable, and performant code. These standards are designed to be used as context for AI coding assistants like GitHub Copilot, Cursor, and similar tools. ## I. General Security Principles ### A. Principle of Least Privilege * **Do This:** Grant access to resources and functionalities only to those who absolutely need it. Avoid overly permissive configurations or roles. Deno's permission system makes this easy. * **Don't Do This:** Run Deno scripts without proper permissions. Providing "--allow-all" to every script is dangerous and defeats Deno's built-in security model. * **Why:** Minimizes the impact of a potential security breach. If an attacker gains access to a privileged account, the damage they can do is limited by the permissions granted to that account. * **Example:** """typescript // Good: Only allow network access to example.com // deno run --allow-net=example.com script.ts // Bad: Allowing all network access // deno run --allow-net script.ts // Good: Only allow reading specific files // deno run --allow-read=/path/to/allowed/file script.ts """ ### B. Defense in Depth * **Do This:** Implement multiple layers of security controls. Don't rely on a single security mechanism. * **Don't Do This:** Assume that perimeter security is sufficient. Ensure that internal systems and data are protected as well. * **Why:** If one security layer fails, other layers are in place to prevent a complete compromise. * **Example:** * Use HTTPS for all communication. * Validate user input to prevent injection attacks. * Implement rate limiting to prevent brute-force attacks. * Regularly audit your code and infrastructure for security vulnerabilities. ### C. Secure Defaults * **Do This:** Configure systems and applications with the most secure settings by default. * **Don't Do This:** Rely on default configurations that may be insecure or expose sensitive information. * **Why:** Reduces the likelihood of misconfiguration and ensures that security is the default behavior. * **Example:** * Disable directory listing on web servers. * Use strong, unique passwords for all accounts. * Configure firewalls to block all inbound traffic by default. * Use Deno's secure-by-default permission model and explicitly grant permissions. ## II. Specific Security Considerations in Deno ### A. Permissions System * **Do This:** Carefully define the required permissions for each Deno script. Use granular permissions (e.g., specific file paths for "--allow-read" and "--allow-write", specific domains for "--allow-net"). * **Don't Do This:** Use "--allow-all" unless absolutely necessary, and only for trusted code. * **Why:** Deno's permission system provides a strong sandbox that limits the capabilities of untrusted code. Using "--allow-all" bypasses this security feature entirely. * **Example:** """typescript // Good: Only allow reading a specific file. // deno run --allow-read=/etc/passwd my_script.ts // Good: Allow network access only to specific domains. // deno run --allow-net=api.example.com,cdn.example.com my_script.ts // Good: Narrowing permissions down further. // deno run --allow-net=tcp://api.example.com:443 my.script.ts // Good: Using prompt for necessary permissions and gracefully handling user denial // deno-lint-ignore-file no-explicit-any async function readFile() { try { const file = await Deno.readTextFile("./example.txt"); console.log(file) return file } catch (error: any) { if (error instanceof Deno.errors.PermissionDenied) { console.error("Permission denied. Please run with --allow-read=./example.txt"); } else { console.error("An unexpected error occurred:", error); } } } // Bad: Allowing all permissions (effectively disables Deno's security). // deno run --allow-all my_script.ts //my_script.ts const data = await Deno.readTextFile("./example.txt"); // Requires --allow-read console.log(data); """ ### B. Dependency Management * **Do This:** Use explicit versioning for all dependencies in your "deno.json" or import maps. Regularly audit your dependencies for security vulnerabilities. Consider using a dependency pinning tool for added security. * **Don't Do This:** Use unpinned dependencies (e.g., importing directly from "jsr:" without specifying a version). This can lead to unpredictable behavior and security vulnerabilities if a dependency is updated with breaking changes or malicious code. * **Why:** Unpinned dependencies can introduce unexpected changes into your application, including security vulnerabilities. Explicit versioning provides stability and allows you to control the dependencies used in your project. * **Example:** """json // deno.json { "imports": { "oak": "jsr:@oak/oak@^12.6.1", "std/": "jsr:@std/" }, "tasks": { "start": "deno run --allow-net --allow-read server.ts" } } """ """typescript // server.ts import { Application, Router } from "oak"; const app = new Application(); const router = new Router(); router.get("/", (context) => { context.response.body = "Hello Deno!"; }); app.use(router.routes()); app.use(router.allowedMethods()); await app.listen({ port: 8000 }); """ ### C. Web Security * **Do This:** Implement standard web security practices, such as: * **Output Encoding:** Properly encode data when displaying it to prevent XSS attacks. * **Input Validation:** Validate and sanitize all user input to prevent injection attacks (SQL injection, command injection, etc.). * **CSRF Protection:** Implement CSRF protection for state-changing operations. * **HTTPS:** Always use HTTPS to encrypt communication between the client and server. * **CORS:** Configure CORS properly to prevent unauthorized cross-origin requests. * **Don't Do This:** Trust user input without validation. Expose sensitive data in URLs or request parameters. * **Why:** Web applications are a common target for attackers. Implementing these security practices helps to protect against a wide range of web-based attacks. * **Example:** """typescript // Input Validation (example with oak framework) import { Application, Router, Context } from "jsr:@oak/oak@^12.6.1"; const app = new Application(); const router = new Router(); interface User { name: string; email: string; } const users: User[] = []; function isValidEmail(email: string): boolean { // Basic email validation regex. Consider using a more robust library for production. const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } router.post("/users", async (ctx) => { try { const result = ctx.request.body({ type: 'json' }); const body = await result.value; if (!body.name || !body.email) { ctx.response.status = 400; ctx.response.body = { message: "Name and email are required." }; return; } if (typeof body.name !== 'string') { ctx.response.status = 400; ctx.response.body = { message: "Name must be a string." }; return; } if (typeof body.email !== 'string' || !isValidEmail(body.email)) { ctx.response.status = 400; ctx.response.body = { message: "Invalid email format." }; return; } const newUser: User = { name: body.name, email: body.email }; users.push(newUser); ctx.response.status = 201; ctx.response.body = { message: "User created successfully!", user: newUser }; } catch (e) { console.error("Error parsing request body:", e); ctx.response.status = 400; ctx.response.body = { message: "Invalid request body." }; } }); app.use(router.routes()); app.use(router.allowedMethods()); console.log('Server running on port 8000'); await app.listen({ port: 8000 }); """ ### D. Environment Variables and Secrets Management * **Do This:** Use environment variables for configuration and secrets management. Avoid hardcoding sensitive information in your code. Use a tool like "dotenv" to manage environment variables during development. For production, use a secure secret store (e.g., HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager). * **Don't Do This:** Store secrets in your source code or version control system. * **Why:** Environment variables provide a secure and flexible way to manage configuration and secrets. They allow you to change configuration without modifying your code. * **Example:** """typescript // Good: Using environment variables // .env file (never commit this to version control) // API_KEY=your_api_key // DATABASE_URL=your_database_url // main.ts import { config } from "dotenv"; config({ export: true, path: ".env" }); // Load .env file (for local development) const apiKey = Deno.env.get("API_KEY"); console.log("The api key is",apiKey); // In production, ensure the environment variables are set in the deployment environment if (!apiKey) { console.error("API_KEY environment variable not set!"); Deno.exit(1); } // Use apiKey in your application """ ### E. File System Access * **Do This:** Be extremely careful when handling user-provided file paths or file names. Validate and sanitize all input to prevent path traversal attacks and arbitrary file writes. Use Deno's permission system to restrict file system access to only the necessary locations. * **Don't Do This:** Allow users to specify arbitrary file paths or file names. * **Why:** Path traversal attacks can allow attackers to access sensitive files or execute arbitrary code on your system. * **Example:** """typescript // Good: Validating and sanitizing file paths const basePath = "/app/data"; async function readFile(userInput: string) { // Sanitize the user input to prevent path traversal. const sanitizedInput = userInput.replace(/[^a-zA-Z0-9._-]/g, ""); const filePath = Deno.realPathSync(Deno.join(basePath, sanitizedInput)); // Verify that the file path is within the allowed base path. if (!filePath.startsWith(Deno.realPathSync(basePath))) { console.error("Invalid file path. Path traversal attempt detected."); return null; } try { return await Deno.readTextFile(filePath); } catch (error) { console.error("Error reading file:", error); return null; } } // Example usage (run with --allow-read=/app/data) const fileContent = await readFile("user_data.txt"); if (fileContent) { console.log("File content:", fileContent); } // Bad: Directly using user input without validation (vulnerable to path traversal). // const filePath = Deno.join(basePath, userInput); // const fileContent = await Deno.readTextFile(filePath); """ ### F. Denial of Service (DoS) Prevention * **Do This:** Implement rate limiting, request size limits, and connection limits to prevent DoS attacks. Use appropriate timeouts to prevent resource exhaustion. Protect against Slowloris and other slow-attack variants by setting short timeouts for incomplete requests. * **Don't Do This:** Allow unlimited requests or connections. * **Why:** DoS attacks can overwhelm your server and make it unavailable to legitimate users. * **Example:** """typescript // Rate limiting (example with oak) import { Application, Router, Context } from "jsr:@oak/oak@^12.6.1"; import { RateLimit } from "npm:@upstash/ratelimit@latest" import { Redis } from "npm:@upstash/redis" const redis = new Redis({ url: Deno.env.get("UPSTASH_REDIS_REST_URL") ?? "", token: Deno.env.get("UPSTASH_REDIS_REST_TOKEN") ?? "", }) const ratelimit = new RateLimit({ redis, limiter: RateLimit.slidingWindow(10, "10 s"), analytics: true, }) const router = new Router(); router .get("/", async (context) => { const ip = context.request.ip; const { success, pending, limit, reset, remaining } = await ratelimit.limit(ip ?? "anonymous"); context.response.headers.set("X-RateLimit-Limit", limit.toString()); context.response.headers.set("X-RateLimit-Remaining", remaining.toString()); context.response.headers.set("X-RateLimit-Reset", new Date(reset).toISOString()); if (!success) { context.response.status = 429; context.response.body = "Too Many Requests. Please try again after " + new Date(reset).toISOString(); return; } context.response.body = "Hello Deno!"; }); const app = new Application(); app.use(router.routes()); app.use(router.allowedMethods()); console.log('Server running on port 8000'); await app.listen({ port: 8000 }); """ ### G. Third-Party Libraries * **Do This:** Evaluate third-party libraries carefully before using them in your project. Check for known vulnerabilities, review the code for potential security issues, and ensure that the library is actively maintained. * **Don't Do This:** Blindly trust third-party libraries without proper evaluation. * **Why:** Third-party libraries can introduce security vulnerabilities into your application. ### H. Auditing and Logging * **Do This:** Implement comprehensive logging to track important events and detect security incidents. Regularly audit your logs to identify suspicious activity. * **Don't Do This:** Log sensitive information (passwords, API keys, etc.). Disable logging in production. * **Why:** Logging provides valuable information for security monitoring, incident response, and forensics. * **Example:** """typescript // Logging (example using Deno's built-in logging module) import * as log from "https://deno.land/std@0.212.0/log/mod.ts"; await log.setup({ handlers: { console: new log.ConsoleHandler("INFO"), file: new log.FileHandler("INFO", { filename: "./app.log", formatter: "{datetime} {levelName} {msg}", }), }, loggers: { default: { level: "INFO", handlers: ["console", "file"], }, }, }); const logger = log.getLogger(); try { // Some application logic here logger.info("Application started successfully."); // ... } catch (error) { logger.error("An error occurred:", error); } """ ### I. Data Encryption * **Do This:** Encrypt sensitive data at rest and in transit. Use strong encryption algorithms and key management practices. For data in transit, always use HTTPS (TLS). For data at rest, consider using Deno's "crypto" API or external encryption libraries like "sodium-native". * **Don't Do This:** Store sensitive data in plaintext. Use weak or outdated encryption algorithms. * **Why:** Encryption protects sensitive data from unauthorized access. * **Example:** """typescript // Encryption using Deno's crypto API const encoder = new TextEncoder(); const decoder = new TextDecoder(); async function encrypt(data: string, key: CryptoKey): Promise<string> { const encodedData = encoder.encode(data); const iv = crypto.getRandomValues(new Uint8Array(12)); // Initialization Vector const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv: iv, }, key, encodedData); // Combine IV and encrypted data for storage/transmission const encryptedArray = new Uint8Array(encrypted); const combined = new Uint8Array(iv.length + encryptedArray.length); combined.set(iv, 0); combined.set(encryptedArray, iv.length); return btoa(String.fromCharCode(...combined)); // Base64 encode } async function decrypt(encryptedBase64: string, key: CryptoKey): Promise<string> { const combined = new Uint8Array(atob(encryptedBase64).split("").map(c => c.charCodeAt(0))); const iv = combined.slice(0, 12); const encryptedArray = combined.slice(12); const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv: iv, }, key, encryptedArray); return decoder.decode(decrypted); } async function generateKey(): Promise<CryptoKey> { return await crypto.subtle.generateKey({ name: "AES-GCM", length: 256, }, true, ["encrypt", "decrypt"]); } async function example() { const key = await generateKey(); const originalData = "Sensitive data to be encrypted"; const encryptedData = await encrypt(originalData, key); console.log("Encrypted:", encryptedData); const decryptedData = await decrypt(encryptedData, key); console.log("Decrypted:", decryptedData); // Should match originalData } example(); """ ## III. Security Testing and Code Review ### A. Static Analysis * **Do This:** Use static analysis tools (e.g., Deno's built-in linter, SonarQube) to identify potential security vulnerabilities in your code. * **Don't Do This:** Rely solely on manual code review for security analysis. * **Why:** Static analysis tools can automatically detect common security flaws, such as SQL injection vulnerabilities, XSS vulnerabilities, and insecure configurations. """json // deno.json configuration including linting and formatting { "fmt": { "lineWidth": 120, "indentWidth": 2, "useTabs": false, "semiColons": true, "singleQuote": false, "proseWrap": "always" }, "lint": { "rules": { "tags": [ "recommended", "security" ] } }, "tasks": { "fmt": "deno fmt", "lint": "deno lint", "test": "deno test --allow-read --allow-net", "start": "deno run --allow-net --allow-read server.ts" }, "imports": { "oak": "jsr:@oak/oak@^12.6.1", "std/": "jsr:@std/" } } """ ### B. Dynamic Analysis * **Do This:** Perform dynamic analysis (e.g., penetration testing, fuzzing) to identify runtime security vulnerabilities. * **Don't Do This:** Assume that your application is secure simply because it passes static analysis. * **Why:** Dynamic analysis can uncover vulnerabilities that are not detectable by static analysis, such as race conditions, memory leaks, and heap overflows. ### C. Code Review * **Do This:** Conduct thorough code reviews to identify potential security vulnerabilities and ensure that code adheres to security best practices. Involve security experts in the code review process. * **Don't Do This:** Skip code reviews or treat them as a formality. * **Why:** Code review is a critical step in the software development lifecycle that can help to prevent security vulnerabilities from being introduced into production. ## IV. Incident Response ### A. Create an Incident Response Plan * **Do This:** Develop and maintain a comprehensive incident response plan that outlines the steps to take in the event of a security incident. * **Don't Do This:** Wait until an incident occurs to start planning your response. * **Why:** A well-defined incident response plan can help you to quickly and effectively contain and mitigate the impact of a security incident. ### B. Monitor for Security Incidents * **Do This:** Implement continuous monitoring to detect and respond to security incidents in a timely manner. * **Don't Do This:** Assume that your systems are secure and that you will not be targeted by attackers. * **Why:** Continuous monitoring can help you to identify and respond to security incidents before they cause significant damage. ### C. Practice and Test Your Incident Response Plan * **Do This:** Regularly practice and test your incident response plan to ensure that it is effective and that your team is prepared to respond to security incidents. * **Don't Do This:** Assume that your incident response plan will work without testing it. * **Why:** Regular testing can help you to identify weaknesses in your incident response plan and improve your team's ability to respond to security incidents. By following these security best practices, you can significantly reduce the risk of security vulnerabilities in your Deno applications and protect your systems and data from attack. Remember that security is an ongoing process, and you should continuously monitor and improve your security posture.
# State Management Standards for Deno This document outlines the coding standards for state management in Deno applications. It aims to provide a clear and consistent approach to managing application state, data flow, and reactivity, ensuring maintainability, performance, and security. ## 1. Introduction to State Management in Deno State management is a crucial aspect of any application, especially as complexity grows. In Deno, choosing the right strategy and tools can significantly impact performance, scalability, and developer experience. Unlike the browser environment, Deno gives developers more control over the runtime and access to lower-level APIs. Because of this, the state management solutions often need to be re-evaluated. This document covers several acceptable approaches, ranging from simple component-level state to more comprehensive solutions for large applications. We advocate for clarity, predictability, and maintainability in managing state. ## 2. Core Principles * **Single Source of Truth:** Each piece of state should have a single, authoritative source. This avoids inconsistencies and makes debugging easier. * **Immutability:** Treat state as immutable whenever possible. Modifying state by creating new objects, rather than mutating existing ones, simplifies reasoning about state changes and enables efficient change detection. * **Explicit Data Flow:** Make data dependencies and state changes explicit and predictable. Avoid implicit or hidden state mutations. * **Separation of Concerns:** Keep state management logic separate from UI rendering and business logic to improve maintainability and testability. * **Minimization:** Only store the necessary state. Avoid storing derived or computed values directly, instead calculating them on demand. ## 3. Approaches to State Management ### 3.1. Simple Component-Level State For smaller components or applications, simple state management techniques can suffice. This involves using variables within a component or module to hold the state. **Do This:** * Use "let" or "const" to declare state variables within a component or module. * Employ simple event handlers or functions to update the state directly. * Consider using "useState" if integrating with a frontend framework like React (via Fresh). * For simple data dependencies, consider using computed properties if your framework allows (e.g., Fresh). **Don't Do This:** * Avoid creating global variables to store application state directly. This makes the state difficult to track and manage. * Don't mutate state directly without triggering updates in your UI framework (if applicable). Direct mutation can lead to stale data and unexpected behavior. * Avoid deeply nested or complex state objects if simpler alternatives exist. **Example (Vanilla Deno - No Framework Usage):** """typescript // counter.ts let count = 0; export function increment() { count++; console.log("Count:", count); // Notify any UI components that are tracking this state (if applicable) } export function getCount() { return count; } // main.ts import { increment, getCount } from "./counter.ts"; increment(); increment(); console.log("Current count:", getCount()); """ **Why:** Using local variables encapsulates the state within a module, preventing unintended modifications from other parts of the application. Functions like "increment" provide a controlled way to update the state, offering an opportunity to trigger UI updates or other side effects. **Common Mistakes:** * Forgetting to export state update functions. * Modifying state outside its defining module. ### 3.2. Using Signals (Preact Signals, Effector, etc.) Signals offer a reactive approach to state management where changes to a signal automatically trigger updates in components or other reactive expressions. They are efficient and granular, only updating when the signal's value changes. **Do This:** * Choose a signals library suitable for your needs (Preact Signals, Effector, etc.). Preact Signals is well-suited for fine-grained component updates within a Preact/Fresh application. * Create signals to hold your state values. * Use computed signals to derive values from other signals. * Update signals using the ".value" property. * Utilize effects to perform side effects in response to signal changes. **Don't Do This:** * Overuse signals. Not every variable needs to be a signal. Focus on data that requires reactive updates. * Mutate signal values directly without using the ".value" property. * Create circular dependencies between computed signals. **Example (Preact Signals with Deno Fresh):** """tsx // app.tsx (Deno Fresh component) import { signal, computed } from "@preact/signals"; import { useState } from "preact/hooks"; const count = signal(0); const doubledCount = computed(() => count.value * 2); export default function Counter() { const [localState, setLocalState] = useState(0); const increment = () => { count.value++; }; const incrementLocal = () => { setLocalState(localState + 1); } return ( <div> <p>Count: {count.value}</p> <p>Doubled Count: {doubledCount.value}</p> <button onClick={increment}>Increment Count</button> <p>Local State: {localState}</p> <button onClick={incrementLocal}>Increment Local State</button> </div> ); } """ **Why:** Signals provide a more efficient way to update components than traditional virtual DOM diffing. Only components that depend on a changed signal will re-render. Computed signals automatically update when their dependencies change, ensuring values are always consistent. **Common Mistakes:** * Forgetting to include "@preact/signals" in your Fresh project. "deno add @preact/signals" solves this. * Trying to directly modify signal values (e.g. "count = 5;" instead of "count.value = 5;"). * Over-relying on signals for values that don't genuinely need to be reactive, which can lead to unnecessary performance overhead. ### 3.3. Context API (React-like Approach) The Context API allows you to share state between components without explicitly passing props through every level of the component tree. This is useful for themes, user authentication, or other global state. **Do This:** * Create a context using "React.createContext()" (if using React/Preact through Fresh/Islands architecture) * Create a provider component using "context.Provider" to wrap the part of the application that needs access to the context. * Access the context value using "React.useContext(context)". (if using React/Preact through Fresh/Islands architecture) **Don't Do This:** * Overuse the Context API. It's best suited for data that is used by many components, not for isolated component state. * Mutate the context value directly within components. Use a separate state management solution (e.g., "useState", signals, or a more robust library) to manage the context value. **Example (Deno Fresh Context API - note the requirement for React/Preact dependencies):** """tsx // context.ts import React, { createContext, useState, useContext } from "preact/compat"; interface AuthContextType { user: string | null; login: (user: string) => void; logout: () => void; } const AuthContext = createContext<AuthContextType>({ user: null, login: () => {}, logout: () => {}, }); export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState<string | null>(null); const login = (user: string) => { setUser(user); }; const logout = () => { setUser(null); }; const value: AuthContextType = { user, login, logout, }; return ( <AuthContext.Provider value={value}> {children} </AuthContext.Provider> ); } export function useAuth() { return useContext(AuthContext); } // _layout.tsx (wrapping the app) /** @jsx h */ import { h } from 'preact'; import { AuthProvider } from "./context.ts"; export default function Layout({ children }: { children: React.ReactNode }) { return ( <AuthProvider> {children} </AuthProvider> ); } // profile.tsx (using the context) /** @jsx h */ import { h } from 'preact'; import { useAuth } from "./context.ts"; export default function Profile() { const { user, logout } = useAuth(); if (!user) { return <p>Please log in.</p>; } return ( <div> <p>Logged in as: {user}</p> <button onClick={logout}>Logout</button> </div> ); } """ **Why:** The Context API simplifies prop drilling and avoids passing props through intermediate components that don't need them. It's also useful for global state that is shared across the entire application. **Common Mistakes:** * Creating too many contexts. It's better to group related state into a single context than to create a separate context for each piece of state. * Not providing a default value for the context. This can lead to errors if a component tries to access the context outside of a provider. ### 3.4. Redux/Flux-like Architectures (with Deno) For complex applications, consider a Redux/Flux-like architecture, which offers a centralized store, predictable state updates, and unidirectional data flow. There are Deno-specific implementations and libraries built for this. **Do This:** * Define a clear and concise state schema for your application. * Create actions to represent state changes. * Implement pure reducers to handle actions and update the state immutably. * Use a middleware system to handle side effects (e.g., API calls). * Consider using libraries like "deno-redux" or "zustand" (though "zustand" does not follow the Redux pattern strictly, it provides a simplified central store). Evaluate if a full Redux pattern is needed, or if "zustand" is a better, less verbose solution. **Don't Do This:** * Mutate the state directly within reducers. * Perform side effects directly within reducers. * Create excessively complex reducers. Break them down into smaller, more manageable functions. **Example (Redux-like Architecture using "zustand"):** """typescript // store.ts import { create } from 'zustand'; interface BearState { bears: number; increase: (by: number) => void; } export const useBearStore = create<BearState>()((set) => ({ bears: 0, increase: (by) => set((state) => ({ bears: state.bears + by })), })); // component.tsx (demonstrating usage - adapted for Deno/Fresh) import { useBearStore } from "./store.ts"; function MyComponent() { const bears = useBearStore((state) => state.bears); const increase = useBearStore((state) => state.increase); return ( <div> <p>Number of bears: {bears}</p> <button onClick={() => increase(1)}>Add a bear</button> </div> ); } """ **Why:** Redux-like architectures offer a predictable and manageable way to handle complex application state. They promote testability, maintainability, and scalability. **Common Mistakes:** * Overcomplicating the store setup and structure. * Not using TypeScript interfaces for state and actions, leading to type errors. * Ignoring the unidirectional data flow principle and causing unintended side effects. ### 3.5. Server-Side State Management Deno also offers the possibility to perform state management on the server. This is essential if you are working with user sessions, database interactions, or caching strategies. **Do This:** * Leverage Deno's built-in modules like "Deno.KV". "Deno.KV" provides a durable, low-latency key-value database. * Utilize caching strategies for frequently accessed data to minimize database queries. * Consider using libraries like Redis or Memcached for more advanced caching needs. * Implement proper security measures to protect sensitive data stored on the server. **Don't Do This:** * Expose sensitive server-side state directly to the client. * Store large amounts of data in memory without proper caching and garbage collection. * Ignore security vulnerabilities related to data storage and retrieval. **Example (Server-Side State Management with Deno.KV):** """typescript // server.ts const kv = await Deno.openKv(); async function incrementCounter(key: string) { const atomicOp = kv.atomic(); const counter = await kv.get([key]); const currentValue = counter.value || 0; atomicOp.set([key], currentValue + 1); const { ok } = await atomicOp.commit(); return ok; } // Example Usage incrementCounter("page_views") .then((success) => { if (success) { console.log("Counter incremented successfully."); } else { console.error("Failed to increment counter."); } }); // Get the key value const res = await kv.get(["page_views"]) console.log(res); //Close the KV store when done //await kv.close() """ **Why:** Server-side state management allows centralized and secure data storage, reducing client-side dependencies and improving performance by caching frequently accessed data. "Deno.KV" provides a built-in solution, simplifying setup and reducing external dependencies. **Common Mistakes:** * Storing sensitive data in plain text. * Not implementing proper error handling for database operations. * Ignoring potential race conditions when updating server-side state concurrently. ## 4. Advanced Patterns and Techniques ### 4.1. Functional Reactive Programming (FRP) FRP combines functional programming with reactive programming to create declarative and composable state management solutions. This can be accomplished via RxJS or similar libraries. **Do This:** * Use observables to represent streams of data over time. * Transform data streams using operators like "map", "filter", and "reduce". * Combine multiple data streams using operators like "merge", "combineLatest", and "concat". * Handle side effects using subscriptions. **Don't Do This:** * Create excessively complex observable chains that are difficult to understand and debug. * Ignore potential memory leaks by not unsubscribing from observables when they are no longer needed. ### 4.2. Event Sourcing Event sourcing involves persisting the state of an application as a sequence of events. The current state can be derived by replaying these events. This provides an audit trail and enables time-travel debugging. This approach often works very well with "Deno.KV" for persisting the events. **Do This:** * Define a clear and concise event schema for your application. * Store events in an append-only log (e.g., a database table). * Replay events to derive the current state. * Use snapshots to optimize the replay process for long event streams. **Don't Do This:** * Mutate existing events. * Store derived state directly in the event log. ## 5. Specific Considerations for Deno * **Permissions:** Deno's permission system should be explicitly considered when dealing with server-side state. Ensure the necessary permissions are granted for accessing databases, the file system, or other resources. * **Module Resolution:** Be mindful of Deno's module resolution when organizing state management logic. Use explicit imports and exports to clearly define dependencies and avoid naming conflicts. * **Top-Level Await:** Use top-level "await" with caution. Ensure that any asynchronous initializations of state or connections to data sources are handled correctly to avoid blocking the application's startup. * **ESM (ECMAScript Modules):** Deno is designed around ESM, so ensure your state management code is fully compatible and takes advantage of ESM's benefits. ## 6. Conclusion Choosing the right state management approach in Deno depends on the complexity of your application. Start with simple techniques for smaller projects and consider more robust solutions like Redux or FRP for larger, more complex applications. By following the principles and standards outlined in this document, you can ensure that your Deno applications are maintainable, performant, and secure. Always remember clarity, predictability, and immutability as cornerstones of effective state management.
# Code Style and Conventions Standards for Deno This document outlines the code style and convention standards for Deno projects. Adhering to these standards ensures maintainability, readability, and consistency across the codebase, leading to a more robust and collaborative development environment. It is designed to be used by Deno developers and as context for AI coding assistants such as GitHub Copilot and Cursor. ## 1. Formatting Consistent formatting is fundamental for code readability. Deno provides a built-in formatter to automatically enforce these rules. ### 1.1. Code Formatting and Linting Deno offers "deno fmt" and "deno lint" as built-in tools for formatting and linting code, respectively. **Standard:** Always use the "deno fmt" and "deno lint" tools on every commit. Configure your editor or IDE to automatically format and lint the code upon saving. **Why:** Automated formatting reduces stylistic debates and ensures consistent code style across the project. Linting helps catch potential errors and enforce best practices. **Do This:** """bash deno fmt deno lint """ Alternatively, use a pre-commit hook. Use a tool like "husky" and "lint-staged" or "simple-git-hooks" can automate these tasks. **Example "simple-git-hooks" configuration:** """json { "pre-commit": "deno fmt && deno lint" } """ **Don't Do This:** * Manually format code, except when "deno fmt" fails to provide the desired outcome (report such cases to the Deno community). * Ignore linting warnings or errors. Address them promptly, or suppress them with justification if they're false positives using appropriate pragmas ("// deno-lint-ignore <rule-name>"). **Technology-Specific Details:** * "deno fmt" is based on the "prettier" JavaScript formatter and supports basic formatting options like tab width and single/double quotes. * "deno lint" allows for custom rules and configurations, providing greater control over code quality. * You can configure "deno fmt" and "deno lint" via CLI flags or a configuration file (deno.json or deno.jsonc). ### 1.2. Whitespace and Indentation **Standard:** * Use 2 spaces for indentation. Never use tabs. * Add a single blank line at the end of each file. * Use blank lines to separate logical blocks of code within functions. * Avoid trailing whitespace. **Why:** Consistent indentation improves readability and helps visually separate logical blocks of code. Trailing whitespace adds unnecessary noise and can cause confusion. **Do This:** """typescript function calculateArea(width: number, height: number): number { const area = width * height; return area; } """ **Don't Do This:** """typescript function calculateArea(width: number, height: number): number { const area = width*height; return area; } """ ### 1.3. Line Length **Standard:** Limit lines to a maximum of 120 characters. **Why:** Enforces readability, especially on smaller screens or when code is displayed side-by-side. **Do This:** """typescript function calculateComplexValue( input1: number, input2: number, input3: number, ): number { const intermediateValue = input1 * input2 + input3; return Math.sqrt(intermediateValue); } """ **Don't Do This:** """typescript function calculateComplexValue(input1: number, input2: number, input3: number): number { const intermediateValue = input1*input2+input3; return Math.sqrt(intermediateValue); } """ ### 1.4. Quotes **Standard:** Prefer single quotes ("'") for string literals unless the string contains single quotes, in which case use double quotes ("""). **Why:** Consistent use of single quotes, except where impractical, can simplify string formatting. **Do This:** """typescript const name = 'John Doe'; const message = "He said, 'Hello!'"; """ **Don't Do This:** """typescript const name = "John Doe"; // Inconsistent with the standard """ ## 2. Naming Conventions Consistent naming conventions are crucial for code clarity and maintainability. ### 2.1. General Naming **Standard:** * Use descriptive and meaningful names. * Avoid abbreviations unless they are widely understood in the domain (e.g., "URL", "ID"). * Be consistent within a project. **Why:** Clear names make code self-documenting and easier to understand. **Do This:** """typescript const userAge = 30; function calculateTax(income: number): number { // ... return 0; } """ **Don't Do This:** """typescript const a = 30; // Meaningless name function calc(i: number): number { // ... return 0; } // Abbreviation with unclear meaning """ ### 2.2. Variables and Constants **Standard:** * Use camelCase for variable names ("userName", "productPrice"). * Use UPPER_SNAKE_CASE for constants ("MAX_SIZE", "DEFAULT_TIMEOUT"). * Use "const" by default; use "let" only when the variable needs to be reassigned. **Why:** Clearly distinguishes variables from constants and promotes immutability. **Do This:** """typescript const maxRetries = 3; let retryCount = 0; const API_ENDPOINT = "https://example.com/api"; """ **Don't Do This:** """typescript var MaxRetries = 3; // Incorrect case, uses var instead of const let APIEndpoint = "https://example.com/api"; // Inconsistent case for constant """ ### 2.3. Functions and Methods **Standard:** * Use camelCase for function and method names ("getUserData", "calculateTotal"). * Use descriptive verbs that indicate the function's action. **Why:** Provides a consistent and understandable naming convention. **Do This:** """typescript async function fetchUserData(userId: string): Promise<UserData> { // ... return {} as UserData; } class Cart { calculateTotal(): number { // ... return 0; } } """ **Don't Do This:** """typescript function UserData(userId: string): Promise<UserData> { // Confusing name // ... return {} as UserData; } class Cart { total(): number { // Unclear verb // ... return 0; } } """ ### 2.4. Classes and Interfaces **Standard:** * Use PascalCase for class and interface names ("UserData", "ProductService"). * Interface names can optionally be prefixed with "I" (e.g., "IUserData"), but consistency is key. **Why:** Distinguishes classes and interfaces from other code elements. **Do This:** """typescript interface UserData { id: string; name: string; } class UserService { // ... } """ **Don't Do This:** """typescript interface user_data { // Incorrect case id: string; name: string; } class user_service { // Incorrect case // ... } """ ### 2.5. Boolean Variables **Standard:** * Prefer prefixes like "is", "has", or "should" for boolean variables and functions. **Why:** Improves their readability and conveys their true/false nature. **Do This:** """typescript const isValid = true; const hasPermission = false; function shouldUpdate(): boolean { return true; } """ **Don't Do This:** """typescript const valid = true; // Less clear meaning """ ## 3. Stylistic Consistency Consistency in style enhances code readability and reduces cognitive load. ### 3.1. Imports **Standard:** * Group imports by origin: Deno standard library, third-party modules, and local modules. * Sort each group alphabetically. * Use bare module specifiers for Deno standard library imports. * Fully specified URLs for third-party modules. **Why:** Clear separation of import origins and simplified maintenance. Consistent sorting minimizes unnecessary changes and conflicts. **Do This:** """typescript // Deno standard library import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { serve } from "https://deno.land/std@0.211.0/http/server.ts"; // Third-party modules import { oak } from "https://deno.land/x/oak@v12.6.1/mod.ts"; import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts"; // Local modules import { UserService } from "./user_service.ts"; import { validateInput } from "./validation.ts"; """ **Don't Do This:** """typescript import { serve, assertEquals } from "https://deno.land/std@0.211.0/http/server.ts"; // Mixed imports import * as oak from "https://deno.land/x/oak@v12.6.1/mod.ts"; // Inconsistent style import { UserService } from "./user_service.ts"; import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts"; // Out of order """ ### 3.2. Error Handling **Standard:** * Use "try...catch" blocks for handling synchronous errors. * Use "async/await" with "try...catch" for handling asynchronous errors. * Throw descriptive errors with meaningful messages. * Consider custom error classes for specific error scenarios. **Why:** Consistent error handling prevents unhandled exceptions and provides context for debugging. **Do This:** """typescript async function fetchData(url: string): Promise<any> { try { const response = await fetch(url); if (!response.ok) { throw new Error("HTTP error! status: ${response.status}"); } return await response.json(); } catch (error) { console.error("Error fetching data:", error); throw error; // Re-throw or handle as needed } } class CustomError extends Error { constructor(message: string) { super(message); this.name = "CustomError"; } } """ **Don't Do This:** """typescript async function fetchData(url: string): Promise<any> { const response = await fetch(url); // No error handling return await response.json(); } try { // Risky synchronous code } catch (e) { console.error("An error occurred"); // Vague error message } """ ### 3.3. Comments **Standard:** * Write clear and concise comments to explain complex or non-obvious logic. * Use JSDoc-style comments for documenting functions, classes, and interfaces. * Keep comments up-to-date with code changes. * Avoid commenting obvious code. **Why:** Comments improve code understanding and facilitate maintenance. **Do This:** """typescript /** * Calculates the total price of items in the cart. * * @param cartItems An array of cart item objects. * @returns The total price of the items. */ function calculateTotalPrice(cartItems: CartItem[]): number { // Apply discount if applicable const discount = calculateDiscount(cartItems); return cartItems.reduce((sum, item) => sum + item.price, 0) - discount; } """ **Don't Do This:** """typescript function calculateTotalPrice(cartItems: CartItem[]): number { // Loop through the cart items let total = 0; for (const item of cartItems) { total += item.price; } return total; } // Obvious comment """ ### 3.4. Type Annotations **Standard:** * Use explicit type annotations whenever possible, especially for function parameters, return values, and complex data structures. * Use type inference judiciously when the type is obvious. **Why:** Improves code reliability and maintainability by enabling static type checking. **Do This:** """typescript function formatString(input: string, maxLength: number): string { return input.substring(0, maxLength); } const user: { name: string; age: number } = { name: "John Doe", age: 30, }; """ **Don't Do This:** """typescript function formatString(input, maxLength) { // Missing type annotations return input.substring(0, maxLength); } """ ### 3.5. Test Driven Development **Standard:** * Write tests before writing the actual implementation. * Use Deno's built-in test runner or a third-party testing framework. * Write clear, concise, and comprehensive tests. * Follow the Arrange-Act-Assert pattern. **Why:** Reduces bugs, increases confidence, and ensures long-term maintainability by providing automated verification of code correctness. **Do This:** """typescript import { assertEquals } from "https://deno.land/std@0.211.0/assert/mod.ts"; import { calculateArea } from "./math.ts"; Deno.test("calculateArea should return the correct area", () => { // Arrange const width = 5; const height = 10; const expectedArea = 50; // Act const actualArea = calculateArea(width, height); // Assert assertEquals(actualArea, expectedArea); }); """ **Don't Do This:** * Skipping writing tests due to time constraints. * Writing tests that verify obvious behavior only. * Writing tests that are overly complex preventing easy diagnosis. ## 4. Modern Deno Patterns and Best Practices ### 4.1. Top-Level Await **Standard:** * Utilize top-level await cautiously, as it can impact module loading performance. Only use when truly unavoidable. * Prefer encapsulating asynchronous operations within functions. **Why:** Improves readability and simplifies asynchronous code in appropriate use cases. Using it excessively can lead to performance impact on loading. **Do This:** """typescript // Avoid if possible: this may block module loading const config = await loadConfig(); async function main() { const data = await fetchData(); console.log(data); } main(); """ **Don't Do This:** """typescript const userData = await fetch('./user.json').then(r => r.json()); // Avoid if possible console.log(userData); """ ### 4.2. Dynamic Imports **Standard:** * Use dynamic imports ("import()") only when you need to conditionally load modules or defer loading for performance reasons. **Why:** Allows for code splitting and on-demand module loading, optimizing initial load times. Note: Be aware that using dynamic imports increases operational complexity. **Do This:** """typescript async function loadModule(modulePath: string) { const module = await import(modulePath); return module; } // Usage: loadModule("./my_module.ts").then((module) => { module.doSomething(); }); """ **Don't Do This:** """typescript import * as myModule from "./my_module.ts"; // Use dynamic imports instead if conditional or performance reasons apply. """ ### 4.3. Dependency Injection **Standard:** * Utilize dependency injection (DI) to enhance testability and decouple components. Constructor injection is preferred. * Consider using a DI container for managing dependencies in larger applications if deemed necessary. **Why:** Promotes modularity, testability, and maintainability of code. **Do This:** """typescript interface Logger { log(message: string): void; } class ConsoleLogger implements Logger { log(message: string): void { console.log(message); } } class UserService { private logger: Logger; constructor(logger: Logger) { this.logger = logger; } createUser(username: string) { this.logger.log("Creating user: ${username}"); // ... } } const logger = new ConsoleLogger(); const userService = new UserService(logger); userService.createUser("John"); """ **Don't Do This:** """typescript class UserService { private logger: ConsoleLogger; // Tightly coupled to a specific implementation constructor() { this.logger = new ConsoleLogger(); } createUser(username: string) { this.logger.log("Creating user: ${username}"); // ... } } """ ### 4.4. Standard Library Module Usage **Standard:** * Favor the Deno standard library modules over third-party libraries for common tasks, when appropriate. * Third party libraries should be carefully assessed for security and performance implications. **Why:** Reduces external dependencies and promotes consistent code practices. **Do This:** """typescript import { serve } from "https://deno.land/std@0.211.0/http/server.ts"; const handler = (request: Request): Response => { const body = "Your user-agent is:\n\n${request.headers.get( "user-agent", ) ?? "Unknown"}"; return new Response(body, { status: 200 }); }; serve(handler, { port: 8000 }); """ **Don't Do This:** """typescript // Using a third party library for a basic HTTP server when the Deno std lib offers a comparable solution import { serve } from "npm:http-server"; """ ### 4.5. Error Handling Using Result Type **Standard:** * Consider using a "Result" type to make functions return either expected values, or well-defined errors. **Why:** By making functions return a defined success or failure outcome, you ensure the caller handles all cases, and you will ensure the caller is aware of the success/failure scenarios immediately at compile time. **Do This:** """typescript type Result<T, E extends Error = Error> = | { ok: true; value: T } | { ok: false; error: E }; async function maybeReadFile( path: string, ): Promise<Result<string, Error>> { try { const contents = await Deno.readTextFile(path); return { ok: true, value: contents }; } catch (error) { return { ok: false, error: error instanceof Error ? error : new Error(String(error)) }; } } const result = await maybeReadFile("foo.txt"); if (result.ok) { console.log("File contents", result.value); } else { console.error("Failed to read file", result.error); } """ **Don't Do This:** """typescript async function readFile(path: string): Promise<string> { return Deno.readTextFile(path); // No error handling; an exception will be thrown instead. } """ ## 5. Security Best Practices ### 5.1. Permissions **Standard:** * Run Deno programs with the minimum necessary permissions. * Avoid using "--allow-all" in production. * Explicitly specify required permissions using flags like "--allow-read", "--allow-net", etc. **Why:** Reduces the attack surface and limits the potential impact of security vulnerabilities. **Do This:** """bash deno run --allow-read=. --allow-net=api.example.com app.ts """ **Don't Do This:** """bash deno run --allow-all app.ts # Too permissive """ ### 5.2. Secure Coding Practices **Standard:** * Sanitize user inputs to prevent injection attacks (e.g., SQL injection, XSS). * Use secure versions of libraries and dependencies. Regularly update dependencies to patch vulnerabilities. * Follow secure coding guidelines for web applications (e.g., OWASP). **Why:** Protects against common web application vulnerabilities. **Do This:** """typescript import { escape } from "https://deno.land/x/escape@v2.0.0/mod.ts"; const userInput = "<script>alert('XSS')</script>"; const sanitizedInput = escape(userInput); console.log(sanitizedInput); // <script>alert('XSS')</script> """ **Don't Do This:** """typescript const userInput = Deno.args[0]; // Directly using userInput without sanitization Deno.writeFile("./log.txt", new TextEncoder().encode(userInput)); """ ## Conclusion By adhering to these code style and convention standards, Deno projects can achieve a high level of consistency, readability, and maintainability. These standards not only improve the development experience but also contribute to the overall quality and security of the codebase. Developers are encouraged to continuously review and refine these guidelines as the Deno ecosystem evolves.
# Performance Optimization Standards for Deno This document outlines coding standards for optimizing the performance of Deno applications. These standards are designed to improve speed, responsiveness, and resource utilization, leveraging Deno's unique features and the latest versions of the runtime. These standards are not just about writing code that works, but code that is efficient, scalable, and maintainable. ## 1. Architectural Considerations ### 1.1. Module Loading and Dependency Management **Standard:** Optimize module loading to reduce startup time and memory footprint. * **Do This:** * Use specific imports instead of wildcard imports to only load necessary modules. * Lazy load modules that are not immediately required at startup. * Leverage Deno's built-in dependency inspector to analyze module sizes and loading times ("deno info"). * **Don't Do This:** * Avoid wildcard imports ("import * as something from "./something.ts""). * Eagerly load all dependencies during application startup. * Over-rely on large, monolithic libraries when smaller, more focused alternatives exist. **Why:** Efficient module loading directly impacts application startup time and memory usage. Deno's decentralized module system requires careful management to prevent performance bottlenecks. **Example:** """typescript // Instead of: // import * as utils from "./utils.ts"; // utils.doSomething(); // utils.doSomethingElse(); // Do this: import { doSomething, doSomethingElse } from "./utils.ts"; doSomething(); doSomethingElse(); """ Lazy loading: """typescript async function loadModule() { const module = await import("./heavy_module.ts"); module.run(); } // Only load the module when needed document.getElementById("myButton").addEventListener("click", loadModule); """ ### 1.2 Caching Strategies **Importance:** Implementing effective caching mechanisms is critical for reducing latency and improving responsiveness, especially in web applications. **Standards:** * **Do This:** * Implement HTTP caching headers (e.g., "Cache-Control", "Expires", "ETag") to allow browsers and CDNs to cache static assets and API responses. * Use Deno's built-in "Deno.Kv" key-value store for caching frequently accessed data. * Utilize service workers to cache static assets and API responses for offline access and faster loading times. * **Don't Do This:** * Neglect caching static assets, leading to unnecessary server requests. * Over-cache dynamic content, resulting in stale data being displayed to users. * Store sensitive data in caches without proper encryption and security measures. **Why:** * *HTTP Caching:* Reduces server load and latency by allowing clients and intermediaries to cache responses. * *Deno.Kv Caching:* Provides fast and efficient access to cached data within your application. * *Service Worker Caching:* Enables offline access and significantly improves loading times for web applications. **Code Examples** Using Deno.Kv for caching: """typescript import { Kv } from "@deno/kv"; const kv = await new Kv(); async function getCachedData(key: string, fetcher: () => Promise<any>, ttl: number): Promise<any> { const cached = await kv.get([key]); if (cached.value) { console.log("Cache hit!"); return cached.value; } const data = await fetcher(); await kv.set([key], data, { expireIn: ttl }); console.log("Cache miss, fetching and caching."); return data; } async function fetchDataFromSource(): Promise<any> { // Simulate fetching data from an external source await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate network latency return { value: Math.random() }; } // Example usage: const data = await getCachedData("my_data", fetchDataFromSource, 60); // Cache for 60 seconds console.log(data); kv.close(); """ ### 1.3. Concurrency and Parallelism **Standard:** Utilize Deno's concurrency model effectively to maximize resource utilization. * **Do This:** * Use "async/await" and Promises for asynchronous operations to avoid blocking the main thread. * Leverage web workers for CPU-intensive tasks to offload work from the main event loop. * Utilize streams and iterators for processing large datasets without loading them entirely into memory. * **Don't Do This:** * Perform long-running, synchronous operations on the main thread. * Underutilize available CPU cores. * Load entire large files into memory at once. **Why:** Concurrency and parallelism are essential for building responsive and scalable applications. Deno's modern concurrency model enables developers to write efficient code that takes advantage of multiple CPU cores. **Example:** Web worker example: """typescript // main.ts const worker = new Worker(new URL("./worker.ts", import.meta.url).href, { type: "module" }); worker.postMessage("Start processing!"); worker.onmessage = (event) => { console.log("Received from worker:", event.data); }; // worker.ts self.onmessage = (event) => { console.log("Worker received:", event.data); // Simulate a CPU-intensive task let result = 0; for (let i = 0; i < 1000000000; i++) { result += i; } self.postMessage("Result: ${result}"); }; """ Stream example (copying a file): """typescript const file = await Deno.open("large_file.txt"); const dest = await Deno.create("copy_of_large_file.txt"); await Deno.copy(file, dest); file.close(); dest.close(); """ ### 1.4 Database Interactions **Standard:** Optimize database interactions to minimize latency and resource consumption. * **Do This:** * Use connection pooling to reuse database connections. * Optimize database queries with indexes. * Use prepared statements to prevent SQL injection and improve query performance. * Consider using an ORM or query builder for abstraction and security. * **Don't Do This:** * Create a new database connection for every request. * Run complex queries without proper indexing. * Construct SQL queries by concatenating strings directly (SQL injection risk). **Why:** Database interactions are often a bottleneck in web applications. Efficient database access patterns are crucial for minimizing latency and improving overall system performance. **Example:** Using connection pooling with "postgres": """typescript import { Pool } from "https://deno.land/x/postgres@v0.17.0/mod.ts"; const pool = new Pool({ hostname: "localhost", database: "mydb", user: "myuser", password: "mypassword", port: 5432, }, 10); // Limit to 10 connections const client = await pool.connect(); try { const result = await client.queryObject("SELECT * FROM users WHERE id = $1", [1]); console.log(result.rows); } finally { client.release(); } // await pool.end(); // Close the pool when done """ ## 2. Code-Level Optimizations ### 2.1. Data Structures and Algorithms **Standard:** Choose appropriate data structures and algorithms for the task at hand. * **Do This:** * Use "Map" and "Set" for efficient lookups and uniqueness checks. * Use Typed Arrays ("Uint8Array", "Float64Array", etc.) when working with binary data. * Choose algorithms with appropriate time complexity for the expected input size (e.g., use efficient sorting algorithms for large datasets). * **Don't Do This:** * Use arrays for lookups when "Map" or "Set" would be more efficient. * Perform unnecessary iterations or computations. * Ignore the performance characteristics of different data structures and algorithms. **Why:** The choice of data structures and algorithms has a significant impact on the performance of your code, especially when dealing with large datasets or computationally intensive tasks. **Example:** Using "Set" for uniqueness checking: """typescript const myArray = [1, 2, 2, 3, 4, 4, 5]; const uniqueValues = [...new Set(myArray)]; console.log(uniqueValues); // [1, 2, 3, 4, 5] """ Using "Uint8Array" for binary data: """typescript const buffer = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // "Hello" in ASCII const decoder = new TextDecoder(); const text = decoder.decode(buffer); console.log(text); // Hello """ ### 2.2. String Manipulation **Standard:** Optimize string manipulation routines to reduce memory allocation and improve speed. * **Do This:** * Use template literals for string concatenation instead of the "+" operator. * Use "String.prototype.startsWith", "String.prototype.endsWith", and "String.prototype.includes" instead of regular expressions for simple checks. * Avoid unnecessary string copies. * **Don't Do This:** * Perform excessive string concatenation using the "+" operator in loops. * Use regular expressions when simpler string methods suffice. * Create unnecessary intermediate strings. **Why:** String operations can be surprisingly expensive, especially in performance-critical sections of your code. Optimizing string manipulation can lead to significant performance improvements. **Example:** Using template literals: """typescript const name = "World"; const greeting = "Hello, ${name}!"; console.log(greeting); // Hello, World! """ Avoiding unnecessary string copies: """typescript // Instead of // let str = ""; // for (let i = 0; i < 1000; i++) { // str += "a"; // creates a new string each time // } // Do This: const arr = new Array(1000).fill("a"); const str = arr.join(""); // Creates one string at the end. """ ### 2.3. Object and Array Operations **Standard:** Optimize object and array operations regarding immutability, cloning, and modification. * **Do This:** * Use immutable data structures where appropriate to avoid unintended side effects. * Use the spread operator ("...") or "Array.from()" for shallow copying of objects or arrays. * Use in-place array modification methods when possible. * **Don't Do This:** * Mutate objects and arrays directly when immutability is desired. * Use deep cloning for simple object copies. * Create unnecessary intermediate arrays. **Why:** Efficient object and array manipulation is crucial for data processing-intensive applications. Immutability can improve code clarity and prevent bugs, while efficient copying techniques can reduce memory allocation and improve performance. **Example:** Shallow copying using the spread operator: """typescript const originalArray = [1, 2, 3]; const newArray = [...originalArray]; // Creates a shallow copy newArray[0] = 4; // Modifying newArray does not affect originalArray console.log(originalArray); // [1, 2, 3] console.log(newArray); // [4, 2, 3] """ Using immutability with "Object.freeze()": """typescript const immutableObject = Object.freeze({ x: 1, y: 2 }); // immutableObject.x = 3; // This will throw an error in strict mode """ ### 2.4 Regular Expressions **Standard:** Write efficient regular expressions and avoid unnecessary use of regular expressions. * **Do This:** * Use non-capturing groups "(?:...)" where capturing is not required. * Compile regular expressions that are used repeatedly. * Prefer character classes ("[a-z]") to alternation ("a|b|c") where possible. * **Don't Do This:** * Use overly complex regular expressions when simpler alternatives exist. * Create new regular expressions in loops. * Use backtracking excessively. **Why:** Regular expressions can be powerful but also computationally expensive. Writing efficient regular expressions and avoiding their unnecessary use is crucial for performance. **Example:** Compiling a regular expression: """typescript const regex = /^hello/i; regex.test("Hello world"); """ Using non-capturing groups: """typescript const regex = /^(?:https?:\/\/)?([^\/]+)/; // Only capture the domain name const url = "https://www.example.com/path"; const match = regex.exec(url); console.log(match?.[1]); // www.example.com """ ## 3. Deno-Specific Optimizations ### 3.1. Deno Standard Library Usage **Standard:** Prefer Deno's standard library for common tasks whenever possible. * **Do This:** * Use "Deno.readFile" and "Deno.writeFile" for file I/O. * Use "Deno.serve" for creating HTTP servers unless more complex routing is required. * Use the "std/hash" module. * **Don't Do This:** * Import third-party libraries for simple tasks that can be accomplished with the standard library. * Reinvent the wheel. **Why:** Deno's standard library is designed to be efficient and secure. Using it avoids unnecessary dependencies and reduces the risk of security vulnerabilities. It also ensures tighter integration with the Deno runtime, potentially leading to better performance. **Example:** Using "Deno.serve" for a simple HTTP server: """typescript import { serve } from "https://deno.land/std@0.177.0/http/server.ts"; const handler = (request: Request): Response => { const body = "Your user-agent is:\n\n${request.headers.get("user-agent") ?? "Unknown"}"; return new Response(body, { status: 200 }); }; console.log("Listening on http://localhost:8000"); serve(handler, { port: 8000 }); """ ### 3.2. Permissions Management **Standard:** Request only the necessary permissions for your application. * **Do This:** * Use the "--allow-read", "--allow-write", "--allow-net", "--allow-env", etc., flags to grant specific permissions. * Avoid using "--allow-all" in production environments. * Review and minimize the required permissions regularly. * **Don't Do This:** * Grant unnecessary permissions to your application. * Run your application with "--allow-all" without understanding the security implications. **Why:** Deno's security model relies on explicit permissions. Requesting only the necessary permissions reduces the attack surface and improves the security of your application. It can indirectly impact performance by reducing the overhead of permission checks. **Example:** Running a script with specific permissions: """bash deno run --allow-read=./data.txt --allow-net=example.com main.ts """ ### 3.3. Transpilation and Bundling **Standard:** Optimize transpilation and bundling processes for production deployments. * **Do This:** * Use "deno compile" to create a single, optimized executable. * Use a bundler like "esbuild" or "rollup" when more control over the bundling process is required. * Minify and compress your code for production deployments. * **Don't Do This:** * Deploy unbundled code to production. * Skip minification and compression. **Why:** Transpilation and bundling can significantly improve the performance and deployment characteristics of your Deno applications. Bundling reduces the number of network requests, while minification and compression reduce the size of the code. **Example:** Compiling a Deno application: """bash deno compile --output myapp main.ts """ Using "esbuild": """javascript // build.js import * as esbuild from "https://deno.land/x/esbuild@v0.17.19/mod.js"; await esbuild.build({ entryPoints: ["./main.ts"], bundle: true, outfile: "./dist/main.js", format: "esm", minify: true, platform: "browser", }); esbuild.stop(); """ ### 3.4. Profiling and Performance Monitoring **Standard:** Use profiling and performance monitoring tools to identify bottlenecks and optimize code. * **Do This:** * The Deno runtime includes a built-in profiler which can be enabled via the "--prof" flag. * Use appropriate logging levels. * **Don't Do This:** * Guess at performance issues. * Ignore performance metrics. **Why:** Regular profiling and performance monitoring are essential for identifying and addressing performance bottlenecks in your Deno applications. These tools provide valuable insights into the runtime behavior of your code, enabling you to make informed optimization decisions. **Example:** To generate such a profiling report, run your program with the --prof command line flag as follows: """shell deno run --allow-read --allow-write --prof main.ts """ Once the program exits a "deno_prof.json" file will be written in the current working directory. ## 4. Error Handling **Standard** Implement comprehensive and efficient error handling to prevent performance degradation caused by exceptions. * **Do This:** * Use "try...catch" blocks to handle potential exceptions gracefully. * Implement custom error types for better error identification and handling. * Use "finally" blocks to ensure resources are released even if an error occurs. * **Don't Do This:** * Ignore potential errors, leading to unhandled exceptions and crashes. * Rely solely on generic "catch" blocks without specific error handling. * Throw and catch exceptions for control flow, as it is expensive. **Why:** Proper error handling prevents abrupt program termination and resource leaks, ensuring stability and performance. Custom error types improve debuggability and allow for targeted error handling. **Example:** """typescript async function readFile(filename: string): Promise<string | undefined> { try { const data = await Deno.readTextFile(filename); return data; } catch (error) { if (error instanceof Deno.errors.NotFound) { console.error("File not found: ${filename}"); return undefined; } else { console.error("Error reading file ${filename}: ${error.message}"); throw error; // Re-throw the error if it's not a NotFound error } } finally { console.log("File reading process completed (or attempted)."); } } // Usage readFile("my_file.txt") .then(content => { if (content) { console.log("File content: ${content}"); } }); """ ### 5. Code Reviews and Continuous Improvement **Standard:** Incorporate performance considerations into code reviews and continuously improve code based on performance metrics. * **Do This:** * Include performance as a key criterion in code reviews. * Regularly profile and analyze code for potential performance improvements. * Track performance metrics over time to identify regressions and areas for optimization. * **Don't Do This:** * Ignore performance considerations during code reviews. * Treat performance optimization as a one-time task. * Fail to monitor and track performance metrics. **Why:** Continuous attention to performance is crucial for maintaining a high-quality, efficient codebase. Code reviews and performance monitoring provide valuable opportunities to identify and address potential performance issues early in the development lifecycle.
# Component Design Standards for Deno This document outlines the standards for creating reusable and maintainable components in Deno. It emphasizes best practices specific to Deno's unique features and promotes clean, modern coding patterns. ## 1. Principles of Component Design in Deno ### 1.1 Reusability Through Modularity * **Do This:** Design components with single, well-defined responsibilities. This promotes reusability across different parts of the application. * **Don't Do This:** Create monolithic components that perform multiple unrelated tasks. This hinders reusability and makes maintenance difficult. * **Why:** Modular components are easier to understand, test, and reuse in different contexts. """typescript // Good: Single responsibility - formatting dates export function formatDate(date: Date): string { return date.toLocaleDateString('en-US'); } // Bad: Multi-responsibility - formatting dates and logging export function formatDateAndLog(date: Date): string { const formattedDate = date.toLocaleDateString('en-US'); console.log("Date formatted: ${formattedDate}"); // Logging is not the responsibility of a date formatter return formattedDate; } """ ### 1.2 Abstraction and Encapsulation * **Do This:** Use interfaces and abstract classes to define contracts for components. Encapsulate internal implementation details to hide them from the outside world. * **Don't Do This:** Expose internal state or implementation details directly. This can lead to tight coupling and brittle code. * **Why:** Abstraction reduces complexity and allows you to change the implementation without affecting the clients. Encapsulation protects the component's integrity. """typescript // Good: Interface-based abstraction export interface Logger { log(message: string): void; } export class ConsoleLogger implements Logger { log(message: string): void { console.log(message); } } // Clients depend on the interface, not the concrete implementation export function useLogger(logger: Logger, message: string) { logger.log(message); } // Bad: Direct dependency on concrete class export class MyComponent { constructor(private logger: ConsoleLogger) {} // Tight coupling } """ ### 1.3 Separation of Concerns * **Do This:** Divide the application into distinct sections, each addressing a separate concern (e.g., data access, business logic, presentation). * **Don't Do This:** Mix concerns within a single component or module. * **Why:** Clear separation of concerns leads to better maintainability, testability, and scalability. """typescript // Good: Separated concerns // data_access.ts export async function fetchData(url: string) { const response = await fetch(url); return await response.json(); } // business_logic.ts export function processData(data: any) { // Perform calculations or transformations return data.map((item: any) => item.value * 2); } // presentation.ts import { fetchData } from './data_access.ts'; import { processData } from './business_logic.ts'; export async function displayData(url: string) { const data = await fetchData(url); const processedData = processData(data); console.log(processedData); } // Bad: Mixing concerns export async function displayAndProcessData(url: string) { const response = await fetch(url); const data = await response.json(); const processedData = data.map((item: any) => item.value * 2); console.log(processedData); } """ ### 1.4 Loose Coupling * **Do This:** Design components to minimize dependencies on each other. Use techniques like dependency injection and event-driven architectures. * **Don't Do This:** Create tightly coupled components that rely on specific implementations of other components. * **Why:** Loose coupling makes components more independent, easier to test in isolation, and less prone to cascading failures when one component changes. """typescript // Good: Loose coupling with dependency injection interface NotificationService { sendNotification(message: string): Promise<void>; } class EmailNotificationService implements NotificationService { async sendNotification(message: string): Promise<void> { console.log("Sending email: ${message}"); // Actual email sending logic would go here } } class SMSNotificationService implements NotificationService { async sendNotification(message: string): Promise<void> { console.log("Sending SMS: ${message}"); // Actual SMS sending logic would go here } } class UserRegistration { constructor(private notificationService: NotificationService) {} async registerUser(email: string): Promise<void> { // User registration logic await this.notificationService.sendNotification("Welcome, ${email}!"); } } // Using the components const emailService = new EmailNotificationService(); const registration = new UserRegistration(emailService); await registration.registerUser("test@example.com"); const smsService = new SMSNotificationService(); const registration2 = new UserRegistration(smsService); await registration2.registerUser("123-456-7890"); // Bad: Tight coupling (directly instantiating the EmailNotificationService inside UserRegistration) class UserRegistrationTight { private emailService : EmailNotificationService; constructor(){ this.emailService = new EmailNotificationService(); } async registerUser(email: string): Promise<void> { // User registration logic await this.emailService.sendNotification("Welcome, ${email}!"); } } """ ### 1.5 Single Source of Truth (SSOT) * **Do This:** Identify and define a single source of truth for each piece of information or configuration. * **Don't Do This:** Duplicate or hardcode the same information in multiple places. * **Why:** Ensures consistency and reduces the risk of conflicting information. Changes only need to be made in one place. """typescript // Good: Configuration from environment variables (single source of truth) const API_URL = Deno.env.get("API_URL") || "http://localhost:8000"; //default if not available export async function fetchData(endpoint: string) { const response = await fetch("${API_URL}/${endpoint}"); return await response.json(); } // Bad: Hardcoding the API URL in multiple places export async function fetchDataBad(endpoint: string) { const response = await fetch("http://localhost:8000/${endpoint}"); //URL hardcoded here return await response.json(); } export async function anotherFunctionBad(endpoint: string) { const response = await fetch("http://localhost:8000/${endpoint}"); //AND here; not DRY return await response.json(); } """ ## 2. Deno-Specific Component Design Considerations ### 2.1 Leveraging the Standard Library * **Do This:** Utilize the Deno standard library whenever possible for common tasks (e.g., file system operations, HTTP handling). * **Don't Do This:** Reimplement functionality that is already available in the standard library. * **Why:** The standard library is well-maintained, tested, and optimized for Deno. It’s a single source of truth for common functionalities. """typescript // Good: Using the standard library for file system operations import { readTextFile } from "https://deno.land/std@0.212.0/fs/mod.ts"; async function loadConfig(filePath: string): Promise<string> { return await readTextFile(filePath); // Using standard library } // Bad: Implementing file reading logic manually async function loadConfigManual(filePath: string): Promise<string> { // Replicating readTextFile's logic const file = await Deno.open(filePath); const decoder = new TextDecoder("utf-8"); const contents = decoder.decode(await Deno.readAll(file)); Deno.close(file.rid); return contents; } """ ### 2.2 Dependency Management * **Do This:** Use explicit import maps to manage dependencies and ensure that your code uses consistent versions of libraries. * **Don't Do This:** Rely on implicit resolution or hardcoded URLs for dependencies. This can lead to dependency conflicts and unpredictable behavior. * **Why:** Import maps improve reproducibility and help prevent dependency-related issues. Create an import map ("import_map.json"): """json { "imports": { "fmt/": "https://deno.land/std@0.212.0/fmt/", "lodash": "https://cdn.skypack.dev/lodash" } } """ Use the import map: """typescript // Good: Using import maps import { format } from "fmt/datetime.ts"; // Resolves to std@0.212.0 correctly import * as _ from "lodash"; console.log(format(new Date(), "yyyy-MM-dd")); console.log(_.chunk([1,2,3,4,5], 2)) // Bad: Hardcoded URLs import { format as formatBad } from "https://deno.land/std@0.211.0/fmt/datetime.ts"; // Version is hardcoded, creating potential issues """ Run the code with the import map: """bash deno run --import-map import_map.json your_file.ts """ ### 2.3 Permissions Model * **Do This:** Request only the minimum necessary permissions for each component. Follow the principle of least privilege. * **Don't Do This:** Request blanket permissions (e.g., "--allow-all") unless absolutely necessary. * **Why:** Limiting permissions enhances the security of your application. """typescript // Good: Requesting specific permissions // deno run --allow-read=./data.txt --allow-net=api.example.com your_script.ts async function readFile(filePath: string): Promise<string> { try { return await Deno.readTextFile(filePath); } catch (error) { console.error("Error reading file: ${error}"); return ""; } } async function fetchFromAPI(url: string): Promise<any> { try { const response = await fetch(url); return await response.json(); } catch (error) { console.error("Error fetching data: ${error}"); return null; } } // Bad: Using --allow-all // deno run --allow-all your_script.ts // Avoid unless absolutely required """ ### 2.4 Asynchronous Operations and "async"/"await" * **Do This:** Embrace asynchronous operations and "async"/"await" syntax for non-blocking I/O and other long-running tasks. * **Don't Do This:** Use synchronous operations that can block the event loop. * **Why:** Asynchronous operations improve the responsiveness and scalability of your application. """typescript // Good: Asynchronous file reading async function processFile(filePath: string): Promise<void> { try { const contents = await Deno.readTextFile(filePath); console.log("File contents: ${contents.substring(0, 100)}"); // Show first 100 chars } catch (error) { console.error("Error processing file: ${error}"); } } // Bad: Synchronous file reading (blocking) Avoid! function processFileSync(filePath: string): void { try { const contents = Deno.readTextFileSync(filePath); //This could block the event loop console.log("File contents: ${contents.substring(0, 100)}"); // Show first 100 chars } catch (error) { console.error("Error processing file: ${error}"); } } """ ### 2.5 Error Handling * **Do This:** Implement robust error handling using "try...catch" blocks, and consider custom error types for better context. * **Don't Do This:** Ignore errors or let exceptions propagate unhandled. * **Why:** Proper error handling prevents application crashes and provides useful debugging information. """typescript // Good: Custom error handling class APIError extends Error { constructor(message: string, public statusCode: number) { super(message); this.name = "APIError"; } } async function fetchDataWithErrorHandling(url: string): Promise<any> { try { const response = await fetch(url); if (!response.ok) { throw new APIError("HTTP error! status: ${response.status}", response.status); } return await response.json(); } catch (error) { if (error instanceof APIError) { console.error("API Error: ${error.message}, Status Code: ${error.statusCode}"); // Log the error appropriately. Don't just print to console in production. } else { console.error("Unexpected error: ${error}"); } return null; // Or re-throw, depending on your needs. } } // Bad: Ignoring errors (dangerous!) async function fetchDataIgnoringErrors(url: string): Promise<any> { const response = await fetch(url); //No error handling return await response.json(); //No error handling } """ ## 3. Design Patterns for Deno Components ### 3.1 Factory Pattern * **Do This:** Use the factory pattern to create objects of different types based on runtime conditions. * **Why:** Decouples object creation from the client code and allows for easy extensibility. """typescript interface PaymentGateway { processPayment(amount: number): Promise<boolean>; } class StripePaymentGateway implements PaymentGateway { async processPayment(amount: number): Promise<boolean> { console.log("Processing $${amount} via Stripe"); return true; // Simulate success } } class PayPalPaymentGateway implements PaymentGateway { async processPayment(amount: number): Promise<boolean> { console.log("Processing $${amount} via PayPal"); return true; // Simulate success } } class PaymentGatewayFactory { static createPaymentGateway(type: "stripe" | "paypal"): PaymentGateway { switch (type) { case "stripe": return new StripePaymentGateway(); case "paypal": return new PayPalPaymentGateway(); default: throw new Error("Unsupported payment gateway type: ${type}"); } } } // Usage const stripeGateway = PaymentGatewayFactory.createPaymentGateway("stripe"); await stripeGateway.processPayment(100); const paypalGateway = PaymentGatewayFactory.createPaymentGateway("paypal"); await paypalGateway.processPayment(50); """ ### 3.2 Observer Pattern * **Do This:** Employ the observer pattern to create loosely coupled components that can react to events. * **Why:** Allows for a publish-subscribe mechanism where components can subscribe to events and be notified when they occur. """typescript interface Observer { update(message: string): void; } class Subject { private observers: Observer[] = []; attach(observer: Observer): void { this.observers.push(observer); } detach(observer: Observer): void { this.observers = this.observers.filter((obs) => obs !== observer); } notify(message: string): void { for (const observer of this.observers) { observer.update(message); } } } class ConcreteObserver implements Observer { constructor(private name: string) {} update(message: string): void { console.log("${this.name} received: ${message}"); } } // Usage const subject = new Subject(); const observer1 = new ConcreteObserver("Observer 1"); const observer2 = new ConcreteObserver("Observer 2"); subject.attach(observer1); subject.attach(observer2); subject.notify("Hello, observers!"); subject.detach(observer2); subject.notify("Another message!"); """ ### 3.3 Strategy Pattern * **Do This:** Use the strategy pattern to define a family of algorithms, encapsulate each one, and make them interchangeable. * **Why:** Enables you to select an algorithm at runtime and easily add new algorithms without modifying the client code. """typescript interface SortStrategy { sort(data: number[]): number[]; } class BubbleSortStrategy implements SortStrategy { sort(data: number[]): number[] { console.log("Using Bubble Sort"); // Bubble sort implementation (simplified) return [...data].sort((a, b) => a - b); } } class QuickSortStrategy implements SortStrategy { sort(data: number[]): number[] { console.log("Using Quick Sort"); // Quick sort implementation (simplified) - uses standard sort for brevity return [...data].sort((a, b) => a - b); } } class Sorter { constructor(private strategy: SortStrategy) {} setStrategy(strategy: SortStrategy): void { this.strategy = strategy; } sort(data: number[]): number[] { return this.strategy.sort(data); } } // Usage const data = [5, 2, 8, 1, 9, 4]; const bubbleSorter = new Sorter(new BubbleSortStrategy()); const sortedData1 = bubbleSorter.sort(data); console.log(sortedData1); const quickSorter = new Sorter(new QuickSortStrategy()); const sortedData2 = quickSorter.sort(data); console.log(sortedData2); quickSorter.setStrategy(new BubbleSortStrategy()) const sortedData3 = quickSorter.sort(data); // Now uses bubble sort console.log(sortedData3); """ ## 4. Testing Deno Components ### 4.1 Unit Testing * **Do This:** Write unit tests for each component to verify its functionality in isolation. Use Deno's built-in testing framework ("Deno.test"). * **Don't Do This:** Skip unit tests or write tests that are too broad in scope. * **Why:** Unit tests help you catch bugs early and ensure that your components behave as expected. """typescript // Example: Unit test for the formatDate function import { formatDate } from "./date_utils.ts"; import { assertEquals } from "https://deno.land/std@0.212.0/assert/mod.ts"; Deno.test("formatDate should return a formatted date string", () => { const date = new Date(2024, 0, 20); // January 20, 2024 const formattedDate = formatDate(date); assertEquals(formattedDate, "1/20/2024"); }); """ ### 4.2 Integration Testing * **Do This:** Write integration tests to verify that different components work together correctly. Simulate real-world scenarios. * **Don't Do This:** Assume that components will work together correctly without testing their interactions. * **Why:** Integration tests help you detect issues that may arise when different parts of your application interact. """typescript //Example (basic): Check that an http endpoint returns expected json import { assertEquals } from "https://deno.land/std@0.212.0/assert/mod.ts"; Deno.test("Integration: Fetch data and verify structure", async () => { const response = await fetch("https://rickandmortyapi.com/api/character/1"); //Real endpoint, but could be "localhost:8000" for a local Deno server assertEquals(response.status, 200); const data = await response.json(); assertEquals(data.name, "Rick Sanchez"); assertEquals(data.status, "Alive"); }); """ ### 4.3 Mocking * **Do This:** Use mocking libraries (e.g., "std/testing/mock") to isolate components during testing. * **Don't Do This:** Rely on real dependencies in unit tests, as this can make tests slow and unreliable. * **Why:** Mocking allows you to control the behavior of dependencies and focus on testing the logic of a single component. """typescript // Example: Mocking the fetch API import { stub } from "https://deno.land/std@0.212.0/testing/mock.ts"; import { assertEquals } from "https://deno.land/std@0.212.0/assert/mod.ts"; async function fetchData(url: string): Promise<any> { const response = await fetch(url); return await response.json(); } Deno.test("fetchData should return mocked data", async () => { const mockData = { message: "Mocked data" }; const fetchStub = stub( globalThis, "fetch", () => Promise.resolve({ json: () => Promise.resolve(mockData) } as Response), ); const data = await fetchData("https://example.com/api"); assertEquals(data, mockData); fetchStub.restore(); // Restore the original fetch function }); """ ## 5. Documentation and Style ### 5.1 JSDoc Comments * **Do This:** Use JSDoc comments to document all exported functions, classes, and interfaces. * **Don't Do This:** Omit documentation or write vague and unhelpful comments. * **Why:** JSDoc comments make your code easier to understand and use. They can also be used to generate API documentation. """typescript /** * Formats a date object into a string. * * @param {Date} date The date to format. * @returns {string} The formatted date string (e.g., "YYYY-MM-DD"). */ export function formatDate(date: Date): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return "${year}-${month}-${day}"; } """ ### 5.2 Code Formatting * **Do This:** Use a code formatter (e.g., "deno fmt") to ensure consistent code style. * **Don't Do This:** Rely on manual formatting, which can lead to inconsistencies. * **Why:** Consistent code style improves readability and reduces cognitive load. Run "deno fmt" to automatically format your code. ### 5.3 Code Reviews * **Do This:** Conduct code reviews to ensure that code meets the established standards and best practices. * **Don't Do This:** Merge code without review or ignore feedback from reviewers. * **Why:** Code reviews help catch errors, improve code quality, and promote knowledge sharing.