@aiquants/duckdb-helper 1.6.2 → 1.6.3
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.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/docs/guides/2026.04.11 [AI] development.md +4 -4
- package/docs/plans/2026.07.22 [AI] duckdb-init-timeout-bugfix-plan.md +134 -0
- package/docs/specs/2026.04.11 [AI] 00-overview.md +8 -8
- package/docs/specs/2026.04.11 [AI] 01-types.md +3 -3
- package/docs/specs/2026.04.11 [AI] 02-service.md +13 -13
- package/docs/specs/2026.04.11 [AI] 03-hooks.md +6 -6
- package/docs/specs/2026.04.11 [AI] 04-utils.md +6 -6
- package/docs/specs/2026.04.11 [AI] 05-logger.md +7 -7
- package/docs/specs/2026.04.11 [AI] 06-build-config.md +7 -7
- package/docs/specs/2026.07.05 [AI] 07-testing.md +11 -10
- package/package.json +4 -4
package/dist/index.mjs.map
CHANGED
|
@@ -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/** バッチ 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 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 } catch (error) {\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","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","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 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"]}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
### 前提条件
|
|
8
8
|
|
|
9
9
|
| ツール | バージョン |
|
|
10
|
-
|
|
10
|
+
| --- | --- |
|
|
11
11
|
| Node.js | >= 16.0.0 |
|
|
12
12
|
| pnpm | >= 8.0.0 |
|
|
13
13
|
|
|
@@ -32,7 +32,7 @@ pnpm run type-check
|
|
|
32
32
|
### 日常的なコマンド
|
|
33
33
|
|
|
34
34
|
| やりたいこと | コマンド |
|
|
35
|
-
|
|
35
|
+
| --- | --- |
|
|
36
36
|
| ビルド | `pnpm run build` |
|
|
37
37
|
| ウォッチモードで開発 | `pnpm run dev` |
|
|
38
38
|
| 型チェック | `pnpm run type-check` |
|
|
@@ -92,7 +92,7 @@ src/
|
|
|
92
92
|
### ファイルの責務境界
|
|
93
93
|
|
|
94
94
|
| 変更したい内容 | 編集するファイル |
|
|
95
|
-
|
|
95
|
+
| --- | --- |
|
|
96
96
|
| DuckDB の初期化方法を変えたい | `services/duckdb.ts` の `internalInitializeWorker()` |
|
|
97
97
|
| 新しい DuckDB 拡張を追加したい | `services/duckdb.ts` の `internalInitializeWorker()` |
|
|
98
98
|
| クエリ実行のタイムアウトを変更したい | `services/duckdb.ts` の `executeQuery()` 内の `TIMEOUT_MS` |
|
|
@@ -184,7 +184,7 @@ pnpm run type-check && pnpm run test && pnpm run build
|
|
|
184
184
|
### テストの分類
|
|
185
185
|
|
|
186
186
|
| ディレクトリ | 種類 | 説明 |
|
|
187
|
-
|
|
187
|
+
| --- | --- | --- |
|
|
188
188
|
| `tests/unit/` | 単体テスト | 個々の関数・クラスのテスト |
|
|
189
189
|
| `tests/integration/` | 統合テスト | 複数モジュールの連携テスト |
|
|
190
190
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Bugfix Plan — DuckDB Initialization Timeout
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
`duckdb-helper` の初期化処理( `DuckDBService.initialize()` )において、 `splink_udfs` 拡張機能のインストール( `INSTALL splink_udfs FROM community;` )がネットワークの遮断やプロキシの影響でハングした場合に、初期化処理全体が 120 秒のタイムアウト( `INIT_TIMEOUT_MS` )で失敗するのを防ぎます。
|
|
6
|
+
`splink_udfs` のインストール処理に対して個別の短いタイムアウト( 10 秒)を設け、万が一ハングした場合でも、警告ログを出力した上で、DuckDB 本体の初期化処理は正常に完了させます。
|
|
7
|
+
|
|
8
|
+
## Files to Change
|
|
9
|
+
|
|
10
|
+
| ファイルパス | 変更箇所数 | 役割 |
|
|
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` のインストール処理がハングしてタイムアウトしても初期化が正常に完了することを確認するテストを追加。 |
|
|
14
|
+
|
|
15
|
+
## Per-file Change Details
|
|
16
|
+
|
|
17
|
+
### [duckdb.ts](../../src/services/duckdb.ts)
|
|
18
|
+
|
|
19
|
+
- `SPLINK_TIMEOUT_MS` の追加(約 [duckdb.ts:15 周辺](../../src/services/duckdb.ts)):
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
/** クエリ実行タイムアウト。 */
|
|
23
|
+
const QUERY_TIMEOUT_MS = 60000
|
|
24
|
+
/** splink_udfs インストールの個別タイムアウト。 */
|
|
25
|
+
const SPLINK_TIMEOUT_MS = 10000
|
|
26
|
+
/** バッチ INSERT の1回あたり行数。 */
|
|
27
|
+
const BATCH_SIZE = 1000
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- `internalInitializeWorker()` 内の `splink_udfs` インストール処理(約 [duckdb.ts:245 周辺](../../src/services/duckdb.ts))の変更:
|
|
31
|
+
|
|
32
|
+
```diff
|
|
33
|
+
Logger.info("📦 Installing splink_udfs from community...")
|
|
34
|
+
const connection = await db.connect()
|
|
35
|
+
try {
|
|
36
|
+
- await connection.query("INSTALL splink_udfs FROM community;")
|
|
37
|
+
- Logger.info("✅ splink_udfs installed successfully")
|
|
38
|
+
-
|
|
39
|
+
- await connection.query("LOAD splink_udfs;")
|
|
40
|
+
- Logger.info("✅ splink_udfs loaded successfully")
|
|
41
|
+
+ // splink_udfs のインストール/ロードはネットワークアクセスを伴うため、
|
|
42
|
+
+ // 個別の短いタイムアウトを設けてハングを防止する。
|
|
43
|
+
+ let splinkTimeoutId!: ReturnType<typeof setTimeout>
|
|
44
|
+
+ const splinkTimeoutPromise = new Promise<never>((_, reject) => {
|
|
45
|
+
+ splinkTimeoutId = setTimeout(() => {
|
|
46
|
+
+ reject(new Error(`splink_udfs installation timed out after ${SPLINK_TIMEOUT_MS}ms`))
|
|
47
|
+
+ }, SPLINK_TIMEOUT_MS)
|
|
48
|
+
+ })
|
|
49
|
+
+
|
|
50
|
+
+ await Promise.race([
|
|
51
|
+
+ (async () => {
|
|
52
|
+
+ await connection.query("INSTALL splink_udfs FROM community;")
|
|
53
|
+
+ Logger.info("✅ splink_udfs installed successfully")
|
|
54
|
+
+
|
|
55
|
+
+ await connection.query("LOAD splink_udfs;")
|
|
56
|
+
+ Logger.info("✅ splink_udfs loaded successfully")
|
|
57
|
+
+ })(),
|
|
58
|
+
+ splinkTimeoutPromise
|
|
59
|
+
+ ])
|
|
60
|
+
+ clearTimeout(splinkTimeoutId)
|
|
61
|
+
} catch (error) {
|
|
62
|
+
+ // タイムアウトやネットワークエラーの場合は警告ログを出して続行
|
|
63
|
+
Logger.warn("⚠️ Failed to install/load splink_udfs:", error)
|
|
64
|
+
} finally {
|
|
65
|
+
await connection.close()
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### [duckdb.test.ts](../../tests/services/duckdb.test.ts)
|
|
72
|
+
|
|
73
|
+
- テストケース `continues initialization even if splink_udfs install hangs and times out` の追加(約 [duckdb.test.ts:180 周辺](../../tests/services/duckdb.test.ts)):
|
|
74
|
+
|
|
75
|
+
```diff
|
|
76
|
+
it("continues initialization even if splink_udfs install fails", async () => {
|
|
77
|
+
h.query = async (sql: string) => {
|
|
78
|
+
if (sql.includes("splink_udfs")) {
|
|
79
|
+
throw new Error("no community extension")
|
|
80
|
+
}
|
|
81
|
+
return { sql }
|
|
82
|
+
}
|
|
83
|
+
await expect(svc.initialize()).resolves.toBeDefined()
|
|
84
|
+
expect(svc.getStatus()).toBe("ready")
|
|
85
|
+
expect(h.connCloseCount).toBe(1) // finally で必ず close
|
|
86
|
+
})
|
|
87
|
+
+
|
|
88
|
+
+ it("continues initialization even if splink_udfs install hangs and times out", async () => {
|
|
89
|
+
+ vi.useFakeTimers()
|
|
90
|
+
+ h.query = async (sql: string) => {
|
|
91
|
+
+ if (sql.includes("splink_udfs")) {
|
|
92
|
+
+ return new Promise<never>(() => {}) // 解決しない
|
|
93
|
+
+ }
|
|
94
|
+
+ return { sql }
|
|
95
|
+
+ }
|
|
96
|
+
+
|
|
97
|
+
+ const p = svc.initialize()
|
|
98
|
+
+
|
|
99
|
+
+ // 非同期の初期化キューが進むようにマクロタスクを実行
|
|
100
|
+
+ await vi.advanceTimersByTimeAsync(0)
|
|
101
|
+
+
|
|
102
|
+
+ // 10秒進めて splink_udfs のタイムアウトを発生させる
|
|
103
|
+
+ await vi.advanceTimersByTimeAsync(SPLINK_TIMEOUT_MS)
|
|
104
|
+
+
|
|
105
|
+
+ // 初期化自体は成功裏に解決する
|
|
106
|
+
+ await expect(p).resolves.toBeDefined()
|
|
107
|
+
+ expect(svc.getStatus()).toBe("ready")
|
|
108
|
+
+ expect(h.connCloseCount).toBe(1) // コネクションが閉じられていること
|
|
109
|
+
+ })
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Checklist
|
|
113
|
+
|
|
114
|
+
- [ ] `packages/duckdb-helper/src/services/duckdb.ts` に定数 `SPLINK_TIMEOUT_MS` を追加
|
|
115
|
+
- [ ] `internalInitializeWorker()` 内の `splink_udfs` インストール処理にタイムアウト処理を適用
|
|
116
|
+
- [ ] `packages/duckdb-helper/tests/services/duckdb.test.ts` にハング時のタイムアウトテストを追加
|
|
117
|
+
- [ ] ローカルビルドが正常に完了することを確認
|
|
118
|
+
- [ ] テストスイートがすべてパスすることを確認
|
|
119
|
+
|
|
120
|
+
## Build & Verify
|
|
121
|
+
|
|
122
|
+
- テスト実行:
|
|
123
|
+
```bash
|
|
124
|
+
pnpm --filter @aiquants/duckdb-helper test
|
|
125
|
+
```
|
|
126
|
+
- ビルド実行:
|
|
127
|
+
```bash
|
|
128
|
+
pnpm --filter @aiquants/duckdb-helper build
|
|
129
|
+
```
|
|
130
|
+
- 型チェック:
|
|
131
|
+
```bash
|
|
132
|
+
pnpm --filter @aiquants/duckdb-helper typecheck
|
|
133
|
+
pnpm --filter @aiquants/duckdb-helper typecheck:test
|
|
134
|
+
```
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
## 1. パッケージ概要
|
|
4
4
|
|
|
5
5
|
| 項目 | 内容 |
|
|
6
|
-
|
|
6
|
+
| --- | --- |
|
|
7
7
|
| パッケージ名 | `@aiquants/duckdb-helper` |
|
|
8
8
|
| バージョン | 1.6.0 |
|
|
9
9
|
| ライセンス | MIT |
|
|
@@ -91,7 +91,7 @@ packages/duckdb-helper/
|
|
|
91
91
|
`src/index.ts` で公開されるすべてのエクスポート:
|
|
92
92
|
|
|
93
93
|
| カテゴリ | エクスポート名 | 種別 | 説明 |
|
|
94
|
-
|
|
94
|
+
| --- | --- | --- | --- |
|
|
95
95
|
| Hooks | `useDuckDB` | 関数 | DuckDB の状態管理と操作を提供する React Hook |
|
|
96
96
|
| Hooks | `useDuckDBQuery` | 関数 | SQL クエリの実行と結果管理を行う React Hook |
|
|
97
97
|
| Service | `DuckDBService` | クラス | DuckDB 操作のシングルトンサービス(名前付きエクスポート) |
|
|
@@ -112,13 +112,13 @@ packages/duckdb-helper/
|
|
|
112
112
|
### 本番依存(dependencies)
|
|
113
113
|
|
|
114
114
|
| パッケージ | バージョン | 用途 |
|
|
115
|
-
|
|
115
|
+
| --- | --- | --- |
|
|
116
116
|
| `@duckdb/duckdb-wasm` | ^1.30.0 | DuckDB の WASM 実装本体 |
|
|
117
117
|
|
|
118
118
|
### ピア依存(peerDependencies)- オプション
|
|
119
119
|
|
|
120
120
|
| パッケージ | バージョン | 用途 |
|
|
121
|
-
|
|
121
|
+
| --- | --- | --- |
|
|
122
122
|
| `react` | `^19.2.7`(catalog) | React Hooks を使う場合に必要 |
|
|
123
123
|
| `react-dom` | `^19.2.7`(catalog) | React Hooks を使う場合に必要 |
|
|
124
124
|
|
|
@@ -141,7 +141,7 @@ flowchart TD
|
|
|
141
141
|
I --> J["3. Blob URL で Web Worker を作成"]
|
|
142
142
|
J --> K["4. AsyncDuckDB インスタンスを作成"]
|
|
143
143
|
K --> L["5. WASM モジュールをインスタンス化"]
|
|
144
|
-
L --> M["6. splink_udfs 拡張をインストール・ロード\n
|
|
144
|
+
L --> M["6. splink_udfs 拡張をインストール・ロード\n(失敗・10秒タイムアウトでも初期化は続行)"]
|
|
145
145
|
M --> N["7. Blob URL を解放"]
|
|
146
146
|
N --> O{結果}
|
|
147
147
|
O -- 成功 --> P["workerInstance を公開 → status: ready"]
|
|
@@ -167,11 +167,11 @@ flowchart TD
|
|
|
167
167
|
## 8. 設計上の重要な判断
|
|
168
168
|
|
|
169
169
|
| 判断事項 | 採用方針 | 理由 |
|
|
170
|
-
|
|
170
|
+
| --- | --- | --- |
|
|
171
171
|
| シングルトンパターン | 採用 | アプリ全体で WASM インスタンスを1つだけ保持し、メモリを節約 |
|
|
172
172
|
| CDN からのバンドル取得 | jsDelivr を使用 | `@duckdb/duckdb-wasm` 公式推奨の配布方法 |
|
|
173
173
|
| Blob URL による Worker 作成 | 採用 | CORS 制約を回避し、CDN の Worker スクリプトをロード |
|
|
174
|
-
| splink_udfs 拡張の自動ロード | 初期化時に実行 |
|
|
174
|
+
| splink_udfs 拡張の自動ロード | 初期化時に実行 | 名寄せ(レコードリンケージ)機能を標準提供( 10 秒個別タイムアウトによりネットワークハング時も続行可能) |
|
|
175
175
|
| 初期化タイムアウト | 120秒 | WASM ダウンロードを考慮した長めの設定 |
|
|
176
176
|
| クエリタイムアウト | 60秒 | 長時間クエリによる UI フリーズを防止 |
|
|
177
177
|
| バッチインサート | 1000行単位 | 大量データ投入時のメモリ効率とパフォーマンスのバランス |
|
|
@@ -186,7 +186,7 @@ flowchart TD
|
|
|
186
186
|
## 9. 関連ドキュメント
|
|
187
187
|
|
|
188
188
|
| ドキュメント | パス | 内容 |
|
|
189
|
-
|
|
189
|
+
| --- | --- | --- |
|
|
190
190
|
| 型定義仕様書 | [01-types.md](./2026.04.11%20%5BAI%5D%2001-types.md) | 型定義の詳細 |
|
|
191
191
|
| サービス仕様書 | [02-service.md](./2026.04.11%20%5BAI%5D%2002-service.md) | DuckDBService の全メソッド仕様 |
|
|
192
192
|
| Hooks 仕様書 | [03-hooks.md](./2026.04.11%20%5BAI%5D%2003-hooks.md) | useDuckDB / useDuckDBQuery の仕様 |
|
|
@@ -20,7 +20,7 @@ export interface DuckDBQueryResult<T = unknown> {
|
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
| プロパティ/メソッド | 型 | 説明 |
|
|
23
|
-
|
|
23
|
+
| --- | --- | --- |
|
|
24
24
|
| `toArray()` | `T[]` | クエリ結果の全行を JavaScript 配列として返すメソッド |
|
|
25
25
|
| `numRows` | `number` | 結果の行数 |
|
|
26
26
|
| `numCols` | `number` | 結果の列数 |
|
|
@@ -50,7 +50,7 @@ export type DuckDBRow<T> = T
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
| 項目 | 内容 |
|
|
53
|
-
|
|
53
|
+
| --- | --- |
|
|
54
54
|
| 定義 | ジェネリクス `T` をそのまま返すエイリアス型 |
|
|
55
55
|
| 用途 | コード上の可読性向上。「この `T` は DuckDB の行データである」という意味を明示する |
|
|
56
56
|
|
|
@@ -86,7 +86,7 @@ DuckDBRow<T> = T ← ドキュメンテーション用エイリアス
|
|
|
86
86
|
## 4. 他モジュールとの関係
|
|
87
87
|
|
|
88
88
|
| 参照元ファイル | 参照する型 | 使われ方 |
|
|
89
|
-
|
|
89
|
+
| --- | --- | --- |
|
|
90
90
|
| `index.ts` | `DuckDBQueryResult`, `DuckDBRow` | 公開 API として re-export(型のみ) |
|
|
91
91
|
| `utils/duckdb-helpers.ts` | `DuckDBQueryResult` | `duckdbTableToArray` 内の型アサーション(`as DuckDBQueryResult<T>`)、`isDuckDBTable` の型ガード戻り値(`result is DuckDBQueryResult`)。なお `getDuckDBRowCount` / `getDuckDBColumnCount` は `DuckDBQueryResult` を参照せず、`unknown` 型の入力に対して `in` 演算子と `typeof` で直接プロパティを検査する |
|
|
92
92
|
| `hooks/useDuckDB.ts` | (直接参照なし) | フック内部では `unknown` 型で結果を返す |
|