メインコンテンツまでスキップ

$extends(query)

$extendsquery コンポーネントは、各操作の実行に割り込むクエリフックを登録するための機能です。Prisma のクライアント拡張($extendsquery)に相当します。フックの中で args を書き換える、結果を加工する、実際の操作を実行せずに短絡する、といった制御ができます。

算出フィールドを追加したい場合は$extends(result)を参照してください。

基本的な使い方

gassma.$extends({ query: {...} }) を呼ぶと、フックを適用した新しいクライアントが返ります。元の gassma は変更されません。

const extended = gassma.$extends({
query: {
Users: {
findMany({ model, operation, args, query }) {
// args を加工してから実際のクエリを実行できる
return query(args);
},
},
},
});

const users = extended.Users.findMany();

フックの形

フックは { model, operation, args, query } を受け取る関数です。

プロパティ内容
model操作対象のモデル名(シートのコード名)
operation操作名(findMany など)
argsその操作に渡された引数
query実際の操作(または次のフック)を実行する関数

query(args) を呼ぶと、渡した args で実際の操作が実行され、その結果が返ります。query を呼ぶ前に args を書き換える、query の戻り値を加工する、query を呼ばずに独自の値を返して短絡する、といった制御が可能です。

const extended = gassma.$extends({
query: {
Users: {
findMany({ args, query }) {
const result = query(args); // 実際の findMany を実行
return result;
},
},
},
});

対象となる操作

query フックは次の 15 操作に対して登録できます。

分類操作
取得findFirst / findFirstOrThrow / findMany
作成create / createMany / createManyAndReturn
更新update / updateMany / updateManyAndReturn
upsertupsert
削除delete / deleteMany
集計count / aggregate / groupBy

構造

query は「モデル名 → 操作名 → フック」の形で指定します。特定のモデル・操作に加えて、モデル内の全操作をまとめて対象にする $allOperations と、全モデルを対象にする $allModels が使えます。

const extended = gassma.$extends({
query: {
// 特定モデルの特定操作
Users: {
findMany({ args, query }) {
return query(args);
},
// モデル内の全操作
$allOperations({ operation, args, query }) {
return query(args);
},
},
// 全モデル
$allModels: {
// 全モデルの特定操作
findMany({ model, args, query }) {
return query(args);
},
// 全モデルの全操作
$allOperations({ model, operation, args, query }) {
return query(args);
},
},
},
});

フックの合成順

1 つの操作に複数のフックがマッチした場合、それらは上書きされずすべて連鎖します。連鎖の順序は次のとおりです。

  • 先に適用した拡張ほど外側になります(後に適用した拡張ほど、実際の操作に近い内側になります)。
  • 1 つの拡張の中では、具体的なフックほど外側になります。優先順位は モデル.操作 > モデル.$allOperations > $allModels.操作 > $allModels.$allOperations です。

外側のフックが query(args) を呼ぶと次に内側のフックが実行され、最も内側のフックが query(args) を呼ぶと実際の操作が実行されます。

const extended = gassma
.$extends({
query: {
Users: {
findMany({ args, query }) {
Logger.log("A: 外側");
return query(args);
},
},
},
})
.$extends({
query: {
Users: {
findMany({ args, query }) {
Logger.log("B: 内側");
return query(args);
},
},
},
});

extended.Users.findMany();
// ログ出力: "A: 外側" → "B: 内側" → 実際の findMany

同期的に実行される

GAS 上で動作するため、query フックは同期的に実行されます。query(args) は Promise ではなく結果そのものを同期的に返します。async / await は不要です。

const extended = gassma.$extends({
query: {
Users: {
findMany({ args, query }) {
const result = query(args); // 同期的に結果が返る
return result;
},
},
},
});

args は参照で渡される

args はフックに参照で渡されます(deep clone されません)。args を破壊的に書き換えると、呼び出し元が持つオブジェクトにも影響します。args を変更したい場合は、新しいオブジェクトを作って query に渡すことを推奨します。

const extended = gassma.$extends({
query: {
Users: {
findMany({ args, query }) {
// NG: 渡された args を直接書き換える
// args.where = { ...args.where, deleted: false };

// OK: 新しいオブジェクトを作って渡す
const nextArgs = Object.assign({}, args, {
where: Object.assign({}, args.where, { deleted: false }),
});
return query(nextArgs);
},
},
},
});

チェーンできる

$extends は連続して呼び出せます。それぞれの呼び出しが新しいクライアントを返します。

const extended = gassma.$extends(extensionA).$extends(extensionB);

include の内部リレーションはフックを通らない

query フックが対象にするのは、最上位で呼び出した操作だけです。include によって内部で解決される関連レコードの取得は、フックを通りません。

const extended = gassma.$extends({
query: {
Posts: {
findMany({ args, query }) {
// Users.findMany に対する include: { posts: true } では
// この Posts.findMany フックは呼ばれない
return query(args);
},
},
},
});

実用例

ソフトデリート

findManywhere に既定の条件を注入して、削除済みレコードを既定で除外できます。

const extended = gassma.$extends({
query: {
Users: {
findMany({ args, query }) {
const nextArgs = Object.assign({}, args, {
where: Object.assign({ deleted: false }, args.where),
});
return query(nextArgs);
},
},
},
});

監査ログ

$allModels$allOperations で、全モデル・全操作の前後にログを記録できます。

const extended = gassma.$extends({
query: {
$allModels: {
$allOperations({ model, operation, args, query }) {
Logger.log(`${model}.${operation} 開始`);
const result = query(args);
Logger.log(`${model}.${operation} 完了`);
return result;
},
},
},
});