Google Apps ScriptでPrisma Schemaを使う
は?と思うかもしれません。順を追って説明します。
はじめに
まずGoogle Workspaceにはスプレッドシートがありますよね。そしてこれはGoogle Apps Script(GAS)で操作できます。そしてスプレッドシートを一種のDBとして扱い、GASで管理したい、そういう時あると思います。
ただ、GASでスプレッドシートを操作する時、セル単位での操作が求められます。(何列の何行目のセルから何列何行分選択して置き換えるみたいなことをしないといけない)
これはDBとして扱うことを考えると非常に面倒です。SQLのような操作ができれば楽だし、なんならORMみたいに操作できれば可読性が高く分かりやすいコードが書けます。
ということでそれを可能にする、Prisma Schemaを使い、Prismaとほぼ同じテンションでスプレッドシートを操作できるライブラリ「GASsma」を自作しました。
使い方
前提としてGASのWebエディタではなく、clasp+esbuild(webpackでも可)を使い、ローカルで開発することが求められます。
GASのローカル開発方法自体については本題から逸れるので、下記記事を参考に。
https://qiita.com/mistylady/items/451c75186412d12fa203
とりあえず、GASsma導入前は以下のディレクトリ構成になっているとします。
project/
├── node_modules/
├── dist/ ← esbuildのビルド先
│ └── appsscript.json
├── src/ ← esbuildのビルド元
│ └── index.ts
├── .clasp.json
├── esbuild.mjs
├── package-lock.json
├── package.json
└── tsconfig.json
ここからnpm i gassma をして、インストールしたのちnpx gassma init をします。すると
project/
├── node_modules/
├── dist/
│ └── appsscript.json
├── src/
│ └── index.ts
├── gassma/ ← NEW
│ └── schema.prisma
├── .clasp.json
├── esbuild.mjs
├── package-lock.json
├── package.json
├── tsconfig.json
└── gassma.config.ts ← NEW
configファイルとschemaファイルが生成されます。
その後、appsscript.jsonに以下の設定(librariesに追加設定)を書きます。
{
"timeZone": "Asia/Tokyo",
"dependencies": {
"libraries": [
{
"userSymbol": "Gassma",
"version": "7",
"libraryId": "1ZVuWMUYs4hVKDCcP3nVw74AY48VqLm50wRceKIQLFKL0wf4Hyou-FIBH"
}
]
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8"
}
これで導入は完了です。後は実際にコードを書いていくのですが、例として、AuthorシートとBookシートの1対多のリレーションがあるDBを作りたいとします。
Authorシート

Bookシート

先ほどのコマンドで生成されたschemaファイルに
generator client {
provider = "prisma-client-js"
output = "./src/generated/gassma"
}
model Author {
id Int @id @default(autoincrement())
name String @default("NO_NAME")
books Book[]
}
model Book {
id Int @id @default(autoincrement())
name String
author_id Int
published Boolean @default(false)
author Author @relation(fields: [author_id], references: [id], onDelete: Cascade)
}
と書いておきます。(prisma同様npx gassma formatでフォーマットできます)
※ 注意としてユニーク関連機能は用意していないので@uniqueは利用できません。
その後npx gassma generateとすると
project/
├── node_modules/
├── dist/
│ └── appsscript.json
├── src/
│ ├── generated/
│ │ └── gassma/
│ │ ├── schemaClient.d.ts ← NEW
│ │ └── schemaClient.js ← NEW
│ └── index.ts
├── gassma/
│ └── schema.prisma
├── .clasp.json
├── esbuild.mjs
├── package-lock.json
├── package.json
├── tsconfig.json
└── gassma.config.ts
先ほど作成したPrisma Schemaを元にクライアントファイル(schemaClient.js)と、型ファイル(schemaClient.d.ts)が生成されます。
あとは本家Prismaとほぼ同じ書き方で
// クライアントファイルをimport
import { GassmaClient } from "./generated/gassma/schemaClient";
const gassma = new GassmaClient();
/**
* 一冊でも本を出版したことがある著者一覧を取得
*/
const getLeastOncePublishedAuthor = () => {
const result = gassma.Author.findMany({
where: {
books: {
some: { published: true },
},
},
select: {
name: true,
},
});
console.log(result);
};
interface Global {
getLeastOncePublishedAuthor: typeof getLeastOncePublishedAuthor;
}
declare const global: Global;
global.getLeastOncePublishedAuthor = getLeastOncePublishedAuthor;
とすればOKです。(本家Prismaと違い、非同期ではないことに注意が必要です。また、ユニーク関連機能は用意していないので、findUniqueに当たる機能はありません。代わりにfindFirstは実装しているので、そちらを利用することを推奨しています。)
先ほど生成した型ファイルがあるおかげで型が効くようになり、クライアントファイルがあるおかげでコードレベルでリレーションや諸々の設定が効くようになります。
あとはbuildののち、clasp pushでGAS上にアップロードし、実行してみると

と取得できていることがわかります。
ほかにもできること
Prismaに存在する機能のうち、GASで実現可能なものはほぼ実装済みです。
具体的には
- CRUD (create / createMany / createManyAndReturn / findMany / findFirst / findFirstOrThrow / update / updateMany / updateManyAndReturn / upsert / delete / deleteMany)
- 統計 (aggregate / groupBy / count)
- 外部キー制約(onDelete / onUpdate)
- スキーマファイルにおけるマッピング(@map / @@map)や無視(@ignore / @@ignore)
- etc...
を用意しています。
さいごに
詳しい仕様は公式リファレンスを用意しているので、そちらをご確認ください。
https://akahoshi1421.github.io/gassma-reference/
また、OSSで開発しているので、リポジトリも公開しています。
本体
https://github.com/akahoshi1421/gassma
CLI
https://github.com/akahoshi1421/gassma-cli
簡潔で分かりやすいコードが書けるのでGASの煩雑なコードに疲れた人におすすめです。
ぜひご活用ください!
また、1人で比較的リッチなOSSを開発していることもあり、細かいバグを見逃している可能性もあるので、これバグかな...?と思ったらissue送っていただけるとありがたいです...!