読む:SQLに対するバックエンドのアプローチ比較、そしてSafeQLの紹介

以下スクラップからの派生
これ読む
バックエンドの知識0に近いので理解できない箇所も多いだろうがとりあえず。

へー
ORM やクエリビルダを利用したり、逆に SQL を記述してコード生成を行ったりと、様々な方法があります。
まず、SQL に対するアプローチには大きく分けて 2 つの方法があります。
- SQL を覆い隠す方法
- 記述した SQL に型を付ける方法

SQL を覆い隠す方法

なるほど。裏で生のsqlが書かれるみたいな感じ?
これらのライブラリは、提供されるメソッドを呼び出すことで、SQL の記述をライブラリが担ってくれるため、SQL を直接記述する必要がありません。

ほー
使い方のフローとしては以下のとおりです。
- スキーマファイルを記述する
- メソッドを呼び出してクエリを実行する

まずはormから
スキーマファイルってやつを作る
Prisma では、独自のスキーマファイルを記述します。
model User { id Int @id @default(autoincrement()) email String @unique name String } ...
自動で作ってくれたりもするよってことかな?
なお、Prisma は DB にアクセスしてスキーマファイルを生成する introspect 機能も提供しています。

へー、prismaは何となくわかった
スキーマファイルを記述したら、以下のようにクエリを実行します。
const user = await prisma.user.findUnique({ where: { email: "elsa@prisma.io", }, });

drizzle ormはprismaと違ってスキーマファイルが独自の書き方ではなく普通にtsで書くってことか
Drizzle ORM では、TypeScript のオブジェクトとしてスキーマを記述します。
import { integer, pgTable, serial, text } from "drizzle-orm/pg-core"; export const user = pgTable("user", { id: serial("id").primaryKey(), name: text("name"), });

ふむ
なお、Drizzle ORM も introspect 機能を提供しています。

へぇ、drizzle ormは書き方2つあんのか。
スキーマを記述したら、以下のようにクエリを実行します。
クエリの方法としては、Prisma 風にクエリを記述する方法と、SQL 風にクエリを記述する方法があります。const result = await db.query.users.findMany({ with: { posts: true, }, });
const result = await db.select().from(users);

次はormではなくクエリビルダのKyselyについて
ふむ、スキーマファイルが不要ってことかな
Kysely では、スキーマ生成について特に記述はありません。
kysely の場合は自分で型を記述する必要があります。こちらも、型を生成するアプローチがいくつか存在しています。
そういう訳でもなさそう?よくわからん
kysely-introspect というライブラリを利用することで、スキーマから型を生成することができます。

(スキーマの記述方法がわからんが)これ一番直感的で好きかも
スキーマを記述したら、以下のようにクエリを実行します。
const persons = await db .selectFrom("person") .select("id") .where("first_name", "=", "Arnold") .execute();
supabaseもこんな感じだったから慣れてるのが理由かもしれない

記述した SQL に型を付ける方法

なるほど、生のsqlに近いのかな?
SQL ファイルとして別途クエリを記述し、これに対してコードジェネレータなどを利用して型を付ける方法です。
そうっぽい
有名どころのライブラリを提示します。
- sqlc
- TypedSQL
これらのライブラリでは、記述された SQL ファイルを外部・DB のクエリパーサで解析し、TypeScript の型定義を生成します。
sqlを生で書きたい人はこれが良いんだろうなぁ

ふむ
使い方のフローとしては以下のとおりです。
- SQL ファイルを記述する
- 設定ファイルを記述する
- コード生成を行い、型定義を生成する
- 生成された型定義を利用してクエリを実行する

追記:「SQLファイルを記述する」の部分は、スキーマ定義のファイルとアプリケーション側(使う側)のファイルの2つが必要っぽい

まずsqlcから
スキーマ定義側。やっぱりsql感がすごい
sqlc
まず、以下のような SQL ファイルを記述します。
schema.sql というファイルに、スキーマ定義を記述します。CREATE TABLE authors ( id BIGSERIAL PRIMARY KEY, name text NOT NULL, bio text );
これが使う側か
また、query.sql というファイルに、アプリケーション内で呼び出すクエリを記述します。
-- name: GetAuthor :one SELECT * FROM authors WHERE id = $1 LIMIT 1;

