encore-code-review

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Encore Code Review

Encore代码审核

Instructions

说明

When reviewing Encore.ts code, check for these common issues:
审核Encore.ts代码时,请检查以下常见问题:

Critical Issues

严重问题

1. Infrastructure Inside Functions

1. 函数内部定义基础设施

typescript
// WRONG: Infrastructure declared inside function
async function setup() {
  const db = new SQLDatabase("mydb", { migrations: "./migrations" });
  const topic = new Topic<Event>("events", { deliveryGuarantee: "at-least-once" });
}

// CORRECT: Package level declaration
const db = new SQLDatabase("mydb", { migrations: "./migrations" });
const topic = new Topic<Event>("events", { deliveryGuarantee: "at-least-once" });
typescript
// 错误示例:在函数内部声明基础设施
async function setup() {
  const db = new SQLDatabase("mydb", { migrations: "./migrations" });
  const topic = new Topic<Event>("events", { deliveryGuarantee: "at-least-once" });
}

// 正确示例:包级别声明
const db = new SQLDatabase("mydb", { migrations: "./migrations" });
const topic = new Topic<Event>("events", { deliveryGuarantee: "at-least-once" });

2. Using require() Instead of import

2. 使用require()而非import

typescript
// WRONG
const { api } = require("encore.dev/api");

// CORRECT
import { api } from "encore.dev/api";
typescript
// 错误示例
const { api } = require("encore.dev/api");

// 正确示例
import { api } from "encore.dev/api";

3. Wrong Service Import Pattern

3. 错误的服务导入方式

typescript
// WRONG: Direct import from another service
import { getUser } from "../user/api";

// CORRECT: Use ~encore/clients
import { user } from "~encore/clients";
const result = await user.getUser({ id });
typescript
// 错误示例:直接导入其他服务
import { getUser } from "../user/api";

// 正确示例:使用~encore/clients
import { user } from "~encore/clients";
const result = await user.getUser({ id });

4. Missing Error Handling

4. 缺少错误处理

typescript
// WRONG: Returning null for not found
const user = await db.queryRow`SELECT * FROM users WHERE id = ${id}`;
if (!user) return null;

// CORRECT: Throw APIError
import { APIError } from "encore.dev/api";

const user = await db.queryRow`SELECT * FROM users WHERE id = ${id}`;
if (!user) {
  throw APIError.notFound("user not found");
}
typescript
// 错误示例:未找到时返回null
const user = await db.queryRow`SELECT * FROM users WHERE id = ${id}`;
if (!user) return null;

// 正确示例:抛出APIError
import { APIError } from "encore.dev/api";

const user = await db.queryRow`SELECT * FROM users WHERE id = ${id}`;
if (!user) {
  throw APIError.notFound("user not found");
}

5. SQL Injection Risk

5. SQL注入风险

typescript
// WRONG: String concatenation
await db.query(`SELECT * FROM users WHERE email = '${email}'`);

// CORRECT: Template literal with automatic escaping
await db.queryRow`SELECT * FROM users WHERE email = ${email}`;
typescript
// 错误示例:字符串拼接
await db.query(`SELECT * FROM users WHERE email = '${email}'`);

// 正确示例:使用自动转义的模板字面量
await db.queryRow`SELECT * FROM users WHERE email = ${email}`;

Warning Issues

警告问题

6. Missing Type Annotations

6. 缺少类型注解

typescript
// WEAK: No explicit types
export const getUser = api(
  { method: "GET", path: "/users/:id", expose: true },
  async ({ id }) => {
    return await findUser(id);
  }
);

// BETTER: Explicit request/response types
interface GetUserRequest { id: string; }
interface User { id: string; email: string; name: string; }

export const getUser = api(
  { method: "GET", path: "/users/:id", expose: true },
  async ({ id }: GetUserRequest): Promise<User> => {
    return await findUser(id);
  }
);
typescript
// 不规范:无显式类型
export const getUser = api(
  { method: "GET", path: "/users/:id", expose: true },
  async ({ id }) => {
    return await findUser(id);
  }
);

