@aiquants/duckdb-helper 1.4.0 → 1.5.0
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 +291 -0
- package/docs/guides/2026.04.11 [AI] getting-started.md +278 -0
- package/docs/specs/2026.04.11 [AI] 00-overview.md +177 -0
- package/docs/specs/2026.04.11 [AI] 01-types.md +101 -0
- package/docs/specs/2026.04.11 [AI] 02-service.md +387 -0
- package/docs/specs/2026.04.11 [AI] 03-hooks.md +298 -0
- package/docs/specs/2026.04.11 [AI] 04-utils.md +220 -0
- package/docs/specs/2026.04.11 [AI] 05-logger.md +188 -0
- package/docs/specs/2026.04.11 [AI] 06-build-config.md +170 -0
- package/package.json +29 -32
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# @aiquants/duckdb-helper 設計書 - 全体概要
|
|
2
|
+
|
|
3
|
+
## 1. パッケージ概要
|
|
4
|
+
|
|
5
|
+
| 項目 | 内容 |
|
|
6
|
+
|------|------|
|
|
7
|
+
| パッケージ名 | `@aiquants/duckdb-helper` |
|
|
8
|
+
| バージョン | 1.4.1 |
|
|
9
|
+
| ライセンス | MIT |
|
|
10
|
+
| 目的 | ブラウザ上で DuckDB-WASM を簡単に利用するためのヘルパーライブラリ |
|
|
11
|
+
| 主な利用者 | React ベースの Web アプリケーション開発者 |
|
|
12
|
+
|
|
13
|
+
## 2. このパッケージが解決する課題
|
|
14
|
+
|
|
15
|
+
DuckDB-WASM をブラウザで使う場合、以下のような複雑な手順が必要になる。
|
|
16
|
+
|
|
17
|
+
1. WASM バンドルの取得とブラウザ互換バンドルの選択
|
|
18
|
+
2. Web Worker の作成と DuckDB インスタンスの初期化
|
|
19
|
+
3. コネクション管理(作成・再利用・破棄)
|
|
20
|
+
4. クエリ結果(Apache Arrow Table)の JavaScript 配列への変換
|
|
21
|
+
5. React コンポーネントでの状態管理(初期化中・準備完了・エラー)
|
|
22
|
+
|
|
23
|
+
このパッケージは上記すべてをラップし、**数行のコードで DuckDB を利用可能**にする。
|
|
24
|
+
|
|
25
|
+
## 3. アーキテクチャ図
|
|
26
|
+
|
|
27
|
+
```mermaid
|
|
28
|
+
graph TD
|
|
29
|
+
subgraph App["利用側アプリケーション"]
|
|
30
|
+
direction TB
|
|
31
|
+
subgraph Hooks["React Hooks"]
|
|
32
|
+
useDuckDB["useDuckDB"]
|
|
33
|
+
useDuckDBQuery["useDuckDBQuery"]
|
|
34
|
+
end
|
|
35
|
+
subgraph Service["サービス層(シングルトン)"]
|
|
36
|
+
DuckDBService["DuckDBService"]
|
|
37
|
+
end
|
|
38
|
+
subgraph Utils["ユーティリティ層"]
|
|
39
|
+
Logger["Logger"]
|
|
40
|
+
helpers["helpers"]
|
|
41
|
+
types["types"]
|
|
42
|
+
end
|
|
43
|
+
useDuckDB --> DuckDBService
|
|
44
|
+
useDuckDBQuery --> DuckDBService
|
|
45
|
+
DuckDBService --> Logger
|
|
46
|
+
DuckDBService --> helpers
|
|
47
|
+
DuckDBService --> types
|
|
48
|
+
end
|
|
49
|
+
DuckDBService --> duckdb_wasm["@duckdb/duckdb-wasm\n(外部依存)"]
|
|
50
|
+
duckdb_wasm --> Worker["Web Worker\n(WASM / ブラウザランタイム)"]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 4. ディレクトリ構成
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
packages/duckdb-helper/
|
|
57
|
+
├── src/
|
|
58
|
+
│ ├── index.ts # エントリーポイント(公開 API の定義)
|
|
59
|
+
│ ├── hooks/
|
|
60
|
+
│ │ └── useDuckDB.ts # React Hooks(useDuckDB, useDuckDBQuery)
|
|
61
|
+
│ ├── services/
|
|
62
|
+
│ │ └── duckdb.ts # DuckDBService クラス(シングルトン)
|
|
63
|
+
│ ├── types/
|
|
64
|
+
│ │ └── duckdb.ts # 型定義(DuckDBQueryResult, DuckDBRow)
|
|
65
|
+
│ └── utils/
|
|
66
|
+
│ ├── duckdb-helpers.ts # ヘルパー関数群
|
|
67
|
+
│ └── simple-logger.ts # ログユーティリティ
|
|
68
|
+
├── tests/
|
|
69
|
+
│ └── integration/
|
|
70
|
+
│ └── dummy.test.ts # テスト(Vitest)
|
|
71
|
+
├── docs/
|
|
72
|
+
│ ├── spec/ # 設計仕様書
|
|
73
|
+
│ ├── guides/ # ガイド(入門・開発)
|
|
74
|
+
│ └── useDuckDB.md # API リファレンス
|
|
75
|
+
├── dist/ # ビルド成果物(自動生成)
|
|
76
|
+
├── package.json
|
|
77
|
+
├── tsconfig.json
|
|
78
|
+
└── tsup.config.ts # ビルド設定
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 5. 公開 API 一覧
|
|
82
|
+
|
|
83
|
+
`src/index.ts` で公開されるすべてのエクスポート:
|
|
84
|
+
|
|
85
|
+
| カテゴリ | エクスポート名 | 種別 | 説明 |
|
|
86
|
+
|----------|---------------|------|------|
|
|
87
|
+
| Hooks | `useDuckDB` | 関数 | DuckDB の状態管理と操作を提供する React Hook |
|
|
88
|
+
| Hooks | `useDuckDBQuery` | 関数 | SQL クエリの実行と結果管理を行う React Hook |
|
|
89
|
+
| Service | `DuckDBService` | クラス | DuckDB 操作のシングルトンサービス(名前付きエクスポート) |
|
|
90
|
+
| Service | `duckDBService` | インスタンス | `DuckDBService.getInstance()` の名前付きエクスポート(`services/duckdb.ts` のデフォルトエクスポートを re-export) |
|
|
91
|
+
| Types | `UseDuckDBResult` | 型 | `useDuckDB` の戻り値の型 |
|
|
92
|
+
| Types | `DuckDBQueryResult` | 型 | クエリ結果のインターフェース |
|
|
93
|
+
| Types | `DuckDBRow` | 型 | 行データのヘルパー型 |
|
|
94
|
+
| Utils | `duckdbTableToArray` | 関数 | クエリ結果を型付き配列に変換 |
|
|
95
|
+
| Utils | `getDuckDBRowCount` | 関数 | 結果の行数を取得 |
|
|
96
|
+
| Utils | `getDuckDBColumnCount` | 関数 | 結果の列数を取得 |
|
|
97
|
+
| Utils | `isDuckDBTable` | 関数 | 結果が有効な DuckDB テーブルか判定(型ガード) |
|
|
98
|
+
|
|
99
|
+
## 6. 依存関係
|
|
100
|
+
|
|
101
|
+
### 本番依存(dependencies)
|
|
102
|
+
|
|
103
|
+
| パッケージ | バージョン | 用途 |
|
|
104
|
+
|-----------|-----------|------|
|
|
105
|
+
| `@duckdb/duckdb-wasm` | ^1.30.0 | DuckDB の WASM 実装本体 |
|
|
106
|
+
|
|
107
|
+
### ピア依存(peerDependencies)- オプション
|
|
108
|
+
|
|
109
|
+
| パッケージ | バージョン | 用途 |
|
|
110
|
+
|-----------|-----------|------|
|
|
111
|
+
| `react` | >=18.0.0 | React Hooks を使う場合に必要 |
|
|
112
|
+
| `react-dom` | >=18.0.0 | React Hooks を使う場合に必要 |
|
|
113
|
+
|
|
114
|
+
> **ポイント**: React はオプショナルなピア依存。Hooks を使わずサービス層のみ利用する場合、React は不要。
|
|
115
|
+
|
|
116
|
+
## 7. データフロー
|
|
117
|
+
|
|
118
|
+
### 初期化フロー
|
|
119
|
+
|
|
120
|
+
```mermaid
|
|
121
|
+
flowchart TD
|
|
122
|
+
A["useDuckDB(autoInitialize=true)"] --> B["マウント時に initialize() を呼び出し"]
|
|
123
|
+
B --> C["DuckDBService.initialize()"]
|
|
124
|
+
C --> D{workerInstance が存在?}
|
|
125
|
+
D -- Yes --> E["workerInstance を返す"]
|
|
126
|
+
D -- No --> F{初期化中?}
|
|
127
|
+
F -- Yes --> G["既存の initPromise を返す(重複防止)"]
|
|
128
|
+
F -- No --> H["1. jsDelivr CDN からバンドル一覧を取得"]
|
|
129
|
+
H --> I["2. ブラウザ互換バンドルを自動選択"]
|
|
130
|
+
I --> J["3. Blob URL で Web Worker を作成"]
|
|
131
|
+
J --> K["4. AsyncDuckDB インスタンスを作成"]
|
|
132
|
+
K --> L["5. WASM モジュールをインスタンス化"]
|
|
133
|
+
L --> M["6. splink_udfs 拡張をインストール・ロード\n(失敗しても初期化は続行)"]
|
|
134
|
+
M --> N["7. Blob URL を解放"]
|
|
135
|
+
N --> O{結果}
|
|
136
|
+
O -- 成功 --> P["status: ready"]
|
|
137
|
+
O -- 失敗 --> Q["status: error"]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### クエリ実行フロー
|
|
141
|
+
|
|
142
|
+
```mermaid
|
|
143
|
+
flowchart TD
|
|
144
|
+
A["executeQuery(sql)"] --> B["getConnection()"]
|
|
145
|
+
B --> C{コネクションが存在?}
|
|
146
|
+
C -- No --> D["initialize() → connect()"]
|
|
147
|
+
C -- Yes --> E["既存コネクションを返す"]
|
|
148
|
+
D --> F["connection.query(sql) を実行"]
|
|
149
|
+
E --> F
|
|
150
|
+
F --> G["60秒タイムアウトで Promise.race"]
|
|
151
|
+
G --> H["Apache Arrow Table を返す"]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 8. 設計上の重要な判断
|
|
155
|
+
|
|
156
|
+
| 判断事項 | 採用方針 | 理由 |
|
|
157
|
+
|---------|---------|------|
|
|
158
|
+
| シングルトンパターン | 採用 | アプリ全体で WASM インスタンスを1つだけ保持し、メモリを節約 |
|
|
159
|
+
| CDN からのバンドル取得 | jsDelivr を使用 | `@duckdb/duckdb-wasm` 公式推奨の配布方法 |
|
|
160
|
+
| Blob URL による Worker 作成 | 採用 | CORS 制約を回避し、CDN の Worker スクリプトをロード |
|
|
161
|
+
| splink_udfs 拡張の自動ロード | 初期化時に実行 | 名寄せ(レコードリンケージ)機能を標準提供 |
|
|
162
|
+
| 初期化タイムアウト | 120秒 | WASM ダウンロードを考慮した長めの設定 |
|
|
163
|
+
| クエリタイムアウト | 60秒 | 長時間クエリによる UI フリーズを防止 |
|
|
164
|
+
| バッチインサート | 1000行単位 | 大量データ投入時のメモリ効率とパフォーマンスのバランス |
|
|
165
|
+
|
|
166
|
+
## 9. 関連ドキュメント
|
|
167
|
+
|
|
168
|
+
| ドキュメント | パス | 内容 |
|
|
169
|
+
|-------------|------|------|
|
|
170
|
+
| 型定義仕様書 | [01-types.md](./2026.04.11%20%5BAI%5D%2001-types.md) | 型定義の詳細 |
|
|
171
|
+
| サービス仕様書 | [02-service.md](./2026.04.11%20%5BAI%5D%2002-service.md) | DuckDBService の全メソッド仕様 |
|
|
172
|
+
| Hooks 仕様書 | [03-hooks.md](./2026.04.11%20%5BAI%5D%2003-hooks.md) | useDuckDB / useDuckDBQuery の仕様 |
|
|
173
|
+
| ユーティリティ仕様書 | [04-utils.md](./2026.04.11%20%5BAI%5D%2004-utils.md) | ヘルパー関数の仕様 |
|
|
174
|
+
| Logger 仕様書 | [05-logger.md](./2026.04.11%20%5BAI%5D%2005-logger.md) | ログユーティリティの仕様 |
|
|
175
|
+
| ビルド設定仕様書 | [06-build-config.md](./2026.04.11%20%5BAI%5D%2006-build-config.md) | tsup / TypeScript 設定の詳細 |
|
|
176
|
+
| 入門ガイド | [getting-started.md](../guides/2026.04.11%20%5BAI%5D%20getting-started.md) | 初めて使う人向けガイド |
|
|
177
|
+
| 開発ガイド | [development.md](../guides/2026.04.11%20%5BAI%5D%20development.md) | 開発・保守作業向けガイド |
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# 型定義仕様書
|
|
2
|
+
|
|
3
|
+
> ソースファイル: `src/types/duckdb.ts`
|
|
4
|
+
|
|
5
|
+
## 1. 概要
|
|
6
|
+
|
|
7
|
+
このファイルは、DuckDB のクエリ結果を TypeScript で安全に扱うための型定義を提供する。
|
|
8
|
+
DuckDB-WASM のクエリ結果は内部的に Apache Arrow Table 形式だが、このライブラリでは直接 Arrow に依存せず、必要最小限のインターフェースを定義している。
|
|
9
|
+
|
|
10
|
+
## 2. 型定義一覧
|
|
11
|
+
|
|
12
|
+
### 2.1 `DuckDBQueryResult<T>`
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
export interface DuckDBQueryResult<T = unknown> {
|
|
16
|
+
toArray(): T[]
|
|
17
|
+
numRows: number
|
|
18
|
+
numCols: number
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| プロパティ/メソッド | 型 | 説明 |
|
|
23
|
+
|-------------------|------|------|
|
|
24
|
+
| `toArray()` | `T[]` | クエリ結果の全行を JavaScript 配列として返すメソッド |
|
|
25
|
+
| `numRows` | `number` | 結果の行数 |
|
|
26
|
+
| `numCols` | `number` | 結果の列数 |
|
|
27
|
+
|
|
28
|
+
#### 設計意図
|
|
29
|
+
|
|
30
|
+
- **なぜ独自インターフェースか**: `@duckdb/duckdb-wasm` が返す Apache Arrow Table の型を直接使うと、Arrow ライブラリへの強い依存が発生する。この最小インターフェースにより、利用側は Arrow の知識なしに結果を扱える。
|
|
31
|
+
- **ジェネリクス `T`**: デフォルトは `unknown`。利用側が行の型を指定することで、`toArray()` の戻り値に型安全性を持たせられる。
|
|
32
|
+
|
|
33
|
+
#### 使用例
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// 型パラメータなし(unknown[] が返る)
|
|
37
|
+
const result: DuckDBQueryResult = await executeQuery("SELECT 1")
|
|
38
|
+
const rows = result.toArray() // unknown[]
|
|
39
|
+
|
|
40
|
+
// 型パラメータあり(型安全)
|
|
41
|
+
interface User { id: number; name: string }
|
|
42
|
+
const result: DuckDBQueryResult<User> = await executeQuery("SELECT * FROM users")
|
|
43
|
+
const rows = result.toArray() // User[]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2.2 `DuckDBRow<T>`
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
export type DuckDBRow<T> = T
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
| 項目 | 内容 |
|
|
53
|
+
|------|------|
|
|
54
|
+
| 定義 | ジェネリクス `T` をそのまま返すエイリアス型 |
|
|
55
|
+
| 用途 | コード上の可読性向上。「この `T` は DuckDB の行データである」という意味を明示する |
|
|
56
|
+
|
|
57
|
+
#### 設計意図
|
|
58
|
+
|
|
59
|
+
機能的には `T` と同一だが、コード上で「この型は DuckDB テーブルの1行を表す」という意図を伝える**ドキュメンテーション型**として存在する。
|
|
60
|
+
|
|
61
|
+
#### 使用例
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
type UserRow = DuckDBRow<{
|
|
65
|
+
id: number
|
|
66
|
+
name: string
|
|
67
|
+
email: string | null
|
|
68
|
+
}>
|
|
69
|
+
|
|
70
|
+
// 以下は同じ意味だが、DuckDBRow を使うと「DuckDB の行データ」という意図が明確
|
|
71
|
+
// type UserRow = { id: number; name: string; email: string | null }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 3. 型の関係図
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
DuckDBQueryResult<T>
|
|
78
|
+
│
|
|
79
|
+
├── toArray() → T[] (= DuckDBRow<T>[])
|
|
80
|
+
├── numRows: number
|
|
81
|
+
└── numCols: number
|
|
82
|
+
|
|
83
|
+
DuckDBRow<T> = T ← ドキュメンテーション用エイリアス
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 4. 他モジュールとの関係
|
|
87
|
+
|
|
88
|
+
| 参照元ファイル | 参照する型 | 使われ方 |
|
|
89
|
+
|--------------|-----------|---------|
|
|
90
|
+
| `index.ts` | `DuckDBQueryResult`, `DuckDBRow` | 公開 API として re-export(型のみ) |
|
|
91
|
+
| `utils/duckdb-helpers.ts` | `DuckDBQueryResult` | `duckdbTableToArray` 内の型アサーション(`as DuckDBQueryResult<T>`)、`isDuckDBTable` の型ガード戻り値(`result is DuckDBQueryResult`)。なお `getDuckDBRowCount` / `getDuckDBColumnCount` は `DuckDBQueryResult` を参照せず、`unknown` 型の入力に対して `in` 演算子と `typeof` で直接プロパティを検査する |
|
|
92
|
+
| `hooks/useDuckDB.ts` | (直接参照なし) | フック内部では `unknown` 型で結果を返す |
|
|
93
|
+
| `services/duckdb.ts` | (直接参照なし) | `executeQuery` の戻り値は `Promise<unknown>`(独立して `unknown` を採用) |
|
|
94
|
+
|
|
95
|
+
> **補足**: `DuckDBRow` は `index.ts` から re-export されているが、`src/` 内のどのモジュールでも実際には使用されていない。利用側アプリケーションでのドキュメンテーション用途を想定した型。
|
|
96
|
+
|
|
97
|
+
## 5. 新人向けポイント
|
|
98
|
+
|
|
99
|
+
1. **`DuckDBQueryResult` は「DuckDB の結果はこういう形ですよ」という約束事**。実際の結果は Apache Arrow Table だが、このインターフェースの3つのメンバーだけ知っていればOK。
|
|
100
|
+
2. **`DuckDBRow` は使っても使わなくても動作は同じ**。ただしチーム内で「これは DuckDB の行データ」と分かりやすくするために使う。
|
|
101
|
+
3. **型パラメータ `<T>` を指定すると、IDE の補完が効くようになる**。可能な限り具体的な型を指定しよう。
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# DuckDBService 詳細仕様書
|
|
2
|
+
|
|
3
|
+
> ソースファイル: `src/services/duckdb.ts`
|
|
4
|
+
|
|
5
|
+
## 1. 概要
|
|
6
|
+
|
|
7
|
+
`DuckDBService` は、ブラウザ上で DuckDB-WASM を操作するための**シングルトンサービスクラス**。
|
|
8
|
+
Web Worker の初期化、コネクション管理、SQL クエリの実行、テーブル操作をすべて担当する。
|
|
9
|
+
|
|
10
|
+
## 2. クラス設計
|
|
11
|
+
|
|
12
|
+
### 2.1 デザインパターン
|
|
13
|
+
|
|
14
|
+
- **シングルトンパターン**: `getInstance()` で唯一のインスタンスを取得する。アプリケーション全体で DuckDB Worker を1つだけ保持し、メモリとリソースを節約する。
|
|
15
|
+
- **オブザーバーパターン**: `addStatusListener` / `removeStatusListener` でステータス変更を監視できる。React Hook (`useDuckDB`) がこれを利用している。
|
|
16
|
+
|
|
17
|
+
### 2.2 プライベートプロパティ
|
|
18
|
+
|
|
19
|
+
| プロパティ名 | 型 | 初期値 | 説明 |
|
|
20
|
+
|-------------|------|--------|------|
|
|
21
|
+
| `instance` | `DuckDBService \| null` | `null` | シングルトンインスタンス(static) |
|
|
22
|
+
| `operationFlags` | `Map<string, boolean>` | 空の Map | テーブル作成の重複実行防止フラグ |
|
|
23
|
+
| `workerInstance` | `AsyncDuckDB \| null` | `null` | DuckDB Worker インスタンス |
|
|
24
|
+
| `connectionInstance` | `AsyncDuckDBConnection \| null` | `null` | DB コネクション(再利用される) |
|
|
25
|
+
| `isInitializing` | `boolean` | `false` | 初期化中フラグ |
|
|
26
|
+
| `initPromise` | `Promise<AsyncDuckDB> \| null` | `null` | 初期化 Promise(重複呼び出し防止用) |
|
|
27
|
+
| `hasError` | `boolean` | `false` | エラー発生フラグ |
|
|
28
|
+
| `listeners` | `Set<() => void>` | 空の Set | ステータス変更リスナー群 |
|
|
29
|
+
|
|
30
|
+
## 3. メソッド仕様
|
|
31
|
+
|
|
32
|
+
### 3.1 `getInstance(): DuckDBService` (static)
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
public static getInstance(): DuckDBService
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| 項目 | 内容 |
|
|
39
|
+
|------|------|
|
|
40
|
+
| 戻り値 | `DuckDBService` のシングルトンインスタンス |
|
|
41
|
+
| 副作用 | インスタンスが未作成の場合、新規作成する |
|
|
42
|
+
| スレッド安全性 | JavaScript はシングルスレッドなので問題なし |
|
|
43
|
+
|
|
44
|
+
**処理フロー**:
|
|
45
|
+
1. `instance` が `null` → 新しい `DuckDBService` を作成して `instance` に格納
|
|
46
|
+
2. `instance` を返す
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### 3.2 `getStatus(): string`
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
public getStatus(): "not-initialized" | "initializing" | "ready" | "error"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| ステータス | 条件 | 意味 |
|
|
57
|
+
|-----------|------|------|
|
|
58
|
+
| `"error"` | `hasError === true` | 初期化に失敗した(最優先で判定) |
|
|
59
|
+
| `"initializing"` | `isInitializing === true` | 初期化処理中 |
|
|
60
|
+
| `"ready"` | `workerInstance !== null` | 利用可能 |
|
|
61
|
+
| `"not-initialized"` | 上記いずれでもない | まだ初期化されていない |
|
|
62
|
+
|
|
63
|
+
> **判定順序に注意**: `error` が最優先。`isInitializing` が `true` かつ `hasError` が `true` の場合は `"error"` になる。
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
### 3.3 `initialize(): Promise<AsyncDuckDB>`
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
public async initialize(): Promise<duckdb.AsyncDuckDB>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| 項目 | 内容 |
|
|
74
|
+
|------|------|
|
|
75
|
+
| 戻り値 | 初期化された `AsyncDuckDB` インスタンス |
|
|
76
|
+
| 例外 | 初期化失敗時(タイムアウト含む)に throw |
|
|
77
|
+
| タイムアウト | 120秒(120,000ms) |
|
|
78
|
+
|
|
79
|
+
**処理フロー**:
|
|
80
|
+
|
|
81
|
+
```mermaid
|
|
82
|
+
flowchart TD
|
|
83
|
+
A["initialize()"] --> B{workerInstance が存在?}
|
|
84
|
+
B -- Yes --> C["そのまま返す(高速パス)"]
|
|
85
|
+
B -- No --> D{既に初期化中?}
|
|
86
|
+
D -- Yes --> E["initPromise を返す(重複防止)"]
|
|
87
|
+
D -- No --> F["isInitializing = true, hasError = false\nリスナーに通知(status: initializing)"]
|
|
88
|
+
F --> G["Promise.race(\n internalInitializeWorker(),\n タイムアウト 120秒\n)"]
|
|
89
|
+
G -- 成功 --> H["isInitializing = false\nリスナーに通知(status: ready)\nAsyncDuckDB を返す"]
|
|
90
|
+
G -- 失敗 --> I["isInitializing = false, hasError = true\nリスナーに通知(status: error)\ninitPromise = null(リトライ可能)\nエラーを throw"]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**重複呼び出し防止の仕組み**:
|
|
94
|
+
複数の箇所から同時に `initialize()` が呼ばれても、2回目以降は `initPromise` を返すため、Worker は1回だけ作成される。
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### 3.4 `internalInitializeWorker(): Promise<AsyncDuckDB>` (private)
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
private async internalInitializeWorker(): Promise<duckdb.AsyncDuckDB>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Worker の実際の初期化処理。以下のステップを順番に実行する。
|
|
105
|
+
|
|
106
|
+
| ステップ | 処理内容 | 補足 |
|
|
107
|
+
|---------|---------|------|
|
|
108
|
+
| 1 | `duckdb.getJsDelivrBundles()` で CDN バンドル一覧を取得 | jsDelivr CDN を使用 |
|
|
109
|
+
| 2 | `duckdb.selectBundle()` でブラウザ互換のバンドルを選択 | ブラウザの機能に応じて最適なものを選択 |
|
|
110
|
+
| 3 | Blob URL を作成して Web Worker を生成 | `importScripts()` で Worker スクリプトを読み込む |
|
|
111
|
+
| 4 | `new duckdb.AsyncDuckDB(logger, worker)` でインスタンス作成 | ConsoleLogger を使用 |
|
|
112
|
+
| 5 | `workerInstance.instantiate()` で WASM モジュールを読み込み | メインモジュールと pthread Worker |
|
|
113
|
+
| 6 | `splink_udfs` 拡張をインストール・ロード | **一時コネクション**を開き、`INSTALL` → `LOAD` を実行後、`finally` で close する。メインの `connectionInstance` とは別。失敗しても初期化は続行 |
|
|
114
|
+
| 7 | Blob URL を `revokeObjectURL` で解放 | メモリリーク防止 |
|
|
115
|
+
|
|
116
|
+
**splink_udfs について**:
|
|
117
|
+
- 名寄せ(レコードリンケージ)用の UDF(ユーザー定義関数)
|
|
118
|
+
- `INSTALL splink_udfs FROM community;` → `LOAD splink_udfs;`
|
|
119
|
+
- インストール/ロードに失敗しても `warn` ログを出すだけで、初期化全体は失敗しない
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### 3.5 `getConnection(): Promise<AsyncDuckDBConnection>`
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
public async getConnection(): Promise<duckdb.AsyncDuckDBConnection>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
| 項目 | 内容 |
|
|
130
|
+
|------|------|
|
|
131
|
+
| 戻り値 | DuckDB コネクション |
|
|
132
|
+
| 動作 | コネクションが無ければ `initialize()` → `connect()` で作成。あれば再利用 |
|
|
133
|
+
|
|
134
|
+
**注意**: コネクションは1つだけ保持・再利用される。並列クエリの実行には対応していない。
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### 3.6 `executeQuery(sql: string): Promise<unknown>`
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
public async executeQuery(sql: string): Promise<unknown>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
| 項目 | 内容 |
|
|
145
|
+
|------|------|
|
|
146
|
+
| 引数 | `sql` - 実行する SQL 文字列 |
|
|
147
|
+
| 戻り値 | クエリ結果(Apache Arrow Table)。型は `unknown` |
|
|
148
|
+
| タイムアウト | 60秒(60,000ms) |
|
|
149
|
+
| 例外 | クエリ失敗時・タイムアウト時に throw |
|
|
150
|
+
|
|
151
|
+
**処理フロー**:
|
|
152
|
+
1. `getConnection()` でコネクションを取得
|
|
153
|
+
2. `connection.query(sql)` を実行
|
|
154
|
+
3. 60秒のタイムアウトと `Promise.race` で競合
|
|
155
|
+
4. 結果を返す(またはエラーを throw)
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### 3.7 `executeQueries(queries: string[]): Promise<unknown[]>`
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
public async executeQueries(queries: string[]): Promise<unknown[]>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
| 項目 | 内容 |
|
|
166
|
+
|------|------|
|
|
167
|
+
| 引数 | `queries` - SQL 文字列の配列 |
|
|
168
|
+
| 戻り値 | 各クエリの結果を順番に格納した配列 |
|
|
169
|
+
| 実行方法 | **逐次実行**(並列ではない) |
|
|
170
|
+
|
|
171
|
+
> **重要**: クエリは配列の順番通りに1つずつ実行される。CREATE TABLE → INSERT → SELECT のように依存関係があるクエリ列に適している。
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### 3.8 `createTableFromData<T>(tableName, data, options): Promise<void>`
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
public async createTableFromData<T extends Record<string, unknown>>(
|
|
179
|
+
tableName: string,
|
|
180
|
+
data: T[],
|
|
181
|
+
options: {
|
|
182
|
+
dropIfExists?: boolean
|
|
183
|
+
primaryKey?: string
|
|
184
|
+
verbose?: boolean
|
|
185
|
+
} = {},
|
|
186
|
+
): Promise<void>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
JavaScript の配列データから DuckDB テーブルを作成する高レベルメソッド。
|
|
190
|
+
|
|
191
|
+
#### 引数
|
|
192
|
+
|
|
193
|
+
| 引数名 | 型 | 必須 | 説明 |
|
|
194
|
+
|--------|------|------|------|
|
|
195
|
+
| `tableName` | `string` | Yes | 作成するテーブル名 |
|
|
196
|
+
| `data` | `T[]` | Yes | テーブルに投入するデータ配列(1行以上必須) |
|
|
197
|
+
| `options.dropIfExists` | `boolean` | No | `true` の場合、既存テーブルを削除してから作成 |
|
|
198
|
+
| `options.primaryKey` | `string` | No | 主キーとして設定するカラム名 |
|
|
199
|
+
| `options.verbose` | `boolean` | No | ログ出力の有無(デフォルト: `true`) |
|
|
200
|
+
|
|
201
|
+
#### 処理フロー
|
|
202
|
+
|
|
203
|
+
```mermaid
|
|
204
|
+
flowchart TD
|
|
205
|
+
A["createTableFromData(tableName, data, options)"] --> B{data が空配列?}
|
|
206
|
+
B -- Yes --> C["Error を throw"]
|
|
207
|
+
B -- No --> D{operationFlags に\n同名テーブルが進行中?}
|
|
208
|
+
D -- Yes --> E["スキップして return"]
|
|
209
|
+
D -- No --> F["operationFlags にフラグをセット"]
|
|
210
|
+
F --> G{dropIfExists === true?}
|
|
211
|
+
G -- Yes --> H["DROP TABLE IF EXISTS を実行"]
|
|
212
|
+
G -- No --> I1["SHOW TABLES(作成前の確認ログ)"]
|
|
213
|
+
H --> I1
|
|
214
|
+
I1 --> I2["型推定(inferColumnTypes)\n先頭100行→関数内部で先頭10行を使用"]
|
|
215
|
+
I2 --> J["CREATE TABLE 文を生成・実行"]
|
|
216
|
+
J --> J1["SHOW TABLES + information_schema\n(作成後の検証ログ)"]
|
|
217
|
+
J1 --> K["データを1000行ずつバッチ INSERT\nformatValueForSQL で値を変換"]
|
|
218
|
+
K --> L["operationFlags からフラグを削除(finally)"]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
> **注意**: `SHOW TABLES` と `information_schema` による検証ステップは `verbose` が `true`(デフォルト)の場合にログ出力される。検証自体が失敗しても処理は続行される(`try/catch` で `warn` ログのみ)。
|
|
222
|
+
|
|
223
|
+
#### 型推定ルール(`inferColumnTypes`)
|
|
224
|
+
|
|
225
|
+
`createTableFromData` は先頭100行を `inferColumnTypes` に渡すが、`inferColumnTypes` 内部でさらに `data.slice(0, Math.min(data.length, 10))` で先頭10行に絞り込む。したがって**実効的なサンプルサイズは最大10行**。
|
|
226
|
+
|
|
227
|
+
| JavaScript の値 | 推定される DuckDB 型 | 条件 |
|
|
228
|
+
|----------------|---------------------|------|
|
|
229
|
+
| `number`(整数) | `INTEGER` | `Number.isInteger()` が true かつ 32bit 範囲内 |
|
|
230
|
+
| `number`(整数、大きい) | `BIGINT` | `Number.isInteger()` が true かつ 32bit 範囲外 |
|
|
231
|
+
| `number`(小数) | `DOUBLE` | 整数でない number |
|
|
232
|
+
| `boolean` | `BOOLEAN` | typeof === "boolean" |
|
|
233
|
+
| `Date` オブジェクト | `TIMESTAMP` | `instanceof Date` |
|
|
234
|
+
| `string`(日付形式) | `TIMESTAMP` | `new Date(value)` が有効かつ "-" を含む |
|
|
235
|
+
| `string`(その他) | `VARCHAR` | 上記以外 |
|
|
236
|
+
| `null` / `undefined` のみ | `VARCHAR` | 値がすべて null/undefined の場合のフォールバック |
|
|
237
|
+
|
|
238
|
+
#### SQL 値フォーマットルール(`formatValueForSQL`)
|
|
239
|
+
|
|
240
|
+
| JavaScript の値 | SQL 出力 | 補足 |
|
|
241
|
+
|----------------|---------|------|
|
|
242
|
+
| `null` / `undefined` | `NULL` | |
|
|
243
|
+
| `string` | `'エスケープ済み文字列'` | シングルクォートは `''` にエスケープ |
|
|
244
|
+
| `Date` | `'2024-01-01T00:00:00.000Z'` | ISO 8601 形式 |
|
|
245
|
+
| `boolean` | `TRUE` / `FALSE` | |
|
|
246
|
+
| `number` | `123` / `45.67` | そのまま文字列化 |
|
|
247
|
+
| その他 | `String(value)` | |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
### 3.9 `getTableInfo(tableName: string): Promise<unknown>`
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
public async getTableInfo(tableName: string): Promise<unknown>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
| 項目 | 内容 |
|
|
258
|
+
|------|------|
|
|
259
|
+
| 実行 SQL | `DESCRIBE {tableName}` |
|
|
260
|
+
| 戻り値 | テーブルのカラム情報(名前、型、NULL 可否など) |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### 3.10 `listTables(): Promise<unknown>`
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
public async listTables(): Promise<unknown>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
| 項目 | 内容 |
|
|
271
|
+
|------|------|
|
|
272
|
+
| 実行 SQL | `SHOW TABLES` |
|
|
273
|
+
| 戻り値 | 現在のデータベースに存在するテーブル一覧 |
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
### 3.11 `isReady(): boolean`
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
public isReady(): boolean
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
| 項目 | 内容 |
|
|
284
|
+
|------|------|
|
|
285
|
+
| 戻り値 | `workerInstance !== null` の場合 `true` |
|
|
286
|
+
| 用途 | 同期的に初期化完了を確認したい場合 |
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
### 3.12 `cleanup(): Promise<void>`
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
public async cleanup(): Promise<void>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
リソースの完全な解放を行う。
|
|
297
|
+
|
|
298
|
+
| ステップ | 処理 |
|
|
299
|
+
|---------|------|
|
|
300
|
+
| 1 | コネクションを close |
|
|
301
|
+
| 2 | Worker を terminate |
|
|
302
|
+
| 3 | すべての内部状態をリセット |
|
|
303
|
+
| 4 | リスナーと operationFlags をクリア |
|
|
304
|
+
|
|
305
|
+
> **注意**: cleanup 後に再度 `initialize()` を呼べば再初期化できる。
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
### 3.13 `resetInstance(): void` (static)
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
public static resetInstance(): void
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
| 項目 | 内容 |
|
|
316
|
+
|------|------|
|
|
317
|
+
| 用途 | テスト時にシングルトンをリセットする |
|
|
318
|
+
| 動作 | `cleanup()` を呼び出し(**await しない**)、直後に `instance = null` にする |
|
|
319
|
+
|
|
320
|
+
> **注意**: `cleanup()` は `async` メソッドだが、`resetInstance()` は同期メソッド(`void`)であり `cleanup()` を await しない。コネクションの close や Worker の terminate が完了する前にインスタンスが `null` になる。テストで確実にリソースを解放したい場合は、`resetInstance()` の前に `await DuckDBService.getInstance().cleanup()` を明示的に呼ぶこと。
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
### 3.14 ステータスリスナー
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
public addStatusListener(listener: () => void): void
|
|
328
|
+
public removeStatusListener(listener: () => void): void
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
| メソッド | 説明 |
|
|
332
|
+
|---------|------|
|
|
333
|
+
| `addStatusListener` | ステータス変更時に呼ばれるコールバックを登録 |
|
|
334
|
+
| `removeStatusListener` | 登録済みコールバックを削除 |
|
|
335
|
+
|
|
336
|
+
リスナーが呼ばれるタイミング:
|
|
337
|
+
- `initialize()` 開始時(→ `"initializing"`)
|
|
338
|
+
- `initialize()` 成功時(→ `"ready"`)
|
|
339
|
+
- `initialize()` 失敗時(→ `"error"`)
|
|
340
|
+
|
|
341
|
+
## 4. エクスポート
|
|
342
|
+
|
|
343
|
+
`services/duckdb.ts` のエクスポート:
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
// 名前付きエクスポート(クラス本体)
|
|
347
|
+
export { DuckDBService }
|
|
348
|
+
|
|
349
|
+
// デフォルトエクスポート(シングルトンインスタンス)— services/duckdb.ts 内でのみ default
|
|
350
|
+
export default DuckDBService.getInstance()
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
`index.ts` での re-export:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
export { DuckDBService, default as duckDBService } from "./services/duckdb"
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
> **注意**: `services/duckdb.ts` はデフォルトエクスポートを持つが、`index.ts` はそれを `duckDBService` という**名前付きエクスポート**として re-export する。パッケージ自体にデフォルトエクスポートは存在しない。
|
|
360
|
+
|
|
361
|
+
| エクスポート | インポート方法 | 用途 |
|
|
362
|
+
|------------|--------------|------|
|
|
363
|
+
| `DuckDBService`(クラス) | `import { DuckDBService } from '...'` | `getInstance()` や `resetInstance()` を呼びたい場合 |
|
|
364
|
+
| `duckDBService`(インスタンス) | `import { duckDBService } from '...'` | 通常利用。シングルトンインスタンスを直接使う |
|
|
365
|
+
|
|
366
|
+
## 5. ステータス遷移図
|
|
367
|
+
|
|
368
|
+
```mermaid
|
|
369
|
+
stateDiagram-v2
|
|
370
|
+
state "not-initialized" as not_init
|
|
371
|
+
[*] --> not_init
|
|
372
|
+
not_init --> initializing : initialize()
|
|
373
|
+
initializing --> ready : 成功
|
|
374
|
+
initializing --> error : 失敗
|
|
375
|
+
ready --> not_init : cleanup()
|
|
376
|
+
error --> initializing : initialize()(リトライ)
|
|
377
|
+
error --> not_init : cleanup()
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## 6. 新人向けポイント
|
|
381
|
+
|
|
382
|
+
1. **`import { duckDBService } from '@aiquants/duckdb-helper'` が最も簡単な使い方**。シングルトンインスタンスがそのまま使える。
|
|
383
|
+
2. **`initialize()` は何回呼んでも安全**。既に初期化済みならすぐ返る。初期化中なら同じ Promise を返す。
|
|
384
|
+
3. **コネクションは自動で再利用される**。`getConnection()` を何度呼んでも、内部では1つのコネクションが使い回される。
|
|
385
|
+
4. **`createTableFromData` が便利**。JavaScript の配列をそのまま DuckDB テーブルにできる。型推定も自動。
|
|
386
|
+
5. **テスト時は `DuckDBService.resetInstance()` でリセット**できる。テスト間の状態汚染を防げる。
|
|
387
|
+
6. **splink_udfs の失敗は無視される**。名寄せ機能が不要な環境でもエラーにならない。
|