⛈️

BunからDrizzleを用いてD1にクエリする

2024/06/29に公開

やむを得ずWorkersの外部からD1にクエリする場合について考える。ここではBunを用いる。

https://github.com/reiwa/b1

↑ サンプルコード

仕組み

REST APIを用いてD1にクエリできる。

https://developers.cloudflare.com/api/operations/cloudflare-d1-query-database

const endpoint = `https://api.cloudflare.com/client/v4/accounts/${process.env.CLOUDFLARE_ACCOUNT_ID}/d1/database/${process.env.CLOUDFLARE_DATABASE_ID}/query`

const resp = await fetch(endpoint, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,
  },
  body: JSON.stringify({ params, sql }),
})

そしてDrizzleを用いてHTTP経由でも結果を取得できればクエリを送信できる。

https://orm.drizzle.team/docs/get-started-sqlite#http-proxy

const db = drizzle(async (sql, params, method) => {
  return { rows: [] }
})

組み合わせると何処からでもD1にクエリできるはず。

クエリする

簡単なスキーマを定義してデータベースを初期化しておく。

import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"

export const postsTable = sqliteTable("posts", {
  id: integer("id").primaryKey(),
  uuid: text("uuid", { length: 256 }).notNull().unique(),
  title: text("title", { length: 128 }).notNull(),
  text: text("text", { length: 2048 }).notNull(),
})

このようなプログラムを書く。

import { eq } from "drizzle-orm"
import { drizzle } from "drizzle-orm/sqlite-proxy"
import { postsTable } from "~/schema"

const db = drizzle(async (sql, params, method) => {
  const endpoint = `https://api.cloudflare.com/client/v4/accounts/${process.env.CLOUDFLARE_ACCOUNT_ID}/d1/database/${process.env.CLOUDFLARE_DATABASE_ID}/query`

  try {
    const resp = await fetch(endpoint, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,
      },
      body: JSON.stringify({
        params: params,
        sql: sql,
      }),
    })

    const json = await resp.json()

    const [result] = json.result

    if (result.error) {
      throw new Error(result.error)
    }

    const rows = result.results.map((result: never) => {
      return Object.values(result)
    })

    if (method === "get") {
      return { rows: rows[0] }
    }

    return { rows: rows }
  } catch (e) {
    if (e instanceof Error) {
      console.error(e)
    }
    return { rows: [] }
  }
})

後は普通に呼び出す。

await db.insert(postsTable).values({
  uuid: crypto.randomUUID(),
  title: "Hello, World!",
  text: "This is a test post.",
})

const posts = await db.select().from(postsTable).all()

console.log(posts)

const [post] = posts

const firstPost = await db
  .select()
  .from(postsTable)
  .where(eq(postsTable.id, post.id))
  .get()

console.log(firstPost)

Bunで実行する。

bun run app/main.ts

allの場合はこのように返ってくる。

[
  {
    id: 1,
    uuid: "a89f68dc-645d-42cd-ae1f-d351093d1b7e",
    title: "Hello, World!",
    text: "This is a test post.",
  }, {
    id: 2,
    uuid: "83a547f8-65f6-433f-963a-b695ed19be66",
    title: "Hello, World!",
    text: "This is a test post.",
  }
]

最後に

もし使うならエラーを適切に取り扱ったりあとBatchにも対応が必要です。

Aipictors

Discussion