# `_shared` · Edge Function 共享中间件

> 解决 30+ Edge Function 重复写 CORS / 错误处理 / 依赖漂移 的问题。
> 新函数请优先使用本目录提供的工具；老函数可渐进迁移。

## 文件总览

| 文件 | 作用 |
|---|---|
| `deps.ts` | **唯一**第三方依赖入口（Supabase Client / Zod 等）。升级版本只改这里。 |
| `cors.ts` | CORS 头 + OPTIONS 预检 |
| `trace.ts` | trace_id 生成与日志辅助 |
| `response.ts` | `jsonResponse` / `errorResponse` 统一响应 |
| `validate.ts` | `parseJsonBody(req, schema)` / `parseQuery(req, schema)` — Zod 校验，统一 400 输出 |
| `sse.ts` | `sseStream(ctx, producer)` — SSE 流封装，自带心跳/CORS/异常 → error 帧 |
| `supabase.ts` | `getServiceClient()` / `getUserClient(req)` 客户端工厂 |
| `middleware.ts` | `withMiddleware()` 一站式包裹 |

## 推荐写法

```ts
import { withMiddleware } from "../_shared/middleware.ts";
import { getServiceClient } from "../_shared/supabase.ts";

Deno.serve(withMiddleware("my-fn", async (req, ctx) => {
  if (req.method !== "POST") return ctx.fail("Method not allowed", 405);

  const body = await req.json();
  ctx.log("payload", body);

  const supabase = getServiceClient();
  const { data, error } = await supabase.from("products").select("id, name").limit(5);
  if (error) return ctx.fail(error.message, 500, { code: "DB_ERROR" });

  return ctx.json({ data });
}));
```

### 输入校验（Zod）

```ts
import { withMiddleware } from "../_shared/middleware.ts";
import { parseJsonBody } from "../_shared/validate.ts";
import { z } from "../_shared/deps.ts";

const Body = z.object({
  phone: z.string().regex(/^1\d{10}$/),
  code: z.string().length(6),
});

Deno.serve(withMiddleware("verify-sms-code", async (req, ctx) => {
  const parsed = await parseJsonBody(req, Body);
  if (!parsed.ok) {
    return ctx.fail(parsed.message, 400, {
      code: "VALIDATION_ERROR",
      details: parsed.issues,
    });
  }
  const { phone, code } = parsed.data;
  // ...
}));
```

### SSE 流

```ts
import { sseStream } from "../_shared/sse.ts";

Deno.serve(withMiddleware("chat-stream", async (req, ctx) => {
  return sseStream(ctx, async ({ send }) => {
    for await (const token of upstreamTokens()) {
      send({ type: "delta", text: token });
    }
    send({ type: "done" });
  });
}));
```

中间件自动处理：
- ✅ CORS 预检（OPTIONS 直接 204）
- ✅ `x-trace-id` 透传 / 生成
- ✅ 入口/出口日志（含耗时）
- ✅ 未捕获异常 → 500 + 结构化 JSON

## 迁移老函数的最小步骤

1. 把 `import { createClient } from "https://esm.sh/..."` 改成 `from "../_shared/deps.ts"`
2. 把手写 `corsHeaders` 删掉，改用 `withMiddleware` 包裹 handler
3. `new Response(JSON.stringify(...), {...})` 替换为 `ctx.json(...)` / `ctx.fail(...)`

## 红线

- ❌ 不要在业务函数里直接写 `https://esm.sh/...` 导入第三方库（除非不在 `deps.ts` 范围内）
- ❌ 不要在 `_shared/` 引入 `npm:openai` 等重型依赖（保持纯净，避免 503 复发）
- ❌ 不要在前端代码里 `import` `_shared/` 文件（仅 Deno 运行时使用）
