3K Views
September 25, 24
スライド概要
2024/9/25に開催されたRemix Tokyo Meetup (https://lu.ma/4yu4sbys) にて発表しました。
Workers環境でもprocess.envのようにグローバルなコンテキストで環境変数を使える方法を検討しました。
Remix x Workers でグローバルなenv 乾 夏衣 合同会社六花 @_kaiinui
グローバルにenvがとれない
リクエストハンドラからenvやctxを引き渡していくコードを書くこと
に ⇒ 環境特有なコード感がすごい
export async function loader({ context, request }: LoaderFunctionArgs) {
const rawText = new URL(request.url).searchParams.get("text");
if (!rawText) {
return new Response("Bad Request", {
status: 400,
});
}
const text = await convertToKatakana(rawText, {
apiKey: context.cloudflare.env.AIROUTER_API_KEY,
kv: context.cloudflare.env.G2P_CACHE,
});
async function toKatakana(words: string[], opts: Options): Promise
const prompt = words.join(", ");
const res = await generate({
apiKey: opts.apiKey,
system,
message: prompt,
});
const result = res.replace("RESULT=", "")
return result.split(",").map((l) => l.tr
}
export async f
text: string
deps: Options,
Promise<string> {
const storage = createStorage({
driver: cloudflareKVBindingDriver({ binding: deps.kv }),
});
AsyncLocalStorageを使おう
AsyncLocalStorageで(疑似)グローバル化
import { AsyncLocalStorage } from "node:async_hooks";
import type { AppLoadContext } from "@remix-run/cloudflare";
const storage = new AsyncLocalStorage<AppLoadContext>();
export async function withContext<T>(ctx: AppLoadContext, fn: () => T) {
return storage.run(ctx, () => {
return fn();
});
}
export function getContext(): AppLoadContext {
const ctx = storage.getStore();
if (!ctx) {
throw new Error("Context not found");
}
return ctx;
}
export function getEnv(): AppLoadContext["cloudflare"]["env"] {
return getContext().cloudflare.env;
}
AsyncLocalStorageを使おう
リクエストハンドラをALSでラップ
export async function loader({ context, request }: LoaderFunctionArgs) {
return withContext(context, async () => {
const rawText = new URL(request.url).searchParams.get("text");
※./functions/[[path]].tsとかで共通処理化できると思いますが時間が足りず…!
(とりまぶちこんだだけだと上手くいかなかった)
AsyncLocalStorageを使おう
getEnvでどこからでもenvを取得。
注意: グローバル定数にはできず、生成関数を用意する必要がある
実装を工夫すれば、node環境とユニバーサルにもできる!
const google = createGoogleGenerativeAI({
apiKey: process.env.GOOGLE_AI_API_KEY,
});
const model = google("gemini-1.5-flash-002")
const makeModel = () => {
const google = createGoogleGenerativeAI({
apiKey: getEnv().GOOGLE_AI_API_KEY,
});
return google("gemini-1.5-flash-002")
};
スッキリ!
export async function convertToKatakana(
text: string,
deps: Options,
): Promise<string> {
const storage = createStorage({
driver: cloudflareKVBindingDriver({ binding: deps.kv }),
});
export async function convertToKatakana(text: string): Promise<string> {
const storage = makeStorage()
if (isOnlyLaughing(text)) {
スッキリ!
async function toKatakana(words: string[], opts: Options): Promise<string[]> {
const prompt = words.join(", ");
const res = (await callLLM({
apiKey: opts.apiKey,
system,
message: prompt,
})) as { RESULT: string };
return res.RESULT.split(",").map((l) => l.trim());
}
async function toKatakana(words: string[]): Promise<string[]> {
const prompt = words.join(", ");
const res = await generate({
system,
message: prompt,
});
補題: hono/context-storage
Honoにもv4.6.0でcontext-storageとして同様の機能が入っている。
最近リリースされたhono-remix-adapterもあわせてみたい。
app.use(contextStorage())
const getMessage = () => {
return getContext<Env>().var.message
}
import serverAdapter from 'hono-remix-adapter/vite'
export default defineConfig({
plugins: [
// ...
remix(),
serverAdapter({
entry: 'server/index.ts',
}),
],
})
おまけ: ユニバーサルenv
export function getEnv(): CloudflareBindings {
try {
return getContext<AppEnv>().env;
} catch {
return process.env as unknown as CloudflareBindings;
}
}