タイトルはさておき、LovefieldというSQLライクなAPIが使えるライブラリがあって、個人的に便利だったので。
Lovefieldとは
っていうライブラリ。
実績としてはGmailで使われてたらしい。(現在もそうなのかは不明)
Is Lovefield production quality?
Yes. As of May 2016, Inbox by GMail heavily relies on Lovefield to perform complex client-side structural data queries.
https://github.com/google/lovefield/blob/master/docs/FAQ.md#is-lovefield-production-quality
(コードが書かれたのは主に2015年頃らしいなので、今さらも今さらなネタではある。けど、そもそも日本でそんなに話題になってなかった気がする?)
Public archiveだが
リポジトリがアーカイブ状態になってるやん!って思いますよね?私は思いました。
これも中の人によると、
Lovefield is under long-term maintenance but there will be no new features (i.e. feature freeze). G-Mail is using it and as long as G-Mail is still using it we'll keep supporting it. There are not many updates because Lovefield has very few bugs (G-Mail only managed to find 7 bugs during their whole usage, and they are all fixed of course).
https://github.com/google/lovefield/issues/266#issuecomment-678883485
という感じで、機能追加の予定がないからアーカイブってだけで、バグってるとかメンテされてないとかそういうわけではないとのこと。
(このコメントは2020年なので、しれっとGmailは2020年でもLovefieldを使ってた情報が更新されてる)
Lovefield-TS
さっきのコメントにもあるけど、GoogleとしてのLovefieldの開発は終わってるけど、中の人が個人的にTypeScriptにポートしたリライト版がある。
サポートブラウザがよりモダンに限定されてたりNode.jsでも動くようになってたり、本家とは微妙にAPIが変わったりしてるらしいけど、今から使うならこっちでよさそう。
というわけで、`lovefield-ts`をnpmからいつもどおりインストールして使えばよい。
モチベーション
そもそもなぜブラウザでSQL?ってところに関しては、まあそうしたい理由があったからってだけなので割愛するとして。
最近ならSQLiteのWASM版を動かすっていう選択肢もあるし、Lovefieldの他にも似たようなライブラリはある。そんな中での差別化ポイントとしては、やはり軽いことと依存がないってところ。
SQLiteのWASM版はbr圧縮でも最低300KBくらいかかるし、他のライブラリたちもそれなりに重い。インメモリでだけ使いたいのに、そう設定してもIndexedDB(FirefoxのPrivateモードで使えない)が必要だったりと、いまいちハマらなかった。その点Lovefield-TSだと依存なしで最大50KB(Tree-shakingされたらもっと小さい)で済む。
基本的な使い方
Lovefield本家はドキュメントがとにかくわかりにくい(個人の感想です)しコードのシンタックスも古いので、Lovefield-TSのリポジトリの`docs`配下を参照するのがもっともよいかと。
https://github.com/arthurhsu/lovefield-ts/blob/master/docs/index.md
いちおう最低限のコードも載せておくと。
import { schema, Type, DataStoreType } from "lovefield-ts/dist/es6/lf" // 1. Create tables const builder = schema.create("my-db", 1); builder .createTable("items") .addColumn("id", Type.STRING) .addColumn("title", Type.STRING) .addColumn("count", Type.NUMBER) .addColumn("url", Type.STRING) .addNullable(["url"]) .addPrimaryKey(["id"]); // 2. Connect to instance(default is `INDEXED_DB`) const db = await builder.connect({ storeType: DataStoreType.MEMORY }); // 3. Insert data const items = db.getSchema().table("items"); const itemsRows = []; for (const data of DATA) { itemsRows.push(items.createRow(data)); } await db.insert().into(items).values(itemsRows).exec(); // 4. Query const rows = await db .select( items.col("title"), items.col("count") ) .from(items) .where(items.col("count").gt(4)) .exec() .then((rows) => /** @type {{ title: string; count: number }[]} */ (rows));
という感じ。直感的でよい。
気になったところ
importまわり
├── LICENSE ├── README.md ├── dist │ ├── es5 │ │ ├── lf.d.ts │ │ ├── lf.d.ts.map │ │ ├── lf.js │ │ └── lf.js.map │ ├── es6 │ │ ├── lf.d.ts │ │ ├── lf.d.ts.map │ │ ├── lf.js │ │ └── lf.js.map │ └── lf.ts ├── index.js └── package.json
npmへはこういうファイル構成で配布されてて、`from "lovefield-ts"`で`import`すると全部いりの`dist/es5/lf`が降ってくるようになってる。
なのでTree-shakingのためには、`from "lovefield-ts/dist/es6/lf"`ってやるか、`"lovefield-ts/dist/lf"`のTSを直で参照してこっちでコンパイルするかになる。
このへんの挙動はバンドラの設定とかでも微妙に変わるはずで、なんしか試行錯誤が少し必要になってなんだかな・・ってちょっとなった。