bryanmanio_obsidian-lexophile/src/sqlite.ts
Bryan Maniotakis cc355c5fbd Replace Dictionary API with a local SQLite dictionary
The plugin used to call dictionaryapi.dev for every lookup, which was
slow, rate-limited, and broke offline. It now ships a local SQLite
file (~23MB, ~167K English entries from Wiktionary via the
MattDodsonEnglish/english-dictionary project) and queries it locally
via sql.js.

Plumbing:
- DictionaryStore loads the SQLite from
  <vault>/.obsidian/plugins/lexophile/dictionary.sqlite, downloads
  on demand from a configurable URL (default: a release asset on this
  repo), and exposes a synchronous lookup() that returns a WordEntry
  or null.
- lookupWord is now an async wrapper around store.lookup that throws
  WordNotFoundError on null. DictionaryNotReadyError surfaces the
  pre-download state so modals can fail fast with a helpful message.
- sql.js initialization moved to its own module (src/sqlite.ts) and
  reused by both the Kobo import and the new dictionary store.
- AddWordModal / MassImportModal / KoboImportModal each take the
  store via constructor and guard against the not-ready state before
  starting work.
- Settings tab gains a "Local dictionary" section with a status pill
  (Ready / Downloaded-not-loaded / Not downloaded), an entry count and
  size summary, a Download/Re-download button, and a configurable URL.
- Removed the inter-request rate limit (was 350ms for API throttling) —
  local lookups are sub-millisecond, so a 0ms yield is enough to let
  the progress line repaint.

Bundled dictionary data is hosted as a release asset on
bryanmanio/obsidian-lexophile (tag: dictionary-v1). The plugin does
not call dictionaryapi.dev at any point during normal use. The Chrome
extension still uses the API for its own lookups — out of scope here.

Build artifacts:
- scripts/build-dictionary.py converts the upstream parquet (167,585
  entries) to the compact SQLite schema we ship; documented for
  reproducibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 02:44:09 -06:00

17 lines
768 B
TypeScript

import initSqlJs, { type SqlJsStatic } from 'sql.js';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore — esbuild's binary loader resolves this to a Uint8Array
import wasmBinary from 'sql.js/dist/sql-wasm.wasm';
let sqlJsPromise: Promise<SqlJsStatic> | null = null;
// Lazy-initialize sql.js so the WASM compile only happens when something
// actually needs the SQLite runtime (Kobo import, local dictionary lookup).
export function getSqlJs(): Promise<SqlJsStatic> {
if (!sqlJsPromise) {
// esbuild's binary loader hands us a Uint8Array; sql.js types want
// ArrayBuffer but accept either at runtime. Cast through unknown.
sqlJsPromise = initSqlJs({ wasmBinary: wasmBinary as unknown as ArrayBuffer });
}
return sqlJsPromise;
}