ゆっきーの砂場

React(Next.js)でDexieを使っているときのMissingAPIErrorの解決メモ

2023年2月16日 投稿

初めに

Next.js 13でDexieを用いてindexedDBへのアクセスを試みていたところ、MissingAPIErrorに遭遇しました。
公式ドキュメントを参照したところ、原因はNext.js起動時にNode側でindexedDBにアクセスしようとして失敗しているためとのこと。公式ドキュメントには解決策として幾つかのライブラリを導入する方法が挙げられていますが、どれもモック用のライブラリっぽいくて、プロダクションでは採用したくありません。。。
そこで、他の解決策を探すことにしました。

環境

Next.js 13.1.6
dexie 3.2.3
dexie-react-hooks 1.1.1

chrome 108.0.5359.124

エラーが起きたコード


/** @format */


import { IMemoryMarker } from '@/types/indexedDB'
import { PromiseExtended } from 'dexie'
import { IndexedDB } from '../infrastructure/indexedDB'


export class LocalMemoryMarkerRepository {
  private indexedDB
  private static localMemoryMarkerRepository = new LocalMemoryMarkerRepository()
  private constructor() {
    this.indexedDB = IndexedDB.getInstance()
  }
  static getRepository() {
    return this.localMemoryMarkerRepository
  }
  save({ lat, lng, created_at, updated_at }: IMemoryMarker): PromiseExtended<number> {
    return await this.indexedDB.memory_marker.add({ lat, lng, created_at, updated_at })
  }
  findById(id: number): PromiseExtended<IMemoryMarker | undefined> {
    return await this.indexedDB.memory_marker.get({ id: id })
  }
   findAll(): Promise<IMemoryMarker[]> {
    return await this.indexedDB.memory_marker.toArray()
  }
  update({ id, lat, lng, updated_at }: IMemoryMarker): PromiseExtended<number> {
    if (!id) throw new Error('idがundefiedです')
    const updateParam = {
      lat: lat,
      lng: lng,
      updated_at: updated_at,
    }
    return await this.indexedDB.memory_marker.update(id, updateParam)
  }
  delete(id: number) {
    this.indexedDB.memory_marker.delete(id)
  }
}



上記のコードは別クラスでシングルトンしてあるDexieインスタンス(変数名: indexedDB)を呼びだし、DexieインスタンスからDBの更新を行うリポジトリです。
コンパイル後のサーバー立ち上げ時にこちらのコードでエラーが発生していました。

解決策


  • Dexieインスタンスを参照する際、hasFailed()メソッドを呼び出してindexedDBの存在を確かめる。

ドキュメントはこちら

動作したコード


/** @format */


import { IMemoryMarker } from '@/types/indexedDB'
import { IndexedDB } from '../infrastructure/indexedDB'


export class LocalMemoryMarkerRepository {
  private indexedDB
  private static localMemoryMarkerRepository = new LocalMemoryMarkerRepository()
  private constructor() {
    this.indexedDB = IndexedDB.getInstance()
  }
  static getRepository() {
    return this.localMemoryMarkerRepository
  }
  async save({ lat, lng, created_at, updated_at }: IMemoryMarker): Promise<number> {
    if (this.indexedDB.hasFailed()) throw Error('データベースはOpenしていないようです。')
    return await this.indexedDB.memory_marker.add({ lat, lng, created_at, updated_at })
  }
  async findById(id: number): Promise<IMemoryMarker | undefined> {
    if (this.indexedDB.hasFailed()) return undefined
    return await this.indexedDB.memory_marker.get({ id: id })
  }
  async findAll(): Promise<IMemoryMarker[]> {
    if (this.indexedDB.hasFailed()) return []
    return await this.indexedDB.memory_marker.toArray()
  }
  async update({ id, lat, lng, updated_at }: IMemoryMarker): Promise<number> {
    if (!id) throw new Error('idがundefiedです')
    if (this.indexedDB.hasFailed()) return 0
    const updateParam = {
      lat: lat,
      lng: lng,
      updated_at: updated_at,
    }
    return await this.indexedDB.memory_marker.update(id, updateParam)
  }
  async delete(id: number) {
    if (this.indexedDB.hasFailed()) return
    this.indexedDB.memory_marker.delete(id)
  }
}



それぞれの関数の戻り値の型は、元々のPromiseExtended型の代わりにその親クラスであるPromiseに変更しました。そして、それぞれの関数で真っ先にhasFailed()メソッドを呼び出すことで、indexedDBの存在を確かめています。