// 更优:显式定义请求/响应类型
interface GetUserRequest { id: string; }
interface User { id: string; email: string; name: string; }

export const getUser = api(
  { method: "GET", path: "/users/:id", expose: true },
  async ({ id }: GetUserRequest): Promise<User> => {
    return await findUser(id);
  }
);

7. Exposed Internal Endpoints

7. 暴露内部端点

typescript
// CHECK: Should this cron endpoint be exposed?
export const cleanupJob = api(
  { expose: true },  // Probably should be false
  async () => { /* ... */ }
);
typescript
// 检查:这个定时任务端点是否应该暴露?
export const cleanupJob = api(
  { expose: true },  // 通常应该设为false
  async () => { /* ... */ }
);

8. Non-Idempotent Subscription Handlers

8. 非幂等的订阅处理器

typescript
// RISKY: Not idempotent (pubsub has at-least-once delivery)
const _ = new Subscription(orderCreated, "process-order", {
  handler: async (event) => {
    await chargeCustomer(event.orderId);  // Could charge twice!
  },
});

// SAFER: Check before processing
const _ = new Subscription(orderCreated, "process-order", {
  handler: async (event) => {
    const order = await getOrder(event.orderId);
    if (order.status !== "pending") return;  // Already processed
    await chargeCustomer(event.orderId);
  },
});
typescript
// 风险:非幂等(发布订阅至少投递一次)
const _ = new Subscription(orderCreated, "process-order", {
  handler: async (event) => {
    await chargeCustomer(event.orderId);  // 可能重复扣费!
  },
});

// 更安全:处理前先检查
const _ = new Subscription(orderCreated, "process-order", {
  handler: async (event) => {
    const order = await getOrder(event.orderId);
    if (order.status !== "pending") return;  // 已处理过则返回
    await chargeCustomer(event.orderId);
  },
});

9. Secrets Called at Module Level

9. 模块级别调用密钥

typescript
// WRONG: Secret accessed at startup
const stripeKey = secret("StripeKey");
const client = new Stripe(stripeKey());  // Called during import

// CORRECT: Access inside functions
const stripeKey = secret("StripeKey");

async function charge() {
  const client = new Stripe(stripeKey());  // Called at runtime
}
typescript
// 错误示例:启动时访问密钥
const stripeKey = secret("StripeKey");
const client = new Stripe(stripeKey());  // 导入时调用

// 正确示例:在函数内部访问
const stripeKey = secret("StripeKey");

async function charge() {
  const client = new Stripe(stripeKey());  // 运行时调用
}

Review Checklist

审核检查清单

  • All infrastructure at package level
  • Using ES6 imports, not require()
  • Cross-service calls use
    ~encore/clients
  • Proper error handling with APIError
  • SQL uses template literals
  • Request/response types defined
  • Internal endpoints have
    expose: false
  • Subscription handlers are idempotent
  • Secrets accessed inside functions, not at import time
  • Migrations follow naming convention (001_name.up.sql)
  • 所有基础设施定义在包级别
  • 使用ES6导入,而非require()
  • 跨服务调用使用
    ~encore/clients
  • 使用APIError进行正确的错误处理
  • SQL查询使用模板字面量
  • 定义了请求/响应类型
  • 内部端点设置
    expose: false
  • 订阅处理器是幂等的
  • 在函数内部访问密钥,而非导入时
  • 迁移文件遵循命名规范(001_name.up.sql)

Output Format

输出格式

When reviewing, report issues as:
[CRITICAL] [file:line] Description of issue
[WARNING] [file:line] Description of concern  
[GOOD] Notable good practice observed
审核时,请按以下格式报告问题:
[CRITICAL] [文件:行号] 问题描述
[WARNING] [文件:行号] 关注点描述  
[GOOD] 值得注意的最佳实践