top of page

webMethod の第2引数を関数リテラルにしなかっただけで、バックエンドの API が全部消えた

管理者向けのダッシュボードで対象日を選んでも、一覧が出ない。画面のステータス欄に出たのは一行だけだった。

(0 , t.getSummary) is not a function

この一行で最初に疑ったのは、サイト上のファイルが古いことだった。Wix エディタへ手で貼り付けて運用しているので、ローカルの最新版が届いていないことは実際に何度もある。貼り直して、公開して、もう一度押した。同じ一行が出た。二度目も同じだった。

このエラーが何を指しているか

t は、フロントエンドが受け取る backend/summaryOps.web.js のクライアントプロキシである。Velo は .web.js をフロントエンドから import できるように、呼び出しを中継するオブジェクトをビルド時に作る。それが t になっている。

読み方は素直で、t 自体は存在するのに getSummary だけが無い。つまりモジュールは解決されていて、その中身が空だということになる。実際、このファイルは5つの関数を export していて、5つとも同じように消えていた。一部ではなく全滅である。

ページ側は動いていた。画面の再読み込み処理が走ってステータス欄に文字が出ているのだから、ダッシュボードのページコードには罪がない。壊れているのはサイト上のバックエンドだ、というところまでは早かった。

ローカルに原因が無いことを先に確定させる

貼り直しても直らないのだから、次は「ローカルのコードが本当に正しいのか」を疑う番になる。ここは機械的に潰した。

検査

結果

関係する6ファイルの named import を、実際の export と突き合わせる

欠けている名前は0件

同6ファイルの ESM 構文チェック(.mjs にコピーして node --check)

すべて ok

backend/jobs.config の関数の場所と名前

実ファイルの export と一致

全部通った。ローカルが正しくて、貼り直しても直らない。ここで手が止まった。

動いている .web.js と形を突き合わせる

同じサイトには、実機で動いている .web.js が4つあった。会員プロフィールを扱うもの、マスタを読むもの、注文をキャンセルするもの、テスト用の注文を作るものである。どれもフロントエンドから普通に呼べている。壊れているファイルだけが持っている性質があるはずだと考えて、5つを並べた。

その .web.js の役割

export 数

webMethod() の第2引数

実機

会員プロフィールの読み書き

5

async (…) => {…}

動いている

マスタの取得

5

async (…) => {…}

動いている

注文のキャンセル

3

async (…) => {…}

動いている

テスト用の注文の作成

4

async (…) => {…}

動いている

集計(症状が出ていたもの)

5

guard(…) の戻り値

動いている14個の export は、例外なく関数リテラルを直接渡していた。壊れているファイルだけが、ヘルパ関数の戻り値を渡していた。

export const getSummary = webMethod(
  Permissions.Admin,
  guard('getSummary', runGetSummary, { … })   // ヘルパの戻り値
);

guard() は非同期関数を返す。構文として正しく、実行時にも第2引数には関数が入る。JavaScript としては何も間違っていない。

ビルド時に読まれる前提で書く

それでも動かない理由は、この判定が実行時ではなくビルド時に行われることにある。Velo は .web.js を静的解析してクライアント側のプロキシを生成する。第2引数が関数リテラルでなければ webMethod として認識されず、プロキシが空になる。モジュールそのものは解決されるので、フロントエンドから見ると「オブジェクトはあるのに関数が1つも生えていない」という形になる。

症状の出方には、そのまま4つの特徴が現れていた。

  • 画面に出るのは (0 , t.getSummary) is not a function だけで、原因を示す情報が何も無い

  • そのファイルの export が全滅する。一部だけ欠けることはない

  • エディタにビルドエラーが出ない。ファイルは構文的に正しいので、止まる理由が無い

  • ページコードは正常に動く。だから「ページは無罪」という正しい切り分けが、そのまま行き止まりになる

この形にしたのは実装した日からで、つまりこの画面は一度も動いたことがなかった。「前は動いていたのに」という前提が最初から成立していなかったわけで、サイト上のファイルが古いという仮説に二往復を使ったのは、そこを確かめていなかったからだった。

共通処理は捨てずに、リテラルの中から呼ぶ

guard() でやっていたことには意味がある。どの関数で落ちたかを戻り値に載せ、依存が届いていなければ名前を挙げて返す、という診断の共通処理である。これを捨てる必要はない。呼ぶ位置を関数リテラルの内側へ移すだけでよい。

async function runGuarded(label, getFn, fallback, input) { … }   // 素のヘルパ

export const getSummary = webMethod(
  Permissions.Admin,
  async (input) => runGuarded('getSummary', () => runGetSummary, FALLBACK.summary, input)
);

この形なら静的解析は第2引数を関数リテラルとして読み、プロキシに getSummary が生える。共通処理はその内側で普通に呼ばれる。貼り直して公開したところ、症状は is not a function から別のエラーへ変わった。関数が無いエラーから、関数が実行されて返らないエラーへの変化なので、ここは解消したと判断できる。

同じ症状を次に見たとき

「モジュールは届くのに中身が0個」という形は、ローカルの静的検査では出てこない。import と export の突き合わせも、構文チェックも、この障害には反応しない。判定しているのがビルド時の Velo だからで、手元でいくら正しさを確認しても、確認している対象が違う。

そのときいちばん速いのは、同じサイトで動いている .web.js と形を突き合わせることだった。動いている実例が同居しているのは、Velo の案件ではむしろ普通のことだと思う。差分は1か所しかなかった。

.web.js の webMethod() は、第2引数を必ず関数リテラルで書く。共通処理を挟みたくなったら、ラッパで包むのではなく、リテラルの中で呼ぶ。


Velo

実装

備忘録

コラム

その他

カテゴリー

bottom of page