๋ฉ”์ธ ์ฝ˜ํ…์ธ ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
Context๋Š” Sonamu API ๋ฉ”์„œ๋“œ ์‹คํ–‰ ์ค‘์— HTTP ์š”์ฒญ๊ณผ ๊ด€๋ จ๋œ ์ •๋ณด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. API ๋ฉ”์„œ๋“œ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฃผ์ž…๋ฐ›์•„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, AsyncLocalStorage๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

ํƒ€์ž… ์ •์˜

type Context = {
  request: FastifyRequest;
  reply: FastifyReply;
  headers: IncomingHttpHeaders;
  createSSE: <T extends ZodObject>(events: T) => ReturnType<typeof createSSEFactory<T>>;
  naiteStore: NaiteStore;
  locale?: string;
} & AuthContext & ContextExtend;

Context ์†์„ฑ

request

request: FastifyRequest
ํ˜„์žฌ HTTP ์š”์ฒญ์„ ๋‚˜ํƒ€๋‚ด๋Š” Fastify Request ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ์š”์ฒญ URL, ๋ฉ”์„œ๋“œ, ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๋“ฑ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

reply

reply: FastifyReply
ํ˜„์žฌ HTTP ์‘๋‹ต์„ ๋‚˜ํƒ€๋‚ด๋Š” Fastify Reply ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ์‘๋‹ต ํ—ค๋” ์„ค์ •, ์ƒํƒœ ์ฝ”๋“œ ๋ณ€๊ฒฝ ๋“ฑ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

headers

headers: IncomingHttpHeaders
HTTP ์š”์ฒญ ํ—ค๋”์ž…๋‹ˆ๋‹ค. request.headers์™€ ๋™์ผํ•œ ๊ฐ’์œผ๋กœ, ํŽธ์˜๋ฅผ ์œ„ํ•ด ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.

createSSE

createSSE: <T extends ZodObject>(events: T) => ReturnType<typeof createSSEFactory<T>>
Server-Sent Events(SSE) ์ŠคํŠธ๋ฆผ์„ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. Zod ์Šคํ‚ค๋งˆ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํƒ€์ž… ์•ˆ์ „ํ•œ ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ ์˜ˆ์‹œ:
import { z } from "zod";

class MyModelClass extends BaseModel {
  @api()
  @stream({
    type: "sse",
    events: z.object({
      progress: z.object({ percent: z.number() }),
      complete: z.object({ result: z.string() })
    })
  })
  async processData(ctx: Context) {
    const sse = ctx.createSSE({
      progress: z.object({ percent: z.number() }),
      complete: z.object({ result: z.string() })
    });

    // ์ง„ํ–‰๋ฅ  ์ „์†ก
    await sse.send("progress", { percent: 50 });
    
    // ์™„๋ฃŒ ์ด๋ฒคํŠธ ์ „์†ก
    await sse.send("complete", { result: "done" });
    
    return sse.getResponse();
  }
}

naiteStore

naiteStore: NaiteStore
Naite ํ…Œ์ŠคํŒ… ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ €์žฅ์†Œ์ž…๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ค‘ ๋ชจํ‚น๋œ ๋ฐ์ดํ„ฐ๋‚˜ ์Šค๋ƒ…์ƒท ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” ์šฉ๋„๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

locale

locale?: string
ํ˜„์žฌ ์š”์ฒญ์˜ locale(์–ธ์–ด ์„ค์ •)์ž…๋‹ˆ๋‹ค. sonamu.config.ts์—์„œ i18n ์„ค์ •์ด ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์„ ๋•Œ๋งŒ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. Accept-Language ํ—ค๋”๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ ์ง€์›ํ•˜๋Š” locale ์ค‘ ํ•˜๋‚˜๋ฅผ ์ž๋™์œผ๋กœ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ์„ค์ • ์˜ˆ์‹œ:
// sonamu.config.ts
export default {
  i18n: {
    defaultLocale: "ko",
    supportedLocales: ["ko", "en", "ja"]
  }
} satisfies SonamuConfig;

AuthContext ์†์„ฑ

Context๋Š” AuthContext์˜ ์†์„ฑ๋“ค๋„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค:
  • user: ํ˜„์žฌ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด
  • passport: ์ธ์ฆ ๊ด€๋ จ ๋ฉ”์„œ๋“œ (login, logout)

Context ํ™•์žฅ

ํ”„๋กœ์ ํŠธ์—์„œ Context์— ์ปค์Šคํ…€ ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ContextExtend ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
// src/types.ts
declare module "sonamu" {
  interface ContextExtend {
    customProperty: string;
    helperMethod: () => void;
  }
}
์ด๋ ‡๊ฒŒ ํ™•์žฅ๋œ ์†์„ฑ์€ contextProvider๋ฅผ ํ†ตํ•ด ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
// sonamu.config.ts
export default {
  server: {
    apiConfig: {
      contextProvider: async (baseContext) => {
        return {
          ...baseContext,
          customProperty: "custom value",
          helperMethod: () => console.log("helper")
        };
      }
    }
  }
} satisfies SonamuConfig;

๊ด€๋ จ ๋ฌธ์„œ