@aiquants/duckdb-helper 1.6.3 → 1.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useDuckDB.ts","../src/services/duckdb.ts","../src/utils/simple-logger.ts","../src/utils/duckdb-helpers.ts"],"sourcesContent":["/**\n * DuckDB React Hook\n *\n * React hook for using DuckDB functionality in components.\n * コンポーネントで DuckDB 機能を使用するための React フック。\n */\n\nimport { type DependencyList, useCallback, useEffect, useRef, useState } from \"react\"\nimport { DuckDBService } from \"../services/duckdb\"\nimport { Logger } from \"../utils/simple-logger\"\n\n/**\n * Derive a constant-size version number that increments whenever the given\n * dependency list changes (by React's own Object.is comparison).\n * 依存配列の変化(React と同じ Object.is 比較)で増える固定サイズのバージョン番号を返す。\n *\n * これにより可変長の `dependencies` を effect の依存配列に直接スプレッドせずに済み、\n * 配列サイズがレンダー間で変わっても React の「依存配列サイズ変更」エラーを起こさない。\n */\nconst useDependencyVersion = (dependencies: DependencyList): number => {\n const ref = useRef<{ deps: DependencyList; version: number }>({ deps: dependencies, version: 0 })\n const previous = ref.current.deps\n const changed = previous.length !== dependencies.length || dependencies.some((dep, index) => !Object.is(dep, previous[index]))\n if (changed) {\n ref.current = { deps: dependencies, version: ref.current.version + 1 }\n }\n return ref.current.version\n}\n\nexport interface UseDuckDBResult {\n status: \"not-initialized\" | \"initializing\" | \"ready\" | \"error\"\n error: string | null\n executeQuery: (sql: string) => Promise<unknown>\n executeQueries: (queries: string[]) => Promise<unknown[]>\n createTableFromData: <T extends Record<string, unknown>>(\n tableName: string,\n data: T[],\n options?: {\n dropIfExists?: boolean\n primaryKey?: string\n verbose?: boolean\n },\n ) => Promise<void>\n getTableInfo: (tableName: string) => Promise<unknown>\n listTables: () => Promise<unknown>\n isReady: boolean\n initialize: () => Promise<void>\n}\n\n/**\n * DuckDB functionality hook.\n * DuckDB 機能を提供するフック。\n *\n * @param autoInitialize - 自動初期化を行うかどうか\n * @returns DuckDB の状態と操作関数\n */\nexport const useDuckDB = (autoInitialize = true): UseDuckDBResult => {\n const [status, setStatus] = useState<\"not-initialized\" | \"initializing\" | \"ready\" | \"error\">(\"not-initialized\")\n const [error, setError] = useState<string | null>(null)\n const [duckDBService] = useState(() => DuckDBService.getInstance())\n\n const updateStatus = useCallback(() => {\n const serviceStatus = duckDBService.getStatus()\n Logger.info(\"🔄 DuckDB status updated:\", serviceStatus)\n setStatus(serviceStatus)\n\n if (serviceStatus === \"error\") {\n setError(\"DuckDB initialization failed\")\n } else if (serviceStatus === \"ready\") {\n setError(null)\n }\n }, [duckDBService])\n\n useEffect(() => {\n duckDBService.addStatusListener(updateStatus)\n updateStatus()\n\n return () => {\n duckDBService.removeStatusListener(updateStatus)\n }\n }, [duckDBService, updateStatus])\n\n const initialize = useCallback(async () => {\n try {\n await duckDBService.initialize()\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"Unknown error occurred\"\n setError(errorMessage)\n Logger.error(\"❌ DuckDB initialization failed:\", err)\n }\n }, [duckDBService])\n\n useEffect(() => {\n if (autoInitialize && status === \"not-initialized\") {\n Logger.info(\"🚀 Auto-initializing DuckDB...\")\n void initialize()\n }\n }, [autoInitialize, status, initialize])\n\n const executeQuery = useCallback(\n async (sql: string): Promise<unknown> => {\n if (status !== \"ready\") {\n throw new Error(\"DuckDB is not ready. Please wait for initialization to complete.\")\n }\n return duckDBService.executeQuery(sql)\n },\n [duckDBService, status],\n )\n\n const executeQueries = useCallback(\n async (queries: string[]): Promise<unknown[]> => {\n if (status !== \"ready\") {\n throw new Error(\"DuckDB is not ready. Please wait for initialization to complete.\")\n }\n return duckDBService.executeQueries(queries)\n },\n [duckDBService, status],\n )\n\n const createTableFromData = useCallback(\n async <T extends Record<string, unknown>>(\n tableName: string,\n data: T[],\n options?: {\n dropIfExists?: boolean\n primaryKey?: string\n verbose?: boolean\n },\n ): Promise<void> => {\n if (status !== \"ready\") {\n throw new Error(\"DuckDB is not ready. Please wait for initialization to complete.\")\n }\n return duckDBService.createTableFromData(tableName, data, options)\n },\n [duckDBService, status],\n )\n\n const getTableInfo = useCallback(\n async (tableName: string): Promise<unknown> => {\n if (status !== \"ready\") {\n throw new Error(\"DuckDB is not ready. Please wait for initialization to complete.\")\n }\n return duckDBService.getTableInfo(tableName)\n },\n [duckDBService, status],\n )\n\n const listTables = useCallback(async (): Promise<unknown> => {\n if (status !== \"ready\") {\n throw new Error(\"DuckDB is not ready. Please wait for initialization to complete.\")\n }\n return duckDBService.listTables()\n }, [duckDBService, status])\n\n return {\n status,\n error,\n executeQuery,\n executeQueries,\n createTableFromData,\n getTableInfo,\n listTables,\n isReady: status === \"ready\",\n initialize,\n }\n}\n\n/**\n * Hook for simple DuckDB query execution.\n * シンプルな DuckDB クエリ実行のためのフック。\n *\n * @remarks\n * `data` は DuckDB(Apache Arrow Table)の**生の結果**であり、行配列ではない。\n * `.length` や `.map` は持たないため、行配列として扱う場合は必ず\n * {@link duckdbTableToArray} で変換すること(`T` には行の型ではなく\n * Arrow Table 相当の型を想定)。\n *\n * @example\n * ```tsx\n * const { data } = useDuckDBQuery(\"SELECT * FROM t\")\n * const rows = duckdbTableToArray<Row>(data)\n * ```\n *\n * @param sql - 実行する SQL クエリ\n * @param dependencies - クエリを再実行するための依存配列\n * @returns クエリ結果(生の Arrow Table)と状態\n */\nexport const useDuckDBQuery = <T = unknown>(\n sql: string,\n dependencies: DependencyList = [],\n): {\n data: T | null\n loading: boolean\n error: string | null\n refetch: () => Promise<void>\n} => {\n const { executeQuery, isReady } = useDuckDB()\n const [data, setData] = useState<T | null>(null)\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const refetch = useCallback(async () => {\n if (!(isReady && sql.trim())) {\n return\n }\n\n try {\n setLoading(true)\n setError(null)\n const result = await executeQuery(sql)\n setData(result as T)\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"Query execution failed\"\n setError(errorMessage)\n setData(null)\n } finally {\n setLoading(false)\n }\n }, [executeQuery, isReady, sql])\n\n // 可変長の dependencies を直接スプレッドせず、変化を検知する固定サイズのバージョンに変換する。\n const dependenciesVersion = useDependencyVersion(dependencies)\n\n useEffect(() => {\n void refetch()\n }, [refetch, dependenciesVersion])\n\n return {\n data,\n loading,\n error,\n refetch,\n }\n}\n","/**\n * DuckDB Web Worker Service\n *\n * Provides reusable DuckDB-wasm functionality across the application.\n * アプリケーション全体で再利用可能な DuckDB-wasm 機能を提供します。\n */\n\nimport * as duckdb from \"@duckdb/duckdb-wasm\"\nimport { Logger } from \"../utils/simple-logger\"\n\n/** 初期化タイムアウト(WASM ダウンロードを考慮した長め設定)。 */\nconst INIT_TIMEOUT_MS = 120000\n/** クエリ実行タイムアウト。 */\nconst QUERY_TIMEOUT_MS = 60000\n/** splink_udfs インストールの個別タイムアウト。 */\nconst SPLINK_TIMEOUT_MS = 10000\n/** バッチ INSERT の1回あたり行数。 */\nconst BATCH_SIZE = 1000\n/** 型推定に使用する先頭サンプル行数。 */\nconst TYPE_INFERENCE_SAMPLE_SIZE = 100\n/**\n * 文字列を TIMESTAMP とみなすための厳密パターン。\n * `YYYY-MM-DD` または `YYYY-MM-DD[ T]HH:MM(:SS(.sss)?)?(Z|±HH:MM)?` のみ許容し、\n * `ABC-123` のような「ハイフンを含むが日付ではない」文字列を誤って日付扱いしない。\n */\nconst TIMESTAMP_PATTERN = /^\\d{4}-\\d{2}-\\d{2}([T ]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?)?$/\n\n/** 内部初期化のアボート指示を運ぶシグナル。 */\ninterface InitSignal {\n aborted: boolean\n}\n\nconst noop = (): void => {\n // 直列化キューの継続用。前段の解決/拒否を握りつぶす。\n}\n\n/**\n * DuckDB service for browser-based SQL operations.\n * ブラウザベースの SQL 操作のための DuckDB サービス。\n */\nclass DuckDBService {\n private static instance: DuckDBService | null = null\n private operationFlags: Map<string, Promise<void>> = new Map()\n\n private workerInstance: duckdb.AsyncDuckDB | null = null\n private connectionInstance: duckdb.AsyncDuckDBConnection | null = null\n private connectionPromise: Promise<duckdb.AsyncDuckDBConnection> | null = null\n private isInitializing = false\n private initPromise: Promise<duckdb.AsyncDuckDB> | null = null\n private hasError = false\n\n /** 初期化中の Worker / Blob URL(タイムアウト時などに確実に破棄するため保持)。 */\n private pendingWorker: Worker | null = null\n private pendingWorkerUrl: string | null = null\n\n /**\n * 共有コネクション上での操作直列化キュー。\n * createTableFromData の DDL/DML と executeQuery が相互に割り込まないようにする。\n */\n private operationQueue: Promise<unknown> = Promise.resolve()\n\n private listeners: Set<() => void> = new Set()\n\n /**\n * Get singleton instance of DuckDBService.\n * DuckDBService のシングルトンインスタンスを取得します。\n */\n public static getInstance(): DuckDBService {\n if (!DuckDBService.instance) {\n DuckDBService.instance = new DuckDBService()\n }\n return DuckDBService.instance\n }\n\n /**\n * Add status change listener.\n * 状態変更リスナーを追加します。\n */\n public addStatusListener(listener: () => void): void {\n this.listeners.add(listener)\n }\n\n /**\n * Remove status change listener.\n * 状態変更リスナーを削除します。\n */\n public removeStatusListener(listener: () => void): void {\n this.listeners.delete(listener)\n }\n\n /**\n * Notify all listeners of status change.\n * すべてのリスナーに状態変更を通知します。\n */\n private notifyListeners(): void {\n for (const listener of this.listeners) {\n try {\n listener()\n } catch (error) {\n Logger.error(\"⚠️ Error in status listener:\", error)\n }\n }\n }\n\n /**\n * Get current status.\n * 現在のステータスを取得します。\n */\n public getStatus(): \"not-initialized\" | \"initializing\" | \"ready\" | \"error\" {\n if (this.hasError) {\n return \"error\"\n }\n if (this.isInitializing) {\n return \"initializing\"\n }\n if (this.workerInstance) {\n return \"ready\"\n }\n return \"not-initialized\"\n }\n\n /** Enqueue a task on the single-connection serialization queue. */\n private enqueue<T>(task: () => Promise<T>): Promise<T> {\n const run = this.operationQueue.then(task, task)\n this.operationQueue = run.then(noop, noop)\n return run\n }\n\n /** アボート済みなら例外を投げる(初期化の各チェックポイントで使用)。 */\n private assertNotAborted(signal: InitSignal): void {\n if (signal.aborted) {\n throw new Error(\"DuckDB initialization aborted\")\n }\n }\n\n /** 初期化中の Worker / Blob URL を確実に破棄する。 */\n private abortPendingWorker(): void {\n if (this.pendingWorker) {\n try {\n this.pendingWorker.terminate()\n } catch {\n // terminate は冪等でなくても良い。失敗は無視する。\n }\n this.pendingWorker = null\n }\n if (this.pendingWorkerUrl) {\n try {\n URL.revokeObjectURL(this.pendingWorkerUrl)\n } catch {\n // revoke 失敗は無視する。\n }\n this.pendingWorkerUrl = null\n }\n }\n\n /**\n * Initialize DuckDB Worker instance.\n * DuckDB Worker インスタンスを初期化します。\n */\n public async initialize(): Promise<duckdb.AsyncDuckDB> {\n if (this.workerInstance) {\n return this.workerInstance\n }\n\n if (this.isInitializing && this.initPromise) {\n return this.initPromise\n }\n\n this.isInitializing = true\n this.hasError = false\n this.notifyListeners()\n\n // タイムアウト設定 (120秒) - WASMのダウンロードなどを考慮して長めに設定\n const signal: InitSignal = { aborted: false }\n // Promise executor は同期実行されるため timeoutId は必ず代入される。\n let timeoutId!: ReturnType<typeof setTimeout>\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n signal.aborted = true\n reject(new Error(`DuckDB initialization timed out after ${INIT_TIMEOUT_MS}ms`))\n }, INIT_TIMEOUT_MS)\n })\n\n this.initPromise = Promise.race([this.internalInitializeWorker(signal), timeoutPromise])\n\n try {\n const result = await this.initPromise\n clearTimeout(timeoutId)\n this.isInitializing = false\n this.notifyListeners()\n return result\n } catch (error) {\n clearTimeout(timeoutId)\n // 失敗/タイムアウト時は状態を完全にリセットして再試行可能にする。\n signal.aborted = true\n this.isInitializing = false\n this.hasError = true\n this.workerInstance = null\n this.connectionInstance = null\n this.connectionPromise = null\n this.initPromise = null\n this.abortPendingWorker()\n this.notifyListeners()\n throw error\n }\n }\n\n /**\n * Internal worker initialization method.\n * 内部的な Worker 初期化メソッド。\n *\n * workerInstance は instantiate 成功後にのみ公開する。途中で失敗/アボートした場合は\n * ローカルの Worker / Blob URL を破棄し、壊れたインスタンスを絶対に this に残さない。\n */\n private async internalInitializeWorker(signal: InitSignal): Promise<duckdb.AsyncDuckDB> {\n let workerUrl: string | null = null\n let worker: Worker | null = null\n try {\n Logger.info(\"🔧 Starting DuckDB Worker initialization...\")\n Logger.info(\"📦 Getting DuckDB bundles from CDN...\")\n const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles()\n\n Logger.info(\"🔍 Selecting appropriate bundle for browser...\")\n const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES)\n this.assertNotAborted(signal)\n Logger.info(\"✅ Bundle selected:\", {\n mainWorker: bundle.mainWorker,\n mainModule: bundle.mainModule,\n pthreadWorker: bundle.pthreadWorker,\n })\n\n Logger.info(\"🚀 Creating Worker with Blob URL...\")\n workerUrl = URL.createObjectURL(new Blob([`importScripts(\"${bundle.mainWorker}\");`], { type: \"text/javascript\" }))\n\n Logger.info(\"👷 Creating Worker instance...\")\n worker = new Worker(workerUrl)\n this.pendingWorker = worker\n this.pendingWorkerUrl = workerUrl\n\n const logger = new duckdb.ConsoleLogger()\n\n Logger.info(\"🗄️ Creating DuckDB instance...\")\n const db = new duckdb.AsyncDuckDB(logger, worker)\n\n Logger.info(\"⚡ Instantiating DuckDB with modules...\")\n await db.instantiate(bundle.mainModule, bundle.pthreadWorker)\n this.assertNotAborted(signal)\n\n Logger.info(\"📦 Installing splink_udfs from community...\")\n const connection = await db.connect()\n try {\n // splink_udfs のインストール/ロードはネットワークアクセスを伴うため、\n // 個別の短いタイムアウトを設けてハングを防止する。\n let splinkTimeoutId!: ReturnType<typeof setTimeout>\n const splinkTimeoutPromise = new Promise<never>((_, reject) => {\n splinkTimeoutId = setTimeout(() => {\n reject(new Error(`splink_udfs installation timed out after ${SPLINK_TIMEOUT_MS}ms`))\n }, SPLINK_TIMEOUT_MS)\n })\n\n await Promise.race([\n (async () => {\n await connection.query(\"INSTALL splink_udfs FROM community;\")\n Logger.info(\"✅ splink_udfs installed successfully\")\n\n await connection.query(\"LOAD splink_udfs;\")\n Logger.info(\"✅ splink_udfs loaded successfully\")\n })(),\n splinkTimeoutPromise,\n ])\n clearTimeout(splinkTimeoutId)\n } catch (error) {\n // タイムアウトやネットワークエラーの場合は警告ログを出して続行\n Logger.warn(\"⚠️ Failed to install/load splink_udfs:\", error)\n } finally {\n await connection.close()\n }\n this.assertNotAborted(signal)\n\n // すべて成功したときのみ公開する。\n this.workerInstance = db\n this.pendingWorker = null\n this.pendingWorkerUrl = null\n URL.revokeObjectURL(workerUrl)\n\n Logger.info(\"✅ DuckDB Worker initialized successfully\")\n return db\n } catch (error) {\n // 途中生成物を必ず破棄する(Worker スレッド / Blob URL のリーク防止)。\n if (worker) {\n try {\n worker.terminate()\n } catch {\n // 無視する。\n }\n }\n if (workerUrl) {\n try {\n URL.revokeObjectURL(workerUrl)\n } catch {\n // 無視する。\n }\n }\n if (this.pendingWorker === worker) {\n this.pendingWorker = null\n }\n if (this.pendingWorkerUrl === workerUrl) {\n this.pendingWorkerUrl = null\n }\n\n Logger.error(\"❌ Failed to initialize DuckDB Worker:\", error)\n if (error instanceof Error) {\n Logger.error(\"❌ Error name:\", error.name)\n Logger.error(\"❌ Error message:\", error.message)\n Logger.error(\"❌ Error stack:\", error.stack)\n }\n\n throw error\n }\n }\n\n /**\n * Get or create DuckDB connection.\n * DuckDB 接続を取得または作成します。\n *\n * 同時呼び出しでも connect() が二重実行されないよう、生成中の Promise を共有する。\n */\n public async getConnection(): Promise<duckdb.AsyncDuckDBConnection> {\n if (this.connectionInstance) {\n return this.connectionInstance\n }\n if (!this.connectionPromise) {\n this.connectionPromise = (async () => {\n const db = await this.initialize()\n const connection = await db.connect()\n this.connectionInstance = connection\n return connection\n })()\n }\n try {\n return await this.connectionPromise\n } catch (error) {\n // 失敗時はリセットして次回リトライ可能にする。\n this.connectionPromise = null\n throw error\n }\n }\n\n /**\n * Run a single query with a timeout, discarding the connection on timeout.\n * 単一クエリをタイムアウト付きで実行する。呼び出し側で直列化キューに載せる前提。\n *\n * タイムアウトしたクエリはワーカー側で走り続けるため、使用中コネクションを破棄して\n * 後続クエリが同じ(詰まった)コネクションで待たされないようにする。\n */\n private async runQueryWithTimeout(sql: string): Promise<unknown> {\n let timedOut = false\n // Promise executor は同期実行されるため timeoutId は必ず代入される。\n let timeoutId!: ReturnType<typeof setTimeout>\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n timedOut = true\n reject(new Error(`DuckDB query execution timed out after ${QUERY_TIMEOUT_MS}ms`))\n }, QUERY_TIMEOUT_MS)\n })\n\n // コネクション取得とクエリ実行の両方をタイムアウトの対象にする。\n const run = (async () => {\n const connection = await this.getConnection()\n return connection.query(sql)\n })()\n\n try {\n const result = await Promise.race([run, timeoutPromise])\n clearTimeout(timeoutId)\n return result\n } catch (error) {\n clearTimeout(timeoutId)\n if (timedOut) {\n const stale = this.connectionInstance\n this.connectionInstance = null\n this.connectionPromise = null\n if (stale) {\n void stale.close().catch(() => {\n // 詰まったコネクションの close 失敗は無視する。\n })\n }\n }\n Logger.error(\"❌ Failed to execute SQL query:\", error)\n throw error\n }\n }\n\n /**\n * Execute SQL query using DuckDB Worker.\n * DuckDB Worker を使用して SQL クエリを実行します。\n */\n public async executeQuery(sql: string): Promise<unknown> {\n return this.enqueue(() => this.runQueryWithTimeout(sql))\n }\n\n /**\n * Execute multiple SQL queries.\n * 複数の SQL クエリを実行します(逐次実行)。\n *\n * バッチ全体を1つのキュー単位として実行し、他操作が途中に割り込まないようにする。\n */\n public async executeQueries(queries: string[]): Promise<unknown[]> {\n return this.enqueue(async () => {\n const results: unknown[] = []\n for (const query of queries) {\n results.push(await this.runQueryWithTimeout(query))\n }\n return results\n })\n }\n\n /**\n * Collect the union of column names across all rows (first-seen order).\n * 全行を走査して列名の和集合を取得します(初出順を保持)。\n * 先頭行に存在しないキーを取りこぼさないため全行を対象とする。\n */\n private collectColumnNames(rows: Record<string, unknown>[]): string[] {\n const seen = new Set<string>()\n const names: string[] = []\n for (const row of rows) {\n if (!row) {\n continue\n }\n for (const key of Object.keys(row)) {\n if (!seen.has(key)) {\n seen.add(key)\n names.push(key)\n }\n }\n }\n return names\n }\n\n /** Check whether a string is a genuine (strict) date/timestamp literal. */\n private looksLikeTimestamp(value: string): boolean {\n if (!TIMESTAMP_PATTERN.test(value)) {\n return false\n }\n return !Number.isNaN(new Date(value).getTime())\n }\n\n /**\n * Infer column types from a data sample for the given columns.\n * 指定した列について、データサンプルから DuckDB の型を推定します。\n *\n * 整数は桁あふれを避けるため一律 BIGINT を採用する。\n */\n private inferColumnTypes(sample: Record<string, unknown>[], columnNames: string[]): Record<string, string> {\n const columnTypes: Record<string, string> = {}\n\n for (const key of columnNames) {\n const values = sample.map((row) => row?.[key]).filter((val) => val !== null && val !== undefined)\n\n if (values.length === 0) {\n columnTypes[key] = \"VARCHAR\"\n continue\n }\n\n const typeInfo = {\n number: 0,\n integer: 0,\n boolean: 0,\n date: 0,\n string: 0,\n }\n\n for (const value of values) {\n if (typeof value === \"number\") {\n if (Number.isFinite(value)) {\n typeInfo.number += 1\n if (Number.isInteger(value)) {\n typeInfo.integer += 1\n }\n } else {\n // NaN / Infinity は数値列にできないため文字列扱い(実挿入時は NULL 化)。\n typeInfo.string += 1\n }\n } else if (typeof value === \"bigint\") {\n typeInfo.number += 1\n typeInfo.integer += 1\n } else if (typeof value === \"boolean\") {\n typeInfo.boolean += 1\n } else if (value instanceof Date) {\n typeInfo.date += 1\n } else if (typeof value === \"string\" && this.looksLikeTimestamp(value)) {\n typeInfo.date += 1\n } else {\n typeInfo.string += 1\n }\n }\n\n if (typeInfo.number === values.length) {\n columnTypes[key] = typeInfo.integer === values.length ? \"BIGINT\" : \"DOUBLE\"\n } else if (typeInfo.boolean === values.length) {\n columnTypes[key] = \"BOOLEAN\"\n } else if (typeInfo.date === values.length) {\n columnTypes[key] = \"TIMESTAMP\"\n } else {\n columnTypes[key] = \"VARCHAR\"\n }\n }\n\n return columnTypes\n }\n\n /**\n * Format value for SQL insertion.\n * SQL 挿入用に値をフォーマットします。\n */\n private formatValueForSQL(value: unknown): string {\n if (value === null || value === undefined) {\n return \"NULL\"\n }\n if (typeof value === \"string\") {\n return `'${value.replace(/'/g, \"''\")}'`\n }\n if (value instanceof Date) {\n return `'${value.toISOString()}'`\n }\n if (typeof value === \"boolean\") {\n return value ? \"TRUE\" : \"FALSE\"\n }\n if (typeof value === \"bigint\") {\n return value.toString()\n }\n if (typeof value === \"number\") {\n // NaN / Infinity / -Infinity は有効な SQL 数値リテラルではないため NULL 化する。\n if (!Number.isFinite(value)) {\n return \"NULL\"\n }\n return String(value)\n }\n // オブジェクト / 配列などは JSON 文字列としてクォート・エスケープする。\n try {\n const json = JSON.stringify(value)\n if (json === undefined) {\n return \"NULL\"\n }\n return `'${json.replace(/'/g, \"''\")}'`\n } catch {\n return \"NULL\"\n }\n }\n\n /**\n * Quote a SQL identifier (table / column / primary key) safely.\n * SQL 識別子を安全にクォートします(内部のダブルクォートはエスケープ)。\n */\n private quoteIdentifier(name: string): string {\n if (typeof name !== \"string\" || name.length === 0) {\n throw new Error(\"Invalid SQL identifier\")\n }\n return `\"${name.replace(/\"/g, '\"\"')}\"`\n }\n\n /**\n * Create table from JavaScript array of objects.\n * JavaScript オブジェクトの配列からテーブルを作成します。\n *\n * 同名テーブルへの同時呼び出しは、進行中の同一 Promise を await する(早期 void 解決しない)。\n */\n public async createTableFromData<T extends Record<string, unknown>>(\n tableName: string,\n data: T[],\n options: {\n dropIfExists?: boolean\n primaryKey?: string\n verbose?: boolean\n } = {},\n ): Promise<void> {\n if (!data || data.length === 0) {\n throw new Error(\"Data array is empty\")\n }\n\n const operationKey = `create_table_${tableName}`\n const verbose = options.verbose ?? true\n\n const inFlight = this.operationFlags.get(operationKey)\n if (inFlight) {\n if (verbose) {\n Logger.info(`⏳ Table creation for '${tableName}' is already in progress, awaiting existing operation...`)\n }\n return inFlight\n }\n\n const operation = this.enqueue(() => this.buildTable(tableName, data, options, verbose))\n this.operationFlags.set(operationKey, operation)\n try {\n await operation\n } finally {\n this.operationFlags.delete(operationKey)\n }\n }\n\n /** Actual (serialized, transactional) table build. */\n private async buildTable<T extends Record<string, unknown>>(tableName: string, data: T[], options: { dropIfExists?: boolean; primaryKey?: string }, verbose: boolean): Promise<void> {\n try {\n const connection = await this.getConnection()\n const quotedTable = this.quoteIdentifier(tableName)\n\n const columnNames = this.collectColumnNames(data)\n if (columnNames.length === 0) {\n throw new Error(\"Data rows have no columns\")\n }\n\n const sampleSize = Math.min(data.length, TYPE_INFERENCE_SAMPLE_SIZE)\n const columnTypes = this.inferColumnTypes(data.slice(0, sampleSize), columnNames)\n\n const columnDefs = columnNames.map((key) => `${this.quoteIdentifier(key)} ${columnTypes[key]}`)\n const primaryKeyClause = options.primaryKey ? `, PRIMARY KEY (${this.quoteIdentifier(options.primaryKey)})` : \"\"\n const createTableSQL = `CREATE TABLE ${quotedTable} (${columnDefs.join(\", \")}${primaryKeyClause})`\n const columnList = columnNames.map((col) => this.quoteIdentifier(col)).join(\", \")\n\n if (verbose) {\n Logger.info(`📥 Building table '${tableName}' with ${data.length} rows`)\n }\n\n // DROP / CREATE / INSERT を1トランザクションにまとめ、途中失敗時はロールバックして\n // 半端に作成されたテーブルを残さない。\n await connection.query(\"BEGIN TRANSACTION\")\n try {\n if (options.dropIfExists) {\n if (verbose) {\n Logger.info(`🗑️ Dropping table '${tableName}' if exists`)\n }\n await connection.query(`DROP TABLE IF EXISTS ${quotedTable}`)\n }\n\n if (verbose) {\n Logger.info(\"🔨 Creating table with SQL:\", createTableSQL)\n }\n await connection.query(createTableSQL)\n\n for (let i = 0; i < data.length; i += BATCH_SIZE) {\n const batch = data.slice(i, i + BATCH_SIZE)\n const valueRows = batch.map((row) => {\n // null/undefined 行はすべて NULL として扱う(不揃いなデータへの耐性)。\n const values = columnNames.map((col) => this.formatValueForSQL(row?.[col]))\n return `(${values.join(\", \")})`\n })\n\n const insertSQL = `INSERT INTO ${quotedTable} (${columnList}) VALUES ${valueRows.join(\", \")}`\n await connection.query(insertSQL)\n if (verbose) {\n Logger.info(`📊 Inserted batch ${Math.floor(i / BATCH_SIZE) + 1} (${batch.length} rows)`)\n }\n }\n\n await connection.query(\"COMMIT\")\n } catch (txError) {\n try {\n await connection.query(\"ROLLBACK\")\n } catch (rollbackError) {\n Logger.warn(\"⚠️ Rollback failed:\", rollbackError)\n }\n throw txError\n }\n\n if (verbose) {\n Logger.info(`🎉 Table '${tableName}' created successfully with ${data.length} rows`)\n }\n } catch (error) {\n Logger.error(`❌ Failed to create table '${tableName}':`, error)\n throw error\n }\n }\n\n /**\n * Get table information.\n * テーブル情報を取得します。\n */\n public async getTableInfo(tableName: string): Promise<unknown> {\n return this.enqueue(async () => {\n const connection = await this.getConnection()\n return connection.query(`DESCRIBE ${this.quoteIdentifier(tableName)}`)\n })\n }\n\n /**\n * List all tables.\n * すべてのテーブルを一覧表示します。\n */\n public async listTables(): Promise<unknown> {\n return this.enqueue(async () => {\n const connection = await this.getConnection()\n return connection.query(\"SHOW TABLES\")\n })\n }\n\n /**\n * Check if DuckDB is ready.\n * DuckDB が準備完了かどうかを確認します。\n */\n public isReady(): boolean {\n return this.workerInstance !== null\n }\n\n /**\n * Cleanup DuckDB Worker resources.\n * DuckDB Worker のリソースをクリーンアップします。\n */\n public async cleanup(): Promise<void> {\n try {\n if (this.connectionInstance) {\n await this.connectionInstance.close()\n this.connectionInstance = null\n }\n if (this.workerInstance) {\n await this.workerInstance.terminate()\n this.workerInstance = null\n }\n this.abortPendingWorker()\n this.isInitializing = false\n this.hasError = false\n this.initPromise = null\n this.connectionPromise = null\n this.operationQueue = Promise.resolve()\n this.operationFlags.clear()\n this.notifyListeners()\n this.listeners.clear()\n } catch (error) {\n Logger.error(\"⚠️ Error during cleanup:\", error)\n }\n }\n\n /**\n * Reset the service instance (useful for testing).\n * サービスインスタンスをリセットします(テスト用)。\n */\n public static resetInstance(): void {\n if (DuckDBService.instance) {\n void DuckDBService.instance.cleanup()\n DuckDBService.instance = null\n }\n }\n}\n\nexport { DuckDBService }\n\nexport default DuckDBService.getInstance()\n","/**\n * @module utils/logger\n * @description Logger utility for consistent log management across the library.\n * @description ライブラリ全体で一貫したログ管理を行うためのロガーユーティリティ。\n */\n\n/**\n * @enum LogLevel\n * @description Log levels for filtering output.\n * @description 出力をフィルタリングするためのログレベル。\n */\nexport enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n NONE = 4,\n}\n\n/**\n * @interface ILogger\n * @description Interface for a logger object compatible with Console.\n * @description Console と互換性のあるロガーオブジェクトのインターフェース。\n */\nexport interface ILogger {\n debug(message?: unknown, ...optionalParams: unknown[]): void\n info(message?: unknown, ...optionalParams: unknown[]): void\n warn(message?: unknown, ...optionalParams: unknown[]): void\n error(message?: unknown, ...optionalParams: unknown[]): void\n}\n\n/**\n * @class Logger\n * @description A wrapper class for handling logging with levels and prefixes.\n * @description レベルとプレフィックスを使用したログ記録を処理するためのラッパークラス。\n */\nexport class Logger implements ILogger {\n private level: LogLevel\n private prefix: string\n private impl: ILogger\n\n /**\n * @constructor\n * @param {LogLevel} [level=LogLevel.WARN] - The minimum log level to output.\n * @param {string} [prefix=\"[duckdb-helper]\"] - The prefix to add to all log messages.\n * @param {ILogger} [impl=console] - The implementation to use for logging.\n */\n constructor(level: LogLevel = LogLevel.WARN, prefix: string = \"[duckdb-helper]\", impl: ILogger = console) {\n this.level = level\n this.prefix = prefix\n this.impl = impl\n }\n\n private static instance: Logger = new Logger(LogLevel.WARN, \"[duckdb-helper]\")\n\n /**\n * @method setLevel\n * @description Updates the current log level for the static instance.\n * @description 静的インスタンスの現在のログレベルを更新します。\n * @param {LogLevel} level - The new log level.\n */\n static setLevel(level: LogLevel): void {\n Logger.instance.setLevel(level)\n }\n\n /**\n * @method setLevel\n * @description Updates the current log level.\n * @description 現在のログレベルを更新します。\n * @param {LogLevel} level - The new log level.\n */\n setLevel(level: LogLevel): void {\n this.level = level\n }\n\n /**\n * @method setImplementation\n * @description Updates the logger implementation for the static instance.\n * @description 静的インスタンスのロガーの実装を更新します。\n * @param {ILogger} impl - The new logger implementation.\n */\n static setImplementation(impl: ILogger): void {\n Logger.instance.setImplementation(impl)\n }\n\n /**\n * @method setImplementation\n * @description Updates the logger implementation.\n * @description ロガーの実装を更新します。\n * @param {ILogger} impl - The new logger implementation.\n */\n setImplementation(impl: ILogger): void {\n this.impl = impl\n }\n\n /**\n * @method setPrefix\n * @description Updates the log prefix for the static instance.\n * @description 静的インスタンスのログのプレフィックスを更新します。\n * @param {string} prefix - The new prefix.\n */\n static setPrefix(prefix: string): void {\n Logger.instance.setPrefix(prefix)\n }\n\n /**\n * @method setPrefix\n * @description Updates the log prefix.\n * @description ログのプレフィックスを更新します。\n * @param {string} prefix - The new prefix.\n */\n setPrefix(prefix: string): void {\n this.prefix = prefix\n }\n\n private formatMessage(message: unknown): unknown[] {\n if (typeof message === \"string\") {\n return [`${this.prefix} ${message}`]\n }\n return [this.prefix, message]\n }\n\n static debug(message?: unknown, ...optionalParams: unknown[]): void {\n Logger.instance.debug(message, ...optionalParams)\n }\n\n debug(message?: unknown, ...optionalParams: unknown[]): void {\n if (this.level <= LogLevel.DEBUG) {\n this.impl.debug(...this.formatMessage(message), ...optionalParams)\n }\n }\n\n static info(message?: unknown, ...optionalParams: unknown[]): void {\n Logger.instance.info(message, ...optionalParams)\n }\n\n info(message?: unknown, ...optionalParams: unknown[]): void {\n if (this.level <= LogLevel.INFO) {\n this.impl.info(...this.formatMessage(message), ...optionalParams)\n }\n }\n\n static warn(message?: unknown, ...optionalParams: unknown[]): void {\n Logger.instance.warn(message, ...optionalParams)\n }\n\n warn(message?: unknown, ...optionalParams: unknown[]): void {\n if (this.level <= LogLevel.WARN) {\n this.impl.warn(...this.formatMessage(message), ...optionalParams)\n }\n }\n\n static error(message?: unknown, ...optionalParams: unknown[]): void {\n Logger.instance.error(message, ...optionalParams)\n }\n\n error(message?: unknown, ...optionalParams: unknown[]): void {\n if (this.level <= LogLevel.ERROR) {\n this.impl.error(...this.formatMessage(message), ...optionalParams)\n }\n }\n}\n","/**\n * DuckDB Helper Functions\n *\n * Utility functions for working with DuckDB query results.\n * DuckDB クエリ結果を扱うためのユーティリティ関数。\n */\n\nimport type { DuckDBQueryResult } from \"../types/duckdb\"\n\n/**\n * Safely convert DuckDB table to typed array\n * DuckDB テーブルを型付き配列に安全に変換します\n *\n * @param result - DuckDB query result\n * @returns Typed array of results\n */\nexport const duckdbTableToArray = <T>(result: unknown): T[] => {\n if (!result || typeof result !== \"object\") {\n return []\n }\n\n if (\"toArray\" in result && typeof result.toArray === \"function\") {\n return (result as DuckDBQueryResult<T>).toArray()\n }\n\n return []\n}\n\n/**\n * Get table row count\n * テーブルの行数を取得します\n *\n * @param result - DuckDB query result\n * @returns Number of rows in the table\n */\nexport const getDuckDBRowCount = (result: unknown): number => {\n if (!result || typeof result !== \"object\") {\n return 0\n }\n\n if (\"numRows\" in result && typeof result.numRows === \"number\") {\n return result.numRows\n }\n\n return 0\n}\n\n/**\n * Get table column count\n * テーブルの列数を取得します\n *\n * @param result - DuckDB query result\n * @returns Number of columns in the table\n */\nexport const getDuckDBColumnCount = (result: unknown): number => {\n if (!result || typeof result !== \"object\") {\n return 0\n }\n\n if (\"numCols\" in result && typeof result.numCols === \"number\") {\n return result.numCols\n }\n\n return 0\n}\n\n/**\n * Check if result is a valid DuckDB table\n * 結果が有効な DuckDB テーブルかどうかをチェックします\n *\n * @param result - DuckDB query result\n * @returns True if result is a valid DuckDB table\n */\nexport const isDuckDBTable = (result: unknown): result is DuckDBQueryResult => {\n if (!result || typeof result !== \"object\") {\n return false\n }\n\n return \"toArray\" in result && typeof result.toArray === \"function\" && \"numRows\" in result && typeof result.numRows === \"number\"\n}\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useState","duckdb","LogLevel","_Logger","level","prefix","impl","message","optionalParams","Logger","INIT_TIMEOUT_MS","QUERY_TIMEOUT_MS","SPLINK_TIMEOUT_MS","BATCH_SIZE","TYPE_INFERENCE_SAMPLE_SIZE","TIMESTAMP_PATTERN","noop","_DuckDBService","listener","error","Logger","task","run","signal","timeoutId","timeoutPromise","_","reject","result","workerUrl","worker","JSDELIVR_BUNDLES","bundle","logger","db","connection","splinkTimeoutId","splinkTimeoutPromise","sql","timedOut","stale","queries","results","query","rows","seen","names","row","key","value","sample","columnNames","columnTypes","values","val","typeInfo","json","name","tableName","data","options","operationKey","verbose","inFlight","operation","quotedTable","sampleSize","columnDefs","primaryKeyClause","createTableSQL","columnList","col","i","batch","valueRows","insertSQL","txError","rollbackError","DuckDBService","duckdb_default","DuckDBService","useDependencyVersion","dependencies","ref","useRef","previous","dep","index","useDuckDB","autoInitialize","status","setStatus","useState","error","setError","duckDBService","DuckDBService","updateStatus","useCallback","serviceStatus","Logger","useEffect","initialize","err","errorMessage","executeQuery","sql","executeQueries","queries","createTableFromData","tableName","data","options","getTableInfo","listTables","useDuckDBQuery","isReady","setData","loading","setLoading","refetch","result","dependenciesVersion","duckdbTableToArray","result","getDuckDBRowCount","getDuckDBColumnCount","isDuckDBTable"]}
1
+ {"version":3,"sources":["../src/hooks/useDuckDB.ts","../src/services/duckdb.ts","../src/utils/simple-logger.ts","../src/utils/duckdb-helpers.ts"],"sourcesContent":["/**\n * DuckDB React Hook\n *\n * React hook for using DuckDB functionality in components.\n * コンポーネントで DuckDB 機能を使用するための React フック。\n */\n\nimport { type DependencyList, useCallback, useEffect, useRef, useState } from \"react\"\nimport { DuckDBService } from \"../services/duckdb\"\nimport { Logger } from \"../utils/simple-logger\"\n\n/**\n * Derive a constant-size version number that increments whenever the given\n * dependency list changes (by React's own Object.is comparison).\n * 依存配列の変化(React と同じ Object.is 比較)で増える固定サイズのバージョン番号を返す。\n *\n * これにより可変長の `dependencies` を effect の依存配列に直接スプレッドせずに済み、\n * 配列サイズがレンダー間で変わっても React の「依存配列サイズ変更」エラーを起こさない。\n */\nconst useDependencyVersion = (dependencies: DependencyList): number => {\n const ref = useRef<{ deps: DependencyList; version: number }>({ deps: dependencies, version: 0 })\n const previous = ref.current.deps\n const changed = previous.length !== dependencies.length || dependencies.some((dep, index) => !Object.is(dep, previous[index]))\n if (changed) {\n ref.current = { deps: dependencies, version: ref.current.version + 1 }\n }\n return ref.current.version\n}\n\nexport interface UseDuckDBResult {\n status: \"not-initialized\" | \"initializing\" | \"ready\" | \"error\"\n error: string | null\n executeQuery: (sql: string) => Promise<unknown>\n executeQueries: (queries: string[]) => Promise<unknown[]>\n createTableFromData: <T extends Record<string, unknown>>(\n tableName: string,\n data: T[],\n options?: {\n dropIfExists?: boolean\n primaryKey?: string\n verbose?: boolean\n },\n ) => Promise<void>\n getTableInfo: (tableName: string) => Promise<unknown>\n listTables: () => Promise<unknown>\n isReady: boolean\n initialize: () => Promise<void>\n}\n\n/**\n * DuckDB functionality hook.\n * DuckDB 機能を提供するフック。\n *\n * @param autoInitialize - 自動初期化を行うかどうか\n * @returns DuckDB の状態と操作関数\n */\nexport const useDuckDB = (autoInitialize = true): UseDuckDBResult => {\n const [status, setStatus] = useState<\"not-initialized\" | \"initializing\" | \"ready\" | \"error\">(\"not-initialized\")\n const [error, setError] = useState<string | null>(null)\n const [duckDBService] = useState(() => DuckDBService.getInstance())\n\n const updateStatus = useCallback(() => {\n const serviceStatus = duckDBService.getStatus()\n Logger.info(\"🔄 DuckDB status updated:\", serviceStatus)\n setStatus(serviceStatus)\n\n if (serviceStatus === \"error\") {\n setError(\"DuckDB initialization failed\")\n } else if (serviceStatus === \"ready\") {\n setError(null)\n }\n }, [duckDBService])\n\n useEffect(() => {\n duckDBService.addStatusListener(updateStatus)\n updateStatus()\n\n return () => {\n duckDBService.removeStatusListener(updateStatus)\n }\n }, [duckDBService, updateStatus])\n\n const initialize = useCallback(async () => {\n try {\n await duckDBService.initialize()\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"Unknown error occurred\"\n setError(errorMessage)\n Logger.error(\"❌ DuckDB initialization failed:\", err)\n }\n }, [duckDBService])\n\n useEffect(() => {\n if (autoInitialize && status === \"not-initialized\") {\n Logger.info(\"🚀 Auto-initializing DuckDB...\")\n void initialize()\n }\n }, [autoInitialize, status, initialize])\n\n const executeQuery = useCallback(\n async (sql: string): Promise<unknown> => {\n if (status !== \"ready\") {\n throw new Error(\"DuckDB is not ready. Please wait for initialization to complete.\")\n }\n return duckDBService.executeQuery(sql)\n },\n [duckDBService, status],\n )\n\n const executeQueries = useCallback(\n async (queries: string[]): Promise<unknown[]> => {\n if (status !== \"ready\") {\n throw new Error(\"DuckDB is not ready. Please wait for initialization to complete.\")\n }\n return duckDBService.executeQueries(queries)\n },\n [duckDBService, status],\n )\n\n const createTableFromData = useCallback(\n async <T extends Record<string, unknown>>(\n tableName: string,\n data: T[],\n options?: {\n dropIfExists?: boolean\n primaryKey?: string\n verbose?: boolean\n },\n ): Promise<void> => {\n if (status !== \"ready\") {\n throw new Error(\"DuckDB is not ready. Please wait for initialization to complete.\")\n }\n return duckDBService.createTableFromData(tableName, data, options)\n },\n [duckDBService, status],\n )\n\n const getTableInfo = useCallback(\n async (tableName: string): Promise<unknown> => {\n if (status !== \"ready\") {\n throw new Error(\"DuckDB is not ready. Please wait for initialization to complete.\")\n }\n return duckDBService.getTableInfo(tableName)\n },\n [duckDBService, status],\n )\n\n const listTables = useCallback(async (): Promise<unknown> => {\n if (status !== \"ready\") {\n throw new Error(\"DuckDB is not ready. Please wait for initialization to complete.\")\n }\n return duckDBService.listTables()\n }, [duckDBService, status])\n\n return {\n status,\n error,\n executeQuery,\n executeQueries,\n createTableFromData,\n getTableInfo,\n listTables,\n isReady: status === \"ready\",\n initialize,\n }\n}\n\n/**\n * Hook for simple DuckDB query execution.\n * シンプルな DuckDB クエリ実行のためのフック。\n *\n * @remarks\n * `data` は DuckDB(Apache Arrow Table)の**生の結果**であり、行配列ではない。\n * `.length` や `.map` は持たないため、行配列として扱う場合は必ず\n * {@link duckdbTableToArray} で変換すること(`T` には行の型ではなく\n * Arrow Table 相当の型を想定)。\n *\n * @example\n * ```tsx\n * const { data } = useDuckDBQuery(\"SELECT * FROM t\")\n * const rows = duckdbTableToArray<Row>(data)\n * ```\n *\n * @param sql - 実行する SQL クエリ\n * @param dependencies - クエリを再実行するための依存配列\n * @returns クエリ結果(生の Arrow Table)と状態\n */\nexport const useDuckDBQuery = <T = unknown>(\n sql: string,\n dependencies: DependencyList = [],\n): {\n data: T | null\n loading: boolean\n error: string | null\n refetch: () => Promise<void>\n} => {\n const { executeQuery, isReady } = useDuckDB()\n const [data, setData] = useState<T | null>(null)\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const refetch = useCallback(async () => {\n if (!(isReady && sql.trim())) {\n return\n }\n\n try {\n setLoading(true)\n setError(null)\n const result = await executeQuery(sql)\n setData(result as T)\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"Query execution failed\"\n setError(errorMessage)\n setData(null)\n } finally {\n setLoading(false)\n }\n }, [executeQuery, isReady, sql])\n\n // 可変長の dependencies を直接スプレッドせず、変化を検知する固定サイズのバージョンに変換する。\n const dependenciesVersion = useDependencyVersion(dependencies)\n\n useEffect(() => {\n void refetch()\n }, [refetch, dependenciesVersion])\n\n return {\n data,\n loading,\n error,\n refetch,\n }\n}\n","/**\n * DuckDB Web Worker Service\n *\n * Provides reusable DuckDB-wasm functionality across the application.\n * アプリケーション全体で再利用可能な DuckDB-wasm 機能を提供します。\n */\n\nimport * as duckdb from \"@duckdb/duckdb-wasm\"\nimport { Logger } from \"../utils/simple-logger\"\n\n/** 初期化タイムアウト(WASM ダウンロードを考慮した長め設定)。 */\nconst INIT_TIMEOUT_MS = 120000\n/** クエリ実行タイムアウト。 */\nconst QUERY_TIMEOUT_MS = 60000\n/** splink_udfs インストールの個別タイムアウト。 */\nconst SPLINK_TIMEOUT_MS = 10000\n/** バッチ INSERT の1回あたり行数。 */\nconst BATCH_SIZE = 1000\n/** 型推定に使用する先頭サンプル行数。 */\nconst TYPE_INFERENCE_SAMPLE_SIZE = 100\n/**\n * 文字列を TIMESTAMP とみなすための厳密パターン。\n * `YYYY-MM-DD` または `YYYY-MM-DD[ T]HH:MM(:SS(.sss)?)?(Z|±HH:MM)?` のみ許容し、\n * `ABC-123` のような「ハイフンを含むが日付ではない」文字列を誤って日付扱いしない。\n */\nconst TIMESTAMP_PATTERN = /^\\d{4}-\\d{2}-\\d{2}([T ]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?)?$/\n\n/** 内部初期化のアボート指示を運ぶシグナル。 */\ninterface InitSignal {\n aborted: boolean\n}\n\nconst noop = (): void => {\n // 直列化キューの継続用。前段の解決/拒否を握りつぶす。\n}\n\n/**\n * DuckDB service for browser-based SQL operations.\n * ブラウザベースの SQL 操作のための DuckDB サービス。\n */\nclass DuckDBService {\n private static instance: DuckDBService | null = null\n private operationFlags: Map<string, Promise<void>> = new Map()\n\n private workerInstance: duckdb.AsyncDuckDB | null = null\n private connectionInstance: duckdb.AsyncDuckDBConnection | null = null\n private connectionPromise: Promise<duckdb.AsyncDuckDBConnection> | null = null\n private isInitializing = false\n private initPromise: Promise<duckdb.AsyncDuckDB> | null = null\n private hasError = false\n\n /** 初期化中の Worker / Blob URL(タイムアウト時などに確実に破棄するため保持)。 */\n private pendingWorker: Worker | null = null\n private pendingWorkerUrl: string | null = null\n\n /**\n * 共有コネクション上での操作直列化キュー。\n * createTableFromData の DDL/DML と executeQuery が相互に割り込まないようにする。\n */\n private operationQueue: Promise<unknown> = Promise.resolve()\n\n private listeners: Set<() => void> = new Set()\n\n /**\n * Get singleton instance of DuckDBService.\n * DuckDBService のシングルトンインスタンスを取得します。\n */\n public static getInstance(): DuckDBService {\n if (!DuckDBService.instance) {\n DuckDBService.instance = new DuckDBService()\n }\n return DuckDBService.instance\n }\n\n /**\n * Add status change listener.\n * 状態変更リスナーを追加します。\n */\n public addStatusListener(listener: () => void): void {\n this.listeners.add(listener)\n }\n\n /**\n * Remove status change listener.\n * 状態変更リスナーを削除します。\n */\n public removeStatusListener(listener: () => void): void {\n this.listeners.delete(listener)\n }\n\n /**\n * Notify all listeners of status change.\n * すべてのリスナーに状態変更を通知します。\n */\n private notifyListeners(): void {\n for (const listener of this.listeners) {\n try {\n listener()\n } catch (error) {\n Logger.error(\"⚠️ Error in status listener:\", error)\n }\n }\n }\n\n /**\n * Get current status.\n * 現在のステータスを取得します。\n */\n public getStatus(): \"not-initialized\" | \"initializing\" | \"ready\" | \"error\" {\n if (this.hasError) {\n return \"error\"\n }\n if (this.isInitializing) {\n return \"initializing\"\n }\n if (this.workerInstance) {\n return \"ready\"\n }\n return \"not-initialized\"\n }\n\n /** Enqueue a task on the single-connection serialization queue. */\n private enqueue<T>(task: () => Promise<T>): Promise<T> {\n const run = this.operationQueue.then(task, task)\n this.operationQueue = run.then(noop, noop)\n return run\n }\n\n /** アボート済みなら例外を投げる(初期化の各チェックポイントで使用)。 */\n private assertNotAborted(signal: InitSignal): void {\n if (signal.aborted) {\n throw new Error(\"DuckDB initialization aborted\")\n }\n }\n\n /** 初期化中の Worker / Blob URL を確実に破棄する。 */\n private abortPendingWorker(): void {\n if (this.pendingWorker) {\n try {\n this.pendingWorker.terminate()\n } catch {\n // terminate は冪等でなくても良い。失敗は無視する。\n }\n this.pendingWorker = null\n }\n if (this.pendingWorkerUrl) {\n try {\n URL.revokeObjectURL(this.pendingWorkerUrl)\n } catch {\n // revoke 失敗は無視する。\n }\n this.pendingWorkerUrl = null\n }\n }\n\n /**\n * Initialize DuckDB Worker instance.\n * DuckDB Worker インスタンスを初期化します。\n */\n public async initialize(): Promise<duckdb.AsyncDuckDB> {\n if (this.workerInstance) {\n return this.workerInstance\n }\n\n if (this.isInitializing && this.initPromise) {\n return this.initPromise\n }\n\n this.isInitializing = true\n this.hasError = false\n this.notifyListeners()\n\n // タイムアウト設定 (120秒) - WASMのダウンロードなどを考慮して長めに設定\n const signal: InitSignal = { aborted: false }\n // Promise executor は同期実行されるため timeoutId は必ず代入される。\n let timeoutId!: ReturnType<typeof setTimeout>\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n signal.aborted = true\n reject(new Error(`DuckDB initialization timed out after ${INIT_TIMEOUT_MS}ms`))\n }, INIT_TIMEOUT_MS)\n })\n\n this.initPromise = Promise.race([this.internalInitializeWorker(signal), timeoutPromise])\n\n try {\n const result = await this.initPromise\n clearTimeout(timeoutId)\n this.isInitializing = false\n this.notifyListeners()\n return result\n } catch (error) {\n clearTimeout(timeoutId)\n // 失敗/タイムアウト時は状態を完全にリセットして再試行可能にする。\n signal.aborted = true\n this.isInitializing = false\n this.hasError = true\n this.workerInstance = null\n this.connectionInstance = null\n this.connectionPromise = null\n this.initPromise = null\n this.abortPendingWorker()\n this.notifyListeners()\n throw error\n }\n }\n\n /**\n * Internal worker initialization method.\n * 内部的な Worker 初期化メソッド。\n *\n * workerInstance は instantiate 成功後にのみ公開する。途中で失敗/アボートした場合は\n * ローカルの Worker / Blob URL を破棄し、壊れたインスタンスを絶対に this に残さない。\n */\n private async internalInitializeWorker(signal: InitSignal): Promise<duckdb.AsyncDuckDB> {\n let workerUrl: string | null = null\n let worker: Worker | null = null\n try {\n Logger.info(\"🔧 Starting DuckDB Worker initialization...\")\n Logger.info(\"📦 Getting DuckDB bundles from CDN...\")\n const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles()\n\n Logger.info(\"🔍 Selecting appropriate bundle for browser...\")\n const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES)\n this.assertNotAborted(signal)\n\n // CDN URL をローカルの静的配信パス (/assets/duckdb/) に差し替える\n const workerName = bundle.mainWorker?.split(\"/\").pop()\n const moduleName = bundle.mainModule?.split(\"/\").pop()\n const pthreadWorkerName = bundle.pthreadWorker?.split(\"/\").pop()\n\n const localBundle = {\n mainWorker: workerName ? `/assets/duckdb/${workerName}` : \"\",\n mainModule: moduleName ? `/assets/duckdb/${moduleName}` : \"\",\n pthreadWorker: pthreadWorkerName ? `/assets/duckdb/${pthreadWorkerName}` : undefined,\n }\n\n Logger.info(\"✅ Bundle selected:\", {\n mainWorker: localBundle.mainWorker,\n mainModule: localBundle.mainModule,\n pthreadWorker: localBundle.pthreadWorker,\n })\n\n Logger.info(\"🚀 Creating Worker with Blob URL...\")\n workerUrl = URL.createObjectURL(new Blob([`importScripts(\"${localBundle.mainWorker}\");`], { type: \"text/javascript\" }))\n\n Logger.info(\"👷 Creating Worker instance...\")\n worker = new Worker(workerUrl)\n this.pendingWorker = worker\n this.pendingWorkerUrl = workerUrl\n\n const logger = new duckdb.ConsoleLogger()\n\n Logger.info(\"🗄️ Creating DuckDB instance...\")\n const db = new duckdb.AsyncDuckDB(logger, worker)\n\n Logger.info(\"⚡ Instantiating DuckDB with modules...\")\n await db.instantiate(localBundle.mainModule, localBundle.pthreadWorker)\n this.assertNotAborted(signal)\n\n Logger.info(\"📦 Installing splink_udfs from community...\")\n const connection = await db.connect()\n try {\n // splink_udfs のインストール/ロードはネットワークアクセスを伴うため、\n // 個別の短いタイムアウトを設けてハングを防止する。\n let splinkTimeoutId!: ReturnType<typeof setTimeout>\n const splinkTimeoutPromise = new Promise<never>((_, reject) => {\n splinkTimeoutId = setTimeout(() => {\n reject(new Error(`splink_udfs installation timed out after ${SPLINK_TIMEOUT_MS}ms`))\n }, SPLINK_TIMEOUT_MS)\n })\n\n await Promise.race([\n (async () => {\n await connection.query(\"INSTALL splink_udfs FROM community;\")\n Logger.info(\"✅ splink_udfs installed successfully\")\n\n await connection.query(\"LOAD splink_udfs;\")\n Logger.info(\"✅ splink_udfs loaded successfully\")\n })(),\n splinkTimeoutPromise,\n ])\n clearTimeout(splinkTimeoutId)\n } catch (error) {\n // タイムアウトやネットワークエラーの場合は警告ログを出して続行\n Logger.warn(\"⚠️ Failed to install/load splink_udfs:\", error)\n } finally {\n await connection.close()\n }\n this.assertNotAborted(signal)\n\n // すべて成功したときのみ公開する。\n this.workerInstance = db\n this.pendingWorker = null\n this.pendingWorkerUrl = null\n URL.revokeObjectURL(workerUrl)\n\n Logger.info(\"✅ DuckDB Worker initialized successfully\")\n return db\n } catch (error) {\n // 途中生成物を必ず破棄する(Worker スレッド / Blob URL のリーク防止)。\n if (worker) {\n try {\n worker.terminate()\n } catch {\n // 無視する。\n }\n }\n if (workerUrl) {\n try {\n URL.revokeObjectURL(workerUrl)\n } catch {\n // 無視する。\n }\n }\n if (this.pendingWorker === worker) {\n this.pendingWorker = null\n }\n if (this.pendingWorkerUrl === workerUrl) {\n this.pendingWorkerUrl = null\n }\n\n Logger.error(\"❌ Failed to initialize DuckDB Worker:\", error)\n if (error instanceof Error) {\n Logger.error(\"❌ Error name:\", error.name)\n Logger.error(\"❌ Error message:\", error.message)\n Logger.error(\"❌ Error stack:\", error.stack)\n }\n\n throw error\n }\n }\n\n /**\n * Get or create DuckDB connection.\n * DuckDB 接続を取得または作成します。\n *\n * 同時呼び出しでも connect() が二重実行されないよう、生成中の Promise を共有する。\n */\n public async getConnection(): Promise<duckdb.AsyncDuckDBConnection> {\n if (this.connectionInstance) {\n return this.connectionInstance\n }\n if (!this.connectionPromise) {\n this.connectionPromise = (async () => {\n const db = await this.initialize()\n const connection = await db.connect()\n this.connectionInstance = connection\n return connection\n })()\n }\n try {\n return await this.connectionPromise\n } catch (error) {\n // 失敗時はリセットして次回リトライ可能にする。\n this.connectionPromise = null\n throw error\n }\n }\n\n /**\n * Run a single query with a timeout, discarding the connection on timeout.\n * 単一クエリをタイムアウト付きで実行する。呼び出し側で直列化キューに載せる前提。\n *\n * タイムアウトしたクエリはワーカー側で走り続けるため、使用中コネクションを破棄して\n * 後続クエリが同じ(詰まった)コネクションで待たされないようにする。\n */\n private async runQueryWithTimeout(sql: string): Promise<unknown> {\n let timedOut = false\n // Promise executor は同期実行されるため timeoutId は必ず代入される。\n let timeoutId!: ReturnType<typeof setTimeout>\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n timedOut = true\n reject(new Error(`DuckDB query execution timed out after ${QUERY_TIMEOUT_MS}ms`))\n }, QUERY_TIMEOUT_MS)\n })\n\n // コネクション取得とクエリ実行の両方をタイムアウトの対象にする。\n const run = (async () => {\n const connection = await this.getConnection()\n return connection.query(sql)\n })()\n\n try {\n const result = await Promise.race([run, timeoutPromise])\n clearTimeout(timeoutId)\n return result\n } catch (error) {\n clearTimeout(timeoutId)\n if (timedOut) {\n const stale = this.connectionInstance\n this.connectionInstance = null\n this.connectionPromise = null\n if (stale) {\n void stale.close().catch(() => {\n // 詰まったコネクションの close 失敗は無視する。\n })\n }\n }\n Logger.error(\"❌ Failed to execute SQL query:\", error)\n throw error\n }\n }\n\n /**\n * Execute SQL query using DuckDB Worker.\n * DuckDB Worker を使用して SQL クエリを実行します。\n */\n public async executeQuery(sql: string): Promise<unknown> {\n return this.enqueue(() => this.runQueryWithTimeout(sql))\n }\n\n /**\n * Execute multiple SQL queries.\n * 複数の SQL クエリを実行します(逐次実行)。\n *\n * バッチ全体を1つのキュー単位として実行し、他操作が途中に割り込まないようにする。\n */\n public async executeQueries(queries: string[]): Promise<unknown[]> {\n return this.enqueue(async () => {\n const results: unknown[] = []\n for (const query of queries) {\n results.push(await this.runQueryWithTimeout(query))\n }\n return results\n })\n }\n\n /**\n * Collect the union of column names across all rows (first-seen order).\n * 全行を走査して列名の和集合を取得します(初出順を保持)。\n * 先頭行に存在しないキーを取りこぼさないため全行を対象とする。\n */\n private collectColumnNames(rows: Record<string, unknown>[]): string[] {\n const seen = new Set<string>()\n const names: string[] = []\n for (const row of rows) {\n if (!row) {\n continue\n }\n for (const key of Object.keys(row)) {\n if (!seen.has(key)) {\n seen.add(key)\n names.push(key)\n }\n }\n }\n return names\n }\n\n /** Check whether a string is a genuine (strict) date/timestamp literal. */\n private looksLikeTimestamp(value: string): boolean {\n if (!TIMESTAMP_PATTERN.test(value)) {\n return false\n }\n return !Number.isNaN(new Date(value).getTime())\n }\n\n /**\n * Infer column types from a data sample for the given columns.\n * 指定した列について、データサンプルから DuckDB の型を推定します。\n *\n * 整数は桁あふれを避けるため一律 BIGINT を採用する。\n */\n private inferColumnTypes(sample: Record<string, unknown>[], columnNames: string[]): Record<string, string> {\n const columnTypes: Record<string, string> = {}\n\n for (const key of columnNames) {\n const values = sample.map((row) => row?.[key]).filter((val) => val !== null && val !== undefined)\n\n if (values.length === 0) {\n columnTypes[key] = \"VARCHAR\"\n continue\n }\n\n const typeInfo = {\n number: 0,\n integer: 0,\n boolean: 0,\n date: 0,\n string: 0,\n }\n\n for (const value of values) {\n if (typeof value === \"number\") {\n if (Number.isFinite(value)) {\n typeInfo.number += 1\n if (Number.isInteger(value)) {\n typeInfo.integer += 1\n }\n } else {\n // NaN / Infinity は数値列にできないため文字列扱い(実挿入時は NULL 化)。\n typeInfo.string += 1\n }\n } else if (typeof value === \"bigint\") {\n typeInfo.number += 1\n typeInfo.integer += 1\n } else if (typeof value === \"boolean\") {\n typeInfo.boolean += 1\n } else if (value instanceof Date) {\n typeInfo.date += 1\n } else if (typeof value === \"string\" && this.looksLikeTimestamp(value)) {\n typeInfo.date += 1\n } else {\n typeInfo.string += 1\n }\n }\n\n if (typeInfo.number === values.length) {\n columnTypes[key] = typeInfo.integer === values.length ? \"BIGINT\" : \"DOUBLE\"\n } else if (typeInfo.boolean === values.length) {\n columnTypes[key] = \"BOOLEAN\"\n } else if (typeInfo.date === values.length) {\n columnTypes[key] = \"TIMESTAMP\"\n } else {\n columnTypes[key] = \"VARCHAR\"\n }\n }\n\n return columnTypes\n }\n\n /**\n * Format value for SQL insertion.\n * SQL 挿入用に値をフォーマットします。\n */\n private formatValueForSQL(value: unknown): string {\n if (value === null || value === undefined) {\n return \"NULL\"\n }\n if (typeof value === \"string\") {\n return `'${value.replace(/'/g, \"''\")}'`\n }\n if (value instanceof Date) {\n return `'${value.toISOString()}'`\n }\n if (typeof value === \"boolean\") {\n return value ? \"TRUE\" : \"FALSE\"\n }\n if (typeof value === \"bigint\") {\n return value.toString()\n }\n if (typeof value === \"number\") {\n // NaN / Infinity / -Infinity は有効な SQL 数値リテラルではないため NULL 化する。\n if (!Number.isFinite(value)) {\n return \"NULL\"\n }\n return String(value)\n }\n // オブジェクト / 配列などは JSON 文字列としてクォート・エスケープする。\n try {\n const json = JSON.stringify(value)\n if (json === undefined) {\n return \"NULL\"\n }\n return `'${json.replace(/'/g, \"''\")}'`\n } catch {\n return \"NULL\"\n }\n }\n\n /**\n * Quote a SQL identifier (table / column / primary key) safely.\n * SQL 識別子を安全にクォートします(内部のダブルクォートはエスケープ)。\n */\n private quoteIdentifier(name: string): string {\n if (typeof name !== \"string\" || name.length === 0) {\n throw new Error(\"Invalid SQL identifier\")\n }\n return `\"${name.replace(/\"/g, '\"\"')}\"`\n }\n\n /**\n * Create table from JavaScript array of objects.\n * JavaScript オブジェクトの配列からテーブルを作成します。\n *\n * 同名テーブルへの同時呼び出しは、進行中の同一 Promise を await する(早期 void 解決しない)。\n */\n public async createTableFromData<T extends Record<string, unknown>>(\n tableName: string,\n data: T[],\n options: {\n dropIfExists?: boolean\n primaryKey?: string\n verbose?: boolean\n } = {},\n ): Promise<void> {\n if (!data || data.length === 0) {\n throw new Error(\"Data array is empty\")\n }\n\n const operationKey = `create_table_${tableName}`\n const verbose = options.verbose ?? true\n\n const inFlight = this.operationFlags.get(operationKey)\n if (inFlight) {\n if (verbose) {\n Logger.info(`⏳ Table creation for '${tableName}' is already in progress, awaiting existing operation...`)\n }\n return inFlight\n }\n\n const operation = this.enqueue(() => this.buildTable(tableName, data, options, verbose))\n this.operationFlags.set(operationKey, operation)\n try {\n await operation\n } finally {\n this.operationFlags.delete(operationKey)\n }\n }\n\n /** Actual (serialized, transactional) table build. */\n private async buildTable<T extends Record<string, unknown>>(tableName: string, data: T[], options: { dropIfExists?: boolean; primaryKey?: string }, verbose: boolean): Promise<void> {\n try {\n const connection = await this.getConnection()\n const quotedTable = this.quoteIdentifier(tableName)\n\n const columnNames = this.collectColumnNames(data)\n if (columnNames.length === 0) {\n throw new Error(\"Data rows have no columns\")\n }\n\n const sampleSize = Math.min(data.length, TYPE_INFERENCE_SAMPLE_SIZE)\n const columnTypes = this.inferColumnTypes(data.slice(0, sampleSize), columnNames)\n\n const columnDefs = columnNames.map((key) => `${this.quoteIdentifier(key)} ${columnTypes[key]}`)\n const primaryKeyClause = options.primaryKey ? `, PRIMARY KEY (${this.quoteIdentifier(options.primaryKey)})` : \"\"\n const createTableSQL = `CREATE TABLE ${quotedTable} (${columnDefs.join(\", \")}${primaryKeyClause})`\n const columnList = columnNames.map((col) => this.quoteIdentifier(col)).join(\", \")\n\n if (verbose) {\n Logger.info(`📥 Building table '${tableName}' with ${data.length} rows`)\n }\n\n // DROP / CREATE / INSERT を1トランザクションにまとめ、途中失敗時はロールバックして\n // 半端に作成されたテーブルを残さない。\n await connection.query(\"BEGIN TRANSACTION\")\n try {\n if (options.dropIfExists) {\n if (verbose) {\n Logger.info(`🗑️ Dropping table '${tableName}' if exists`)\n }\n await connection.query(`DROP TABLE IF EXISTS ${quotedTable}`)\n }\n\n if (verbose) {\n Logger.info(\"🔨 Creating table with SQL:\", createTableSQL)\n }\n await connection.query(createTableSQL)\n\n for (let i = 0; i < data.length; i += BATCH_SIZE) {\n const batch = data.slice(i, i + BATCH_SIZE)\n const valueRows = batch.map((row) => {\n // null/undefined 行はすべて NULL として扱う(不揃いなデータへの耐性)。\n const values = columnNames.map((col) => this.formatValueForSQL(row?.[col]))\n return `(${values.join(\", \")})`\n })\n\n const insertSQL = `INSERT INTO ${quotedTable} (${columnList}) VALUES ${valueRows.join(\", \")}`\n await connection.query(insertSQL)\n if (verbose) {\n Logger.info(`📊 Inserted batch ${Math.floor(i / BATCH_SIZE) + 1} (${batch.length} rows)`)\n }\n }\n\n await connection.query(\"COMMIT\")\n } catch (txError) {\n try {\n await connection.query(\"ROLLBACK\")\n } catch (rollbackError) {\n Logger.warn(\"⚠️ Rollback failed:\", rollbackError)\n }\n throw txError\n }\n\n if (verbose) {\n Logger.info(`🎉 Table '${tableName}' created successfully with ${data.length} rows`)\n }\n } catch (error) {\n Logger.error(`❌ Failed to create table '${tableName}':`, error)\n throw error\n }\n }\n\n /**\n * Get table information.\n * テーブル情報を取得します。\n */\n public async getTableInfo(tableName: string): Promise<unknown> {\n return this.enqueue(async () => {\n const connection = await this.getConnection()\n return connection.query(`DESCRIBE ${this.quoteIdentifier(tableName)}`)\n })\n }\n\n /**\n * List all tables.\n * すべてのテーブルを一覧表示します。\n */\n public async listTables(): Promise<unknown> {\n return this.enqueue(async () => {\n const connection = await this.getConnection()\n return connection.query(\"SHOW TABLES\")\n })\n }\n\n /**\n * Check if DuckDB is ready.\n * DuckDB が準備完了かどうかを確認します。\n */\n public isReady(): boolean {\n return this.workerInstance !== null\n }\n\n /**\n * Cleanup DuckDB Worker resources.\n * DuckDB Worker のリソースをクリーンアップします。\n */\n public async cleanup(): Promise<void> {\n try {\n if (this.connectionInstance) {\n await this.connectionInstance.close()\n this.connectionInstance = null\n }\n if (this.workerInstance) {\n await this.workerInstance.terminate()\n this.workerInstance = null\n }\n this.abortPendingWorker()\n this.isInitializing = false\n this.hasError = false\n this.initPromise = null\n this.connectionPromise = null\n this.operationQueue = Promise.resolve()\n this.operationFlags.clear()\n this.notifyListeners()\n this.listeners.clear()\n } catch (error) {\n Logger.error(\"⚠️ Error during cleanup:\", error)\n }\n }\n\n /**\n * Reset the service instance (useful for testing).\n * サービスインスタンスをリセットします(テスト用)。\n */\n public static resetInstance(): void {\n if (DuckDBService.instance) {\n void DuckDBService.instance.cleanup()\n DuckDBService.instance = null\n }\n }\n}\n\nexport { DuckDBService }\n\nexport default DuckDBService.getInstance()\n","/**\n * @module utils/logger\n * @description Logger utility for consistent log management across the library.\n * @description ライブラリ全体で一貫したログ管理を行うためのロガーユーティリティ。\n */\n\n/**\n * @enum LogLevel\n * @description Log levels for filtering output.\n * @description 出力をフィルタリングするためのログレベル。\n */\nexport enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n NONE = 4,\n}\n\n/**\n * @interface ILogger\n * @description Interface for a logger object compatible with Console.\n * @description Console と互換性のあるロガーオブジェクトのインターフェース。\n */\nexport interface ILogger {\n debug(message?: unknown, ...optionalParams: unknown[]): void\n info(message?: unknown, ...optionalParams: unknown[]): void\n warn(message?: unknown, ...optionalParams: unknown[]): void\n error(message?: unknown, ...optionalParams: unknown[]): void\n}\n\n/**\n * @class Logger\n * @description A wrapper class for handling logging with levels and prefixes.\n * @description レベルとプレフィックスを使用したログ記録を処理するためのラッパークラス。\n */\nexport class Logger implements ILogger {\n private level: LogLevel\n private prefix: string\n private impl: ILogger\n\n /**\n * @constructor\n * @param {LogLevel} [level=LogLevel.WARN] - The minimum log level to output.\n * @param {string} [prefix=\"[duckdb-helper]\"] - The prefix to add to all log messages.\n * @param {ILogger} [impl=console] - The implementation to use for logging.\n */\n constructor(level: LogLevel = LogLevel.WARN, prefix: string = \"[duckdb-helper]\", impl: ILogger = console) {\n this.level = level\n this.prefix = prefix\n this.impl = impl\n }\n\n private static instance: Logger = new Logger(LogLevel.WARN, \"[duckdb-helper]\")\n\n /**\n * @method setLevel\n * @description Updates the current log level for the static instance.\n * @description 静的インスタンスの現在のログレベルを更新します。\n * @param {LogLevel} level - The new log level.\n */\n static setLevel(level: LogLevel): void {\n Logger.instance.setLevel(level)\n }\n\n /**\n * @method setLevel\n * @description Updates the current log level.\n * @description 現在のログレベルを更新します。\n * @param {LogLevel} level - The new log level.\n */\n setLevel(level: LogLevel): void {\n this.level = level\n }\n\n /**\n * @method setImplementation\n * @description Updates the logger implementation for the static instance.\n * @description 静的インスタンスのロガーの実装を更新します。\n * @param {ILogger} impl - The new logger implementation.\n */\n static setImplementation(impl: ILogger): void {\n Logger.instance.setImplementation(impl)\n }\n\n /**\n * @method setImplementation\n * @description Updates the logger implementation.\n * @description ロガーの実装を更新します。\n * @param {ILogger} impl - The new logger implementation.\n */\n setImplementation(impl: ILogger): void {\n this.impl = impl\n }\n\n /**\n * @method setPrefix\n * @description Updates the log prefix for the static instance.\n * @description 静的インスタンスのログのプレフィックスを更新します。\n * @param {string} prefix - The new prefix.\n */\n static setPrefix(prefix: string): void {\n Logger.instance.setPrefix(prefix)\n }\n\n /**\n * @method setPrefix\n * @description Updates the log prefix.\n * @description ログのプレフィックスを更新します。\n * @param {string} prefix - The new prefix.\n */\n setPrefix(prefix: string): void {\n this.prefix = prefix\n }\n\n private formatMessage(message: unknown): unknown[] {\n if (typeof message === \"string\") {\n return [`${this.prefix} ${message}`]\n }\n return [this.prefix, message]\n }\n\n static debug(message?: unknown, ...optionalParams: unknown[]): void {\n Logger.instance.debug(message, ...optionalParams)\n }\n\n debug(message?: unknown, ...optionalParams: unknown[]): void {\n if (this.level <= LogLevel.DEBUG) {\n this.impl.debug(...this.formatMessage(message), ...optionalParams)\n }\n }\n\n static info(message?: unknown, ...optionalParams: unknown[]): void {\n Logger.instance.info(message, ...optionalParams)\n }\n\n info(message?: unknown, ...optionalParams: unknown[]): void {\n if (this.level <= LogLevel.INFO) {\n this.impl.info(...this.formatMessage(message), ...optionalParams)\n }\n }\n\n static warn(message?: unknown, ...optionalParams: unknown[]): void {\n Logger.instance.warn(message, ...optionalParams)\n }\n\n warn(message?: unknown, ...optionalParams: unknown[]): void {\n if (this.level <= LogLevel.WARN) {\n this.impl.warn(...this.formatMessage(message), ...optionalParams)\n }\n }\n\n static error(message?: unknown, ...optionalParams: unknown[]): void {\n Logger.instance.error(message, ...optionalParams)\n }\n\n error(message?: unknown, ...optionalParams: unknown[]): void {\n if (this.level <= LogLevel.ERROR) {\n this.impl.error(...this.formatMessage(message), ...optionalParams)\n }\n }\n}\n","/**\n * DuckDB Helper Functions\n *\n * Utility functions for working with DuckDB query results.\n * DuckDB クエリ結果を扱うためのユーティリティ関数。\n */\n\nimport type { DuckDBQueryResult } from \"../types/duckdb\"\n\n/**\n * Safely convert DuckDB table to typed array\n * DuckDB テーブルを型付き配列に安全に変換します\n *\n * @param result - DuckDB query result\n * @returns Typed array of results\n */\nexport const duckdbTableToArray = <T>(result: unknown): T[] => {\n if (!result || typeof result !== \"object\") {\n return []\n }\n\n if (\"toArray\" in result && typeof result.toArray === \"function\") {\n return (result as DuckDBQueryResult<T>).toArray()\n }\n\n return []\n}\n\n/**\n * Get table row count\n * テーブルの行数を取得します\n *\n * @param result - DuckDB query result\n * @returns Number of rows in the table\n */\nexport const getDuckDBRowCount = (result: unknown): number => {\n if (!result || typeof result !== \"object\") {\n return 0\n }\n\n if (\"numRows\" in result && typeof result.numRows === \"number\") {\n return result.numRows\n }\n\n return 0\n}\n\n/**\n * Get table column count\n * テーブルの列数を取得します\n *\n * @param result - DuckDB query result\n * @returns Number of columns in the table\n */\nexport const getDuckDBColumnCount = (result: unknown): number => {\n if (!result || typeof result !== \"object\") {\n return 0\n }\n\n if (\"numCols\" in result && typeof result.numCols === \"number\") {\n return result.numCols\n }\n\n return 0\n}\n\n/**\n * Check if result is a valid DuckDB table\n * 結果が有効な DuckDB テーブルかどうかをチェックします\n *\n * @param result - DuckDB query result\n * @returns True if result is a valid DuckDB table\n */\nexport const isDuckDBTable = (result: unknown): result is DuckDBQueryResult => {\n if (!result || typeof result !== \"object\") {\n return false\n }\n\n return \"toArray\" in result && typeof result.toArray === \"function\" && \"numRows\" in result && typeof result.numRows === \"number\"\n}\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useState","duckdb","LogLevel","_Logger","level","prefix","impl","message","optionalParams","Logger","INIT_TIMEOUT_MS","QUERY_TIMEOUT_MS","SPLINK_TIMEOUT_MS","BATCH_SIZE","TYPE_INFERENCE_SAMPLE_SIZE","TIMESTAMP_PATTERN","noop","_DuckDBService","listener","error","Logger","task","run","signal","timeoutId","timeoutPromise","_","reject","result","workerUrl","worker","JSDELIVR_BUNDLES","bundle","workerName","moduleName","pthreadWorkerName","localBundle","logger","db","connection","splinkTimeoutId","splinkTimeoutPromise","sql","timedOut","stale","queries","results","query","rows","seen","names","row","key","value","sample","columnNames","columnTypes","values","val","typeInfo","json","name","tableName","data","options","operationKey","verbose","inFlight","operation","quotedTable","sampleSize","columnDefs","primaryKeyClause","createTableSQL","columnList","col","i","batch","valueRows","insertSQL","txError","rollbackError","DuckDBService","duckdb_default","DuckDBService","useDependencyVersion","dependencies","ref","useRef","previous","dep","index","useDuckDB","autoInitialize","status","setStatus","useState","error","setError","duckDBService","DuckDBService","updateStatus","useCallback","serviceStatus","Logger","useEffect","initialize","err","errorMessage","executeQuery","sql","executeQueries","queries","createTableFromData","tableName","data","options","getTableInfo","listTables","useDuckDBQuery","isReady","setData","loading","setLoading","refetch","result","dependenciesVersion","duckdbTableToArray","result","getDuckDBRowCount","getDuckDBColumnCount","isDuckDBTable"]}
@@ -61,7 +61,7 @@ pnpm run check:fix && pnpm run type-check
61
61
 