ぬ、スキーマ定義とは別に設定ファイルというものが必要なのか
次に、以下のような sqlc.yaml ファイルに設定を記述します。
version: "2" plugins: - name: ts wasm: url: (sqlcのwasmのURL) sha256: (対応するハッシュ値) sql: - engine: "postgresql" queries: "query.sql" schema: "schema.sql" database: managed: true codegen: - out: db plugin: ts options: runtime: node driver: pg

コード生成と型定義の生成?よくわからん
- コード生成を行い、型定義を生成する
設定ファイルを記述したら、以下のコマンドを実行してコード生成を行います。
sqlc generate

だめだ全然わからん
生成されたクエリは、以下のように利用します。
import postgres from "postgres"; import { getAuthor, } from "./db/query_sql"; ... // Get that author const seal = await getAuthor(sql, { id: author.id }); if (seal === null) { throw new Error("seal not found"); } console.log(seal);

次、TypedSQL
このsql文の意味を読み取りたいなぁ
TypedSQL
TypedSQL は、Prisma と連携して提供されるライブラリです。
スキーマは Prisma のスキーマを利用するため、クエリのみを記述します。この SQL ファイルはprisma/sql/ディレクトリに配置します。
SELECT u.id, u.name, COUNT(p.id) as "postCount" FROM "User" u LEFT JOIN "Post" p ON u.id = p."authorId" GROUP BY u.id, u.name
GROUP BY はグループ分け
LEFT JOIN / ON は左外部結合
左の行は強制的に全て表示し、右テーブルには全ての値がNULLである行を生成して結合
これかなり図示がわかりやすいし総復習になる
多分u
はUserテーブルの略でp
はPostテーブルの略かと思われる
なるほど、なんとなくこのsql文わかった
「ユーザは全部出すけど、それに合致するpostのデータもあればくっつける、なかったらnullになる」
かな

GPTに聞いてみたらかなり近かった。なるほど~~

さっきのprismaの独自のスキーマファイルとはだいぶ違うなぁ
次に、設定ファイルを記述します。
TypedSQL の場合、ORM で紹介した Prisma と連携して提供されるため、Prisma に設定を記述します。generator client { provider = "prisma-client-js" previewFeatures = ["typedSql"] }

やはりコード生成&型生成のコマンド実行は必須なのか
設定ファイルを記述したら、以下のコマンドを実行してコード生成を行います。
prisma generate --sql

ほう
生成されたクエリは、以下のように利用します。
import { PrismaClient } from "@prisma/client"; import { getUsersWithPosts } from "@prisma/client/sql"; const prisma = new PrismaClient(); const usersWithPostCounts = await prisma.$queryRawTyped(getUsersWithPosts()); console.log(usersWithPostCounts);
-
import { getUsersWithPosts } from "@prisma/client/sql";
とは?- さっきのsqlファイルのこと?であればそのsql実行結果が
getUsersWithPosts()
の実行結果として取得できる感じか?
- さっきのsqlファイルのこと?であればそのsql実行結果が
-
$queryRawTyped()
とは?- 受け取ったsqlの結果を良い感じに取得するメソッドかな?

アプローチの比較
- SQL を覆い隠す方法
- 記述した SQL に型を付ける方法
の比較

たしかに
前者のアプローチは、SQL を覆い隠すことで便利に扱うというものです。
これに対し後者のアプローチは、SQL をそのまま記述し、そこから便利な機能を後付けするというものです。
なるほどなぁ
ふむふむ
それぞれのアプローチには、メリットとデメリットがあります。

なるほど
SQL を覆い隠す方法
メリット
- SQL を直接記述する必要がない
- メソッドで隠蔽されているため、型安全性やエラーチェックが行いやすい
- 厚いラッパーが提供され、便利な機能も多く提供されることがある
デメリット
- SQL の記述をそのまま行えないため、複雑なクエリを記述する際に不便
- ORM・クエリビルダの使い方自体を覚える必要がある
- したがって別言語・別環境への移行コストが高く、コードを使い回すことが難しい
用意されてるから楽だし型安全だけど、複雑なクエリは書きづらいし独自性高くて面倒だったりするよね~という感じ

なるほどー
記述した SQL に機能を後付けする方法
メリット
- SQL をそのまま記述できるため、複雑なクエリも記述しやすい
- SQL ファイルを利用するため、既存の SQL をそのまま利用できる
- コード生成により、型安全性が担保される
また余談ですが、ORM を記述している頃は生の SQL が書けないという後ろめたさがありました。このような感情への対処法として、SQL をそのまま記述する方法は有用であると考えられます。
デメリット
- コード生成の手間がかかる
- sql 記述時にはエラーが発生しないため、別の手段でエラーチェックを行う必要がある
- sql ファイルが分離されるため、命名が面倒
別にこっちもコード生成によって型安全性は担保されるのか。んで複雑なクエリも書きやすい
ただ面倒だったりするよね~あとエラー検知しづらいよね~みたいな

ふむ
3 つ目のデメリットについては賛否両論が存在します。
SQL のクエリを分離することで、SQL の再利用性が高まることは確かです。
一方で、クエリを分離することで関心事が分散し、コードの可読性が下がるという意見もあります。クエリというものは性質上アプリケーションと密結合であり、どちらが好ましいかは人により様々な意見があると思われます。

SafeQL とは
ついに本題

ほー、良いとこ取り的な?構成ファイルも不要なのか
SafeQL は、コード内部で SQL を記述し、これに対して型を付けることができるライブラリです。
分類的には後者のアプローチ(記述した SQL に機能を後付ける方法)に該当します。
しかし、書き心地としては前者のアプローチのように型を付けられ、構成ファイルを記述する必要がなく、使い勝手が良いと考えられます。
そうなのか、safeql使うならpostgresqlの勉強もせねばだな
なお、対応している DB は PostgreSQL のみです。
safeqlはeslintルールなのか
また、SafeQL は eslint ルールとして実装されるため、eslint の導入が必須となっています。
クエリの解析は実際に DB にアクセスして行われます。

これ見たらpostgresqlインストールされてなかったのでインストールするか
psql --version
windows版をインストール
なのにpsql --version
がエラーになったのでGPTの言う通りシステム環境変数のPATHにpostgresqlのbinフォルダのpathを設定すると、
出てくるようになった!すばらし

よし実行してみるか
SafeQL は単に SQL クエリに型が付くだけなので、マイグレーション周りは別途のライブラリを利用する必要があります。
ここは、SQL ファイルを記述してコード生成する方法と同じです。
今回はマイグレーションの手間を省くため、そのまま SQL を実行します。
ローカルで Postgres を起動します。なお、接続 URL はpostgresql://localhost:5432/postgresです。psql -h localhost -p 5432 -U postgres
ちゃんと接続してるっぽい
Claudeに聞いた

んで以下sqlをターミナルで実行か
CREATE DATABASE "safeql-test";
\c safeql-test
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
claudeの説明
よし、無事完了

これはプロジェクトでインストールしちゃうか、そのまま使うかもだし
次に、SafeQL をインストールします。今回は pnpm を利用しています。
pnpm install -D @ts-safeql/eslint-plugin libpg-query
pnpmは入れてないのでnpmに変えて実行
ん、大量のエラーが出るな
Claudeに聞いた
「node gyp」で検索したら良さげな記事発見
Visual StudionC++がインストールされていないことが原因っぽい...?
Pythonはinstallしてある
たしかにちょこちょこ「C++が見当たらないぞ」ってエラーコードがある
とりあえずnpm install --global windows-build-tools
実行してみるか?
しかしClaude曰くこれ
ただし、2024年現在では windows-build-tools パッケージは非推奨となっており、代わりに直接 Visual Studio のインストーラーから「C++ によるデスクトップ開発」ワークロードをインストールする方法が推奨されています。この方が確実で新しい方法です。
たしかに記事コメント欄にもエラーが出たとある。たしかに古い記事だからなぁ
んでgithubも2021年にアーカイブされてしまってるので開発止まってるということか。確かにやめといたほうがよさそうかも
ここにもこう書かれてる
This package has been deprecated
Author message:
Node.js now includes build tools for Windows. You probably no longer need this tool. See https://github.com/felixrieseberg/windows-build-tools for details.