62
62
  ## 3. ディレクトリ構成と役割
63
63
 
64
- ```
64
+ ```text
65
65
  src/
66
66
  ├── index.ts # エントリーポイント
67
67
  │ # → 公開 API をここで管理する
@@ -147,7 +147,7 @@ const DataAnalysis = () => {
147
147
 
148
148
  DuckDB の初期化には以下のステップがあり、**初回は数秒かかります**。
149
149
 
150
- ```
150
+ ```text
151
151
  1. CDN から WASM バンドルをダウンロード
152
152
  2. ブラウザに最適なバンドルを自動選択
153
153
  3. Web Worker を作成
@@ -243,6 +243,7 @@ const LazyInit = () => {
243
243
  **原因**: ブラウザが Web Worker または WASM をサポートしていない、もしくはネットワークが CDN にアクセスできない。
244
244
 
245
245
  **対処**:
246
+
246
247
  1. ブラウザの DevTools コンソールでエラーを確認
247
248
  2. jsDelivr CDN(`cdn.jsdelivr.net`)へのアクセスが許可されているか確認
248
249
  3. CORS やプロキシの設定を確認
@@ -252,6 +253,7 @@ const LazyInit = () => {
252
253
  **原因**: 初期化が完了する前にクエリを実行しようとしている。
253
254
 
254
255
  **対処**:
256
+
255
257
  ```tsx
256
258
  // isReady をチェックしてから実行
257
259
  if (isReady) {
@@ -262,6 +264,7 @@ if (isReady) {
262
264
  ### Q: 大量データの投入が遅い
263
265
 
264
266
  **対処**:
267
+
265
268
  - `createTableFromData` は内部で1000行ずつバッチ INSERT する
266
269
  - データが非常に大きい場合は、CSV/Parquet ファイルを直接読み込む DuckDB のネイティブ機能の使用を検討
267
270
 
@@ -9,14 +9,14 @@
9
9
 
10
10
  | ファイルパス | 変更箇所数 | 役割 |
11
11
  | --- | --- | --- |
12
- | [duckdb.ts](../../src/services/duckdb.ts) | 2 | `DuckDBService` の本体定義。 `splink_udfs` インストール処理への個別タイムアウト設定を追加。 |
13
- | [duckdb.test.ts](../../tests/services/duckdb.test.ts) | 1 | `DuckDBService` のテスト。 `splink_udfs` のインストール処理がハングしてタイムアウトしても初期化が正常に完了することを確認するテストを追加。 |
12
+ | [duckdb.ts](../../src/services/duckdb.ts) | 2 | `DuckDBService` の本体定義。 `splink_udfs` インストール処理への個別タイムアウト設定を追加。 |
13
+ | [duckdb.test.ts](../../tests/services/duckdb.test.ts) | 1 | `DuckDBService` のテスト。 `splink_udfs` のインストール処理がハングしてタイムアウトしても初期化が正常に完了することを確認するテストを追加。 |
14
14
 
15
15
  ## Per-file Change Details
16
16
 
17
17
  ### [duckdb.ts](../../src/services/duckdb.ts)
18
18
 
19
- - `SPLINK_TIMEOUT_MS` の追加(約 [duckdb.ts:15 周辺](../../src/services/duckdb.ts)):
19
+ - `SPLINK_TIMEOUT_MS` の追加(約 [duckdb.ts:15 周辺](../../src/services/duckdb.ts)):
20
20
 
21
21
  ```typescript
22
22
  /** クエリ実行タイムアウト。 */
@@ -27,7 +27,7 @@ const SPLINK_TIMEOUT_MS = 10000
27
27
  const BATCH_SIZE = 1000
28
28
  ```
29
29
 
30
- - `internalInitializeWorker()` 内の `splink_udfs` インストール処理(約 [duckdb.ts:245 周辺](../../src/services/duckdb.ts))の変更:
30
+ - `internalInitializeWorker()` 内の `splink_udfs` インストール処理(約 [duckdb.ts:245 周辺](../../src/services/duckdb.ts))の変更:
31
31
 
32
32
  ```diff
33
33
  Logger.info("📦 Installing splink_udfs from community...")
@@ -120,14 +120,19 @@ const BATCH_SIZE = 1000
120
120
  ## Build & Verify
121
121
 
122
122
  - テスト実行:
123
+
123
124
  ```bash
124
125
  pnpm --filter @aiquants/duckdb-helper test
125
126
  ```
127
+
126
128
  - ビルド実行:
129
+
127
130
  ```bash
128
131
  pnpm --filter @aiquants/duckdb-helper build
129
132
  ```
133
+
130
134
  - 型チェック:
135
+
131
136
  ```bash
132
137
  pnpm --filter @aiquants/duckdb-helper typecheck
133
138
  pnpm --filter @aiquants/duckdb-helper typecheck:test
@@ -5,7 +5,6 @@
5
5
  | 項目 | 内容 |
6
6
  | --- | --- |
7
7
  | パッケージ名 | `@aiquants/duckdb-helper` |
8
- | バージョン | 1.6.0 |
9
8
  | ライセンス | MIT |
10
9
  | 目的 | ブラウザ上で DuckDB-WASM を簡単に利用するためのヘルパーライブラリ |
11
10
  | 主な利用者 | React ベースの Web アプリケーション開発者 |
@@ -52,7 +51,7 @@ graph TD
52
51
 
53
52
  ## 4. ディレクトリ構成
54
53
 
55
- ```
54
+ ```text
56
55
  packages/duckdb-helper/
57
56
  ├── src/
58
57
  │ ├── index.ts # エントリーポイント(公開 API の定義)
@@ -138,11 +137,12 @@ flowchart TD
138
137
  F -- Yes --> G["既存の initPromise を返す(重複防止)"]
139
138
  F -- No --> H["1. jsDelivr CDN からバンドル一覧を取得"]
140
139
  H --> I["2. ブラウザ互換バンドルを自動選択"]
141
- I --> J["3. Blob URL で Web Worker を作成"]
142
- J --> K["4. AsyncDuckDB インスタンスを作成"]
143
- K --> L["5. WASM モジュールをインスタンス化"]
144
- L --> M["6. splink_udfs 拡張をインストール・ロード\n(失敗・10秒タイムアウトでも初期化は続行)"]
145
- M --> N["7. Blob URL を解放"]
140
+ I --> J["3. 解決した URL をローカル配信パス(/assets/duckdb/)に置換"]
141
+ J --> K["4. Blob URL で Web Worker を作成"]
142
+ K --> L["5. AsyncDuckDB インスタンスを作成"]
143
+ L --> M["6. WASM モジュールをインスタンス化"]
144
+ M --> N["7. splink_udfs 拡張をインストール・ロード\n(失敗・10秒タイムアウトでも初期化は続行)"]
145
+ N --> O["8. Blob URL を解放"]
146
146
  N --> O{結果}
147
147
  O -- 成功 --> P["workerInstance を公開 → status: ready"]
148
148
  O -- 失敗/タイムアウト --> Q["状態を完全リセット(workerInstance=null 等)\nWorker/Blob URL を破棄 → status: error(再試行可能)"]
@@ -169,7 +169,7 @@ flowchart TD
169
169
  | 判断事項 | 採用方針 | 理由 |
170
170
  | --- | --- | --- |
171
171
  | シングルトンパターン | 採用 | アプリ全体で WASM インスタンスを1つだけ保持し、メモリを節約 |
172
- | CDN からのバンドル取得 | jsDelivr を使用 | `@duckdb/duckdb-wasm` 公式推奨の配布方法 |
172
+ | ローカルアセットからのバンドル取得 | ローカル配信(/assets/duckdb/)を採用 | ネットワーク接続飽和の回避、複数タブ起動時の Web Locks 競合・タイムアウトの防止 |
173
173
  | Blob URL による Worker 作成 | 採用 | CORS 制約を回避し、CDN の Worker スクリプトをロード |
174
174
  | splink_udfs 拡張の自動ロード | 初期化時に実行 | 名寄せ(レコードリンケージ)機能を標準提供( 10 秒個別タイムアウトによりネットワークハング時も続行可能) |
175
175
  | 初期化タイムアウト | 120秒 | WASM ダウンロードを考慮した長めの設定 |
@@ -73,7 +73,7 @@ type UserRow = DuckDBRow<{
73
73
 
74
74
  ## 3. 型の関係図
75
75
 
76
- ```
76
+ ```text
77
77
  DuckDBQueryResult<T>
78
78
  │
79
79
  ├── toArray() → T[] (= DuckDBRow<T>[])
@@ -94,11 +94,12 @@ Worker の実際の初期化処理。**`workerInstance` は全ステップ成功
94
94
  | --- | --- | --- |
95
95
  | 1 | `duckdb.getJsDelivrBundles()` | jsDelivr CDN のバンドル一覧 |
96
96
  | 2 | `duckdb.selectBundle()` → **アボート確認** | ブラウザ互換バンドルを選択 |
97
- | 3 | Blob URL を作成し Web Worker を生成、`pendingWorker`/`pendingWorkerUrl` に退避 | 失敗時の破棄用 |
98
- | 4 | `new duckdb.AsyncDuckDB(logger, worker)`(ローカル変数 `db`) | まだ `this` に代入しない |
99
- | 5 | `db.instantiate()` → **アボート確認** | WASM モジュール読み込み |
100
- | 6 | `splink_udfs` を一時コネクションで INSTALL/LOAD。**10秒の個別タイムアウト(`SPLINK_TIMEOUT_MS`)を設定**し、タイムアウトや失敗時もログ警告を出した上で続行、`finally` で close。→ **アボート確認** | 名寄せ用 UDF |
101
- | 7 | **成功**: `this.workerInstance = db`、`pendingWorker`/`pendingWorkerUrl` をクリア、Blob URL を revoke | ここで初めて公開 |
97
+ | 3 | URL の置換処理 | 解決された CDN URL のファイル名部分のみを抽出し、ローカルの `/assets/duckdb/` パスにマッピング(接続飽和や複数タブ起動時の競合を回避) |
98
+ | 4 | Blob URL を作成し Web Worker を生成、`pendingWorker`/`pendingWorkerUrl` に退避 | 失敗時の破棄用 |
99
+ | 5 | `new duckdb.AsyncDuckDB(logger, worker)`(ローカル変数 `db`) | まだ `this` に代入しない |
100
+ | 6 | `db.instantiate()` → **アボート確認** | WASM モジュール読み込み |
101
+ | 7 | `splink_udfs` を一時コネクションで INSTALL/LOAD。**10秒の個別タイムアウト(`SPLINK_TIMEOUT_MS`)を設定**し、タイムアウトや失敗時もログ警告を出した上で続行、`finally` で close。→ **アボート確認** | 名寄せ用 UDF |
102
+ | 8 | **成功**: `this.workerInstance = db`、`pendingWorker`/`pendingWorkerUrl` をクリア、Blob URL を revoke | ここで初めて公開 |
102
103
  | 失敗時 | ローカル `worker.terminate()` と `URL.revokeObjectURL()` を**必ず**実行(各々 try/catch で握りつぶす)、エラーを rethrow | Worker/Blob URL リーク防止 |
103
104
 
104
105
  ---
@@ -123,6 +124,7 @@ Worker の実際の初期化処理。**`workerInstance` は全ステップ成功
123
124
  | 直列化 | `operationQueue` で他操作と直列化(テーブル構築中の割り込み read を防止) |
124
125
 
125
126
  **v1.6.0 での修正**:
127
+
126
128
  - タイムアウト用 `setTimeout` を成功/失敗どちらの経路でも `clearTimeout` する(旧実装はクエリ1回ごとに最大60秒残留するタイマーを生成していた)。
127
129
  - **タイムアウト時にコネクションを破棄する**: タイムアウトしたクエリはワーカー側で走り続けるため、使用中コネクションを `connectionInstance`/`connectionPromise` から外して close(best-effort)する。これにより後続クエリが詰まったコネクションで待たされず、次回は新しいコネクションで実行される(内部の `runQueryWithTimeout` が担当)。コネクション取得(`getConnection`)自体がハングした場合もタイムアウトが働き、破棄対象が無ければ何もしない。
128
130
 
@@ -197,6 +199,7 @@ flowchart TD
197
199
  | その他 / 混在 | `VARCHAR` | 上記以外、値がすべて null、型が混在する場合のフォールバック |
198
200
 
199
201
  > **v1.6.0 での修正**:
202
+ >
200
203
  > - 整数は `INTEGER` ではなく **`BIGINT`** を採用し、int32 を超える値でのオーバーフローを解消。
201
204
  > - TIMESTAMP 推定を厳密化。旧実装の「`new Date()` が有効 かつ `"-"` を含む」は緩すぎ、`ABC-123` のような文字列まで日付扱いして後続行のキャスト失敗を招いた。現在は厳密な日付/日時パターンに一致する場合のみ TIMESTAMP。
202
205
  > - サンプルは実際に先頭 100 行を使用(旧実装は「100 行」と称しつつ内部で 10 行に絞る二重スライスのバグがあった)。
@@ -67,7 +67,7 @@ export interface UseDuckDBResult {
67
67
 
68
68
  `useDuckDB` は**ステータスリスナー(push 型)のみ**で `DuckDBService` のステータスを React の状態に同期する。
69
69
 
70
- ```
70
+ ```text
71
71
  DuckDBService
72
72
  ├── addStatusListener(updateStatus) ← マウント時に登録し、updateStatus() を1回即時実行して初期値を取得
73
73
  └── removeStatusListener(updateStatus) ← アンマウント時に解除
@@ -117,7 +117,7 @@ export default defineConfig({
117
117
 
118
118
  | スクリプト | コマンド | 説明 |
119
119
  | --- | --- | --- |
120
- | `build` | `tsup` | ビルド実行 |
120
+ | `build` | `tsup && node scripts/copy-assets.js` | ビルド実行および DuckDB-Wasm のアセットを dist/assets にコピー |
121
121
  | `build:watch` | `tsup --watch` | ファイル変更時に自動リビルド |
122
122
  | `dev` | `tsup --watch` | 開発用(build:watch と同一) |
123
123
  | `watch` | `tsup --watch` | ウォッチモード |
@@ -168,14 +168,19 @@ npm に公開されるファイル。`src/` はパッケージに含まれない
168
168
 
169
169
  ## 5. ビルド成果物の構成
170
170
 
171
- ```
171
+ ```text
172
172
  dist/
173
173
  ├── index.js # CommonJS バンドル(require() 用)
174
174
  ├── index.mjs # ESM バンドル(import 用)
175
175
  ├── index.d.ts # TypeScript 型定義
176
176
  ├── index.d.mts # ESM 用 TypeScript 型定義
177
177
  ├── index.js.map # CommonJS ソースマップ
178
- └── index.mjs.map # ESM ソースマップ
178
+ ├── index.mjs.map # ESM ソースマップ
179
+ └── assets/ # 同梱される DuckDB-Wasm アセット
180
+ ├── duckdb-mvp.wasm
181
+ ├── duckdb-browser-mvp.worker.js
182
+ ├── duckdb-eh.wasm
183
+ └── duckdb-browser-eh.worker.js
179
184
  ```
180
185
 
181
186
  ## 6. 新人向けポイント
@@ -186,3 +191,4 @@ dist/
186
191
  4. **`external` に指定されたパッケージはバンドルに含まれない**。利用側でインストールが必要。
187
192
  5. **ビルドは tsup(esbuild ベース)が行い、tsc は型チェック専用**。この分業により高速ビルドと厳密な型チェックを両立。
188
193
  6. **`src/` は npm パッケージに含まれない**。利用者が見るのは `dist/` のビルド済みコードのみ。
194
+ 7. **WASM/Worker アセットのローカル自動配信**: パッケージ同梱された DuckDB-Wasm アセットは、フロントエンドアプリ側の起動・ビルドプロセス(Vite プラグイン)により `public/assets/duckdb/` に自動コピーされ、ローカル配信( `/assets/duckdb/...` )として利用される。これによりネットワーク競合やデッドロックが防止される。
@@ -1,6 +1,6 @@
1
1
  # テスト仕様書
2
2
 
3
- > 対象: `@aiquants/duckdb-helper` v1.6.3
3
+ > 対象: `@aiquants/duckdb-helper`
4
4
  > テストソース: `tests/**`
5
5
  > 更新: 2026-07-22(v1.6.3 の splink_udfs インストールタイムアウトテスト追加に伴う更新)
6
6
 
@@ -166,7 +166,7 @@ DuckDB-WASM は実際の Worker と WASM ダウンロードを伴うため、単
166
166
 
167
167
  ## 6. カバレッジ結果
168
168
 
169
- `pnpm run test:coverage` の結果(v1.6.0 時点、88 テスト):
169
+ `pnpm run test:coverage` の結果(6 ファイル / 89 テスト):
170
170
 
171
171
  | File | % Stmts | % Branch | % Funcs | % Lines |
172
172
  | --- | --- | --- | --- | --- |
@@ -183,3 +183,4 @@ DuckDB-WASM は実際の Worker と WASM ダウンロードを伴うため、単
183
183
 
184
184
  - 本テストは**単体テスト**であり、実 DuckDB-WASM / 実 Worker を用いた結合は対象外(実 SQL の意味的正しさ、例えば `BEGIN/COMMIT` のトランザクション意味論は DuckDB 側の責務)。
185
185
  - `types/duckdb.ts` は型のみでランタイムコードを持たないためカバレッジ対象から除外。
186
+ - **アセットコピーとローカル解決の検証**: パッケージ同梱用スクリプト( `copy-assets.js` )の動作や Vite プラグイン( `copyDuckDBAssetsPlugin` )によるコピー処理は、単体テストのモック環境ではなく、パッケージのビルド検証ステップ( `pnpm run build` )およびアプリ側のビルド検証において、 `dist/assets` や `public/assets/duckdb` への実アセット配置状況をファイルチェックすることで検証する。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiquants/duckdb-helper",
3
- "version": "1.6.3",
3
+ "version": "1.6.5",
4
4
  "description": "DuckDB helper utilities with React hooks, worker initialization, and typed query helpers",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -69,16 +69,16 @@
69
69
  "access": "public"
70
70
  },
71
71
  "scripts": {
72
- "build": "tsup",
72
+ "build": "tsup && node scripts/copy-assets.js",
73
73
  "build:watch": "tsup --watch",
74
74
  "dev": "tsup --watch",
75
75
  "watch": "tsup --watch",
76
76
  "typecheck": "tsc --noEmit",
77
77
  "typecheck:test": "tsc --noEmit -p tsconfig.test.json",
78
78
  "clean": "rimraf dist",
79
- "publish:patch": "pnpm version patch --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
80
- "publish:minor": "pnpm version minor --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
81
- "publish:major": "pnpm version major --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
79
+ "publish:patch": "pnpm run typecheck && pnpm run --if-present test && pnpm version patch --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
80
+ "publish:minor": "pnpm run typecheck && pnpm run --if-present test && pnpm version minor --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
81
+ "publish:major": "pnpm run typecheck && pnpm run --if-present test && pnpm version major --no-git-tag-version --no-git-checks && pnpm publish --no-git-checks",
82
82
  "lint": "biome lint src/",
83
83
  "lint:fix": "biome lint --write src/",
84
84
  "format": "biome format src/",