さっきのClaudeの言う通りVisual Studio Installerを開き、C++デスクトップ開発ツールをインストールした方が良さそうだな
7.82GB...重いな、まぁしゃあない
(なんでこのnpm iしたパッケージはC++関連必要なんだろう?初めて遭遇した)

インストール完了したので再度npm install -D @ts-safeql/eslint-plugin libpg-query
実行してみる
よっしゃ!成功

次これ
次に、eslint.config.mjsを以下のように記述します。
import safeql from "@ts-safeql/eslint-plugin/config"; import tseslint from "typescript-eslint"; export default tseslint.config( ...tseslint.configs.recommended, safeql.configs.connections({ databaseUrl: "postgresql://postgres@localhost:5432/safeql-test?sslmode=disable", // 接続先の URL targets: [{ tag: "sql", transform: "{type}[]" }], }) );
最後
最後に、src/index.tsに以下のように記述します。
import postgres from "postgres"; const main = async () => { const sql = postgres( "postgresql://postgres@localhost:5432/safeql-test?sslmode=disable" ); const result = await sql`SELECT id, name FROM users WHERE id = ${1}`; console.log(result); }; main();
すでにhonoのプロジェクトということでindex.tsxがあるのでindex.tsではなくindex.tsxに追記するか

んー、出てこないなぁ
ここまで記述すると、SafeQL によって以下のように型の提案が行われます。
ていうかbun run devしようとするとエラーで強制終了してサーバー落ちるな
index.tsxに追加したものが原因っぽい
さてどうすれば良いだろう

そういやpdAdminというGUIツールもインストールしてあるので、これで接続など確認してみるか
んー、見てもよくわからんぞ...

お!GPTに聞いてみたらとりあえずbun run devは繋がるようになった!
index.tsxとeslint.config.mjsにあるURL
""postgresql://postgres@localhost:5432/safeql-test?sslmode=disable""
を
"postgresql://postgres:<your-password>@localhost:5432/safeql-test?sslmode=disable"
というように、パスワード追加したらいけた!
<your-password>
の部分を自分のpostgresqlパスワードに置き換える。
パスワードをハードコーディングすんのは良くないので.envファイル作って移しとくか

ていうかenvの読み取りどうやってやるんだろう、honoって
...だめだ、わからん。wrangler.toml編集とかhonoで用意されてるやつとか試してみたけど全然取れない。全部undefined。
仕方ないので普通のtsファイル作ってそこに変数書いてそれをgitignoreしておきつつ、index.tsxでimportするって感じにしよう。前やった方法
ちなみに同じ階層にそのtsファイル作らないとなぜか読み取れないので同じ階層内に.env.ts作成。
と言う感じでとりあえず読み取れたのでok
import { DATABASE_URL } from './env'
import postgres from "postgres";
const main = async () => {
const sql = postgres(DATABASE_URL);
const result = await sql`SELECT id, name FROM users WHERE id = ${1}`;
console.log("resultだよ", result);
console.log('urlだよ', DATABASE_URL)
};
main();
import safeql from "@ts-safeql/eslint-plugin/config";
import tseslint from "typescript-eslint";
import { DATABASE_URL } from './src/env';
export default tseslint.config(
...tseslint.configs.recommended,
safeql.configs.connections({
databaseUrl: DATABASE_URL,
targets: [{ tag: "sql", transform: "{type}[]" }],
})
);

さて、そして残った問題としては、「なぜか型推論が効かない」ということ。index.tsx内
なんでだろう

あーこれなのかな?tsxじゃなくてtsにしか効かないからこれだとダメ、みたいな?
import safeql from "@ts-safeql/eslint-plugin/config";
import tseslint from "typescript-eslint";
自分が今使ってるのはindex.tsxなので
ん?いやそもそもこのtypescript-eslintってやつ補完出てこない。単純にインストールしてないわこれ。エラー出てないのはなんでだろ、このmjsファイルには推論とかが効いてないのか。
import tseslint from "typescript-eslint";
とりあえずnpm i -D typescript-eslint
実行
うーん、それでも変わらずindex.tsxで型推論は出ないなぁ...

単純にdb.tsにクエリは分けるか
分けてみたけど依然推論は効かない
.tsなんだけどなぁ

ん!package.json眺めてたらtypescriptそのものがインストールされていないことに気付いた
インストールする
npm install -D typescript
...でもだめだった。まじかぁ

ぬ!!vscodeの右下の{}
っていうところにバツついてたからホバーしてみたら、こうなってる!
ESLintが効いていない?これっぽいぞ、見てみよう
Open ESLint Outputを押すと、こんな感じ
Cannot find module 'C:\Users\〇〇\src\env' imported from C:\Users\〇〇\eslint.config.mjs
なるほど~~
実はESLintさんはenvが読み込めておらず、それゆえエラーが出てしまい、それに伴ってESLintさんの構文チェックの機能が死んでしまっている ということでは!?
しかしenvからコードジャンプでちゃんと定義元のenv.tsに飛べるので、vscodeはちゃんとenvファイルを捕捉できているはず。だがESLintは捕捉できてないってことか、おそらく
vscodeではエラー出てなかったから余計混乱した、落とし穴すぎるなこれ
とりあえず、.tsとかで適当にenv作るのはダメなのか...学びだ
じゃあやはりしっかりとenv作る/取得する方法を探さないといけないなー、調べるか
dotenvとかいうやつインストールするとかよく聞くけど、外部ライブラリインストールするのなんか嫌だな、本当に必要なんだろうか

そういえばcloudflare pagesをテンプレートとして使用しているが、viteも使ってるらしいこれ
確かにpackage.jsonにviteがインストールされてる。viteでrun devとかされるようにもなってる
んでViteの環境変数の取得方法を使えるのでは?と思ったらビンゴ!よっしゃ~~~
viteは内部でdotenv使ってるっぽい。へぇ
Vite は、環境ディレクトリーにある以下のファイルから追加の環境変数を読み込むために dotenv を利用します。
VITE_
で始まる名前にしないと取得できないのは注意
- .env.developmentファイルをプロジェクト直下に作成
- ファイル内に
VITE_DATABASE_URL=〇〇
と書く -
import.meta.env.VITE_DATABASE_URL
で取得できる

ということでvscodeの右下にあるeslint見てみたら、まだバツがついたままだ、、、
ログ見てみたらこれ
TypeError: Cannot read properties of undefined (reading 'VITE_DATABASE_URL')
うーん、環境変数をViteの仕様通りちゃんと取得してもだめなのかぁ
とりあえず何とかこれのエラーを解消したいな

試しに直接環境変数をハードコーディングしてみたらできたぞ...!!
eslintのバツも解消された
やっぱりviteのあれやこれやをeslintが検知できないのが問題だったんだな...
GPT
ESLint 実行時には Vite のビルド環境ではないため、そのままでは値を取得できません。
そしてproblemsターミナルのヒントボタンみたいなやつ押してfix ~~も押して自動修正もできた。ナイス。
だがハードコーディングするのは問題だな、どうしよう

ん、新しい発見
エラー出るのは.mjsだけだ!db.tsの方はimport.metaしてもeslintエラー出ない!!
GPTに聞いた、なるほどー、そういうことなのか
import.meta.env は Node.js では利用できないためです。eslint.config.mjs は Node.js 環境で実行されるため、import.meta.env は未定義になり、エラーが発生します。
Vite の import.meta.env はビルド時に環境変数を静的に埋め込む仕組みです。一方で、Node.js の process.env は実行時に環境変数を動的に取得します。ESLint では後者の仕組みを使う必要があります。
.mjsはnode.js環境なのか。んでimport.metaはnode.jsでは利用できないのか。
だがどっちにしろ読み込めるようにしないとまずいので、何かしらせねばならないのは変わらない

これ試してみたけどundefinedで使えなかった...

dotenvインストールしていろいろやったら、読み込み自体はちゃんとできることを確認。一応やり方をまとめておく。だがeslintからエラーが出るのは変わらん...。
- まずインストール
npm install dotenv --save-dev
-
ルートディレクトリに.env.development作成
-
urlは共通化したいのでconfig.tsをルートディレクトリに作成
import dotenv from "dotenv";
dotenv.config({ path: ".env.development" });
const databaseUrl = process.env.VITE_DATABASE_URL;
if (!databaseUrl) {
throw new Error("VITE_DATABASE_URL is not defined in .env.development");
}
export const config = {
databaseUrl,
};
- あとは普通にそれを読み込むだけ
import safeql from "@ts-safeql/eslint-plugin/config";
import tseslint from "typescript-eslint";
import { config } from './config'
export default tseslint.config(
...tseslint.configs.recommended,
safeql.configs.connections({
databaseUrl: config.databaseUrl,
targets: [{ tag: "sql", transform: "{type}[]" }],
})
);
import postgres from "postgres";
import { config } from "../config";
export const main = async () => {
const sql = postgres(config.databaseUrl);
const result = await sql`SELECT id, name FROM users WHERE id = ${1}`;
console.log("resultだよ", result);
};
結局eslintで以下が出る
Cannot find module 'C:\Users\〇〇\config' imported from C:\Users\〇〇\eslint.config.mjs
jsしか読み込めないのだがconfig.tsはtsだから見つからない認定されてる、というのが理由ぽい
かといってconfig.jsにファイル名変更してもdb.tsから.jsをインポートできない。モジュールが見つからないと出てくる。
多分その理由はtsconfig.jsonで"allowJs": true,
がされてないからっぽいが、それを追加すると謎のエラーが出る
Cannot write file 'c:/Users/〇〇/eslint.config.mjs' because it would overwrite input file.ts
構成ファイル系の知識が皆無なのでやばい。詰まった。

Cursorってやつ始めて見るか。エディタレベルで読み込んでくれるなら助け舟を出してくれるのではなかろうか
ということでこれは別スクラップで

戻ってきた。今のプロジェクトをCursorで開いて、Codebase Answersの機能を使ってみよう
ほぼVSCodeだから乗り換えの面倒がまったく無さそうなのが良い

ブログ記事お読みいただきありがとうございます!!検証までしていただいてすごい・・・!
以前執筆したブログ記事なのですが、かなり勢いで書いたせいで結構ぐちゃぐちゃになっていまして、つい先程修正いたしました!お手数おかけします!!
ほぼ同じ内容でスライドも存在しているので、ぜひ!!

ご丁寧にありがとうございます!!わぉ、すごい助かります...!
ぜひ拝見させていただきます!!

んー、だめだCursorに聞いてみたけど補完効くようにできなかった
仕方ない、ハードコーディングで進めていくか...
vscode-eslintとsafeqlと.envの狭間で苦しんで敗北
git pushしないように気をつけねば

まぁクエリ書くのはdb.tsだけなのでeslint.config.mjsは変更しないで良いか
db.tsだけハードコーディングに変更
...と思いきやeslint.config.mjsもハードコーディングしないとdb.tsの補完が効かなくなった、、連鎖するのか、、
しゃあない、どっちもハードコーディングしよう

これエラー出ないぞ...
vscode再起動したら出るようになった。不安定なの嫌だな

へー型キャストもできるのか
では、::uuidを指定してキャストしてみます。

postgresqlのサーバーをコマンド実行で立てとかないと、vscodeで編集するたびにvscodeのeslintエラー通知が右下に出てくるっぽい
だからvscode内のターミナルじゃなくてpcから直接ターミナル開いてサーバー立てといた方が良いな
そうしないと、vscode再起動とかしたら落ちちゃうので
いや関係ないっぽい...??
なんなんだこれめっちゃ邪魔だ、しかもturn offしても出てくるという謎
このeslint serverというのはvscodeのeslint拡張機能だなぁ。オフにはできないから困るぞ。。。。

気付いたこと
-
name=
の後とかの引用符はダブルクォーテーションだと機能しない!シングルクォーテーションにしないといけない- これがvscode-eslint, safeql, postgresqlのどのルールなのかはわからん
- 毎回
bun dev
で開発サーバー立てないでも、普通にnodeがts実行できるようになったのでnode db.ts
でファイル単体実行してもOK

あーselectの時だけeslintエラーが出て型を提案してくれるっぽい
insert/update/deleteでは型書かなくてもエラーでない

おーほんとだ、すごい
LEFT JOIN の場合は、post_id と title に null が許容されることが提案されました。

nullが含まれるべきでないものにもnullが含まれることがあるということかな
注意点
まだ少し不安定な部分があり、型に null が含まれて出力されることがあります。
この場合、VSCode を再起動することで正確な型が生成されるようになる場合が多いです。
テーブルのスキーマ定義を変更した後は、VSCode を再起動することをおすすめします。

コロケーションという概念、なるほど~
コード内部にクエリを記述でき、コロケーションを保つことができる
まとめすぎても見づらくなったりしそうだから、どういうものをどの程度までまとめるべきかとか考える必要があるんだろうなー

コード生成アプローチも知らないので何とも言えないが、とにかく楽になってるらしい
コード生成・import の手間がかからない
コード生成アプローチを利用する場合、コマンドの実行の手間がかかります。また、生成されたコードを import する必要があります。
SafeQL ではコード内に SQL を記述するため、コード生成の手間がかかりません。

本番環境に入りこまない
SafeQL は、単なる eslint ルールとして実装されています。
とりあえずここ重要だな
SafeQL は、単なる eslint ルールとして実装されています。
単なるeslintルールだとして覚えるか
でもeslintという構文チェッカーの役割を大いに超えてORMみたいなこともできているという点で「単なるeslintルール」と言うのは違和感もある
「safeqlというsqlの記述方法を使うとそれをeslintもサポートしてくれる」
「SafeQLにeslintルールが実装されています」
みたいな方が自分の印象に近いなぁ

なるほど
Postgres.js を利用する際にジェネリクス内に型を提案するだけなので、本番環境に余計なライブラリが入りこまないというメリットがあります。
本番環境にライブラリが入りこまないというのは、セキュリティやパフォーマンスの観点からも望ましいです。
devDependenciesにしか入らないから本番ではパフォーマンス上がる的な感じかな(セキュリティも良いのか、へぇ)
参考に、Postgres.js を利用した場合の依存関係は以下の通りです。
...
"devDependencies": {
"@ts-safeql/eslint-plugin": "^3.4.1",
"libpg-query": "15.2.0-rc.deparse.3",
"tsx": "^4.19.0",
"typescript-eslint": "^8.3.0"
},
"dependencies": {
"postgres": "^3.4.4",
"typescript": "^5.5.4"
}
...

ほー
なお、特性としてマイグレーション周りは別途のライブラリを利用する形となります。
筆者の環境では dbmate を利用しています。

おー純粋なsqlを理解するだけで書けるのは良さげ
あと発行クエリの確認ってやつも普通は必要なのか
SQL をそのまま記述でき、見通しが良い
sql タグとして記述された SQL は、そのまま実行されます。
したがって、ORM などのライブラリで提供されるメソッドを覚える必要がありません。
また、ORM を利用する際は発行されるクエリを確認するための手間がかかりますが、SafeQL ではそのような手間がかかりません。実行計画の確認なども容易です。
これはコード生成アプローチの場合と同様のメリットであると考えられます。
だがコード生成アプローチとやらも同じメリットはあるらしい

読み終わったので元スクラップに戻る
しかし環境変数を読み込むとeslint(safeql)が機能しないという個人的な問題は致命的だ...