アーキテクチャ
設計概要
type-gitは3つの主要な原則で設計されています:
- コンテキスト分離 - リポジトリ非依存とリポジトリ固有の操作を明確に区別
- ランタイム抽象化 - アダプターを通じてNode.js、Deno、Bunをサポート
- レイヤードAPI - 用途に応じた3つの抽象化レベル
graph TD
subgraph Application["アプリケーション"]
A[あなたのコード]
end
subgraph HighLevel["高レベルAPI"]
B["TypeGit (便利クラス)"]
C["createGit() (高度な使用)"]
end
subgraph Core["コアインターフェース"]
D["Git (リポジトリ非依存)"]
E["WorktreeRepo / BareRepo (リポジトリ固有)"]
end
subgraph Runner["実行レイヤー"]
F["CliRunner (コマンド構築、進捗トラッキング)"]
end
subgraph Adapters["ランタイムアダプター"]
G["ExecAdapter (プロセス起動)"]
H["FsAdapter (ファイル操作)"]
end
subgraph Runtimes["JavaScriptランタイム"]
I[Node.js]
J[Deno]
K[Bun]
end
A --> B & C
B & C --> D & E
D & E --> F
F --> G & H
G & H --> I & J & K
コンテキスト分離
type-gitはリポジトリコンテキストの必要性に基づいて操作を分離しています。
Git(リポジトリ非依存)
既存のリポジトリを必要としない操作:
const git = new TypeGit();
// リポジトリを開かずに実行可能await git.clone(url, path); // リモートリポジトリをクローンawait git.init(path); // 新規リポジトリ作成await git.lsRemote(url); // リモートrefを一覧表示await git.version(); // gitバージョン取得await git.raw(['help']); // 任意のgitコマンド実行Repo(リポジトリ固有)
リポジトリコンテキストが必要な操作:
const repo = await git.open('./my-repo');
// リポジトリが必要await repo.status();await repo.commit({ message: 'feat: 機能追加' });await repo.branch.create('feature-x');await repo.push();WorktreeRepo vs BareRepo
type-gitは標準リポジトリとベアリポジトリを区別します:
// 標準リポジトリ(作業ディレクトリあり)const worktree = await git.open('./repo');worktree.workdir; // '/path/to/repo'await worktree.status();await worktree.add(['file.txt']);
// ベアリポジトリ(作業ディレクトリなし)const bare = await git.open('./repo.git');bare.gitDir; // '/path/to/repo.git'await bare.fetch();await bare.push();// bare.status() // 利用不可 - 作業ディレクトリなし3層API
type-gitは3つの抽象化レベルを提供します:
1. Raw API
未処理の出力を直接取得:
// { stdout, stderr, exitCode } を返すconst result = await repo.raw(['log', '--oneline', '-5']);console.log(result.stdout);使用場面:
- 型付きAPIでカバーされていない出力が必要
- 自分で出力を処理したい
- カスタムgitコマンドを実行
2. Typed API
パースされた型安全な出力:
// 型付きStatusPorcelainオブジェクトを返すconst status = await repo.status();for (const entry of status.entries) { console.log(entry.path, entry.index, entry.workdir);}
// 型付きCommit配列を返すconst commits = await repo.log({ maxCount: 10 });for (const commit of commits) { console.log(commit.hash, commit.subject);}使用場面:
- 構造化データが必要
- 型安全性が必要
- 出力形式が予測可能(porcelain、JSON)
3. High-Level API
一般的なワークフロー向けの便利メソッド:
// ブランチ操作await repo.branch.create('feature-x');await repo.branch.delete('old-branch');
// スタッシュ操作await repo.stash.push({ message: 'WIP' });await repo.stash.pop();
// タグ操作await repo.tag.create('v1.0.0', { message: 'Release 1.0' });使用場面:
- クリーンで直感的なAPIが必要
- 一般的なgit操作を行う
- メソッドチェーンスタイルを好む
ランタイム抽象化
type-gitはアダプターを通じてランタイム固有のコードを抽象化します。
なぜアダプターが必要か?
JavaScriptランタイムごとにAPIが異なります:
| 操作 | Node.js | Deno | Bun |
|---|---|---|---|
| プロセス起動 | child_process.spawn | Deno.Command | Bun.spawn |
| ファイル読み込み | fs.readFile | Deno.readTextFile | Bun.file().text() |
| 一時ファイル | os.tmpdir() + fs | Deno.makeTempFile | Bun.file |
アダプターアーキテクチャ
interface ExecAdapter { spawn(options: SpawnOptions): Promise<SpawnResult>; getCapabilities(): Capabilities;}
interface FsAdapter { createTempFile(): Promise<string>; deleteFile(path: string): Promise<void>; tail(options: TailOptions): Promise<void>; // ...}
interface RuntimeAdapters { exec: ExecAdapter; fs: FsAdapter;}事前設定されたクラス
各ランタイムには事前設定されたTypeGitクラスがあります:
import { TypeGit } from 'type-git/node';
// Denoimport { TypeGit } from 'npm:type-git/deno';
// Bunimport { TypeGit } from 'type-git/bun';これらは自動的にランタイムに適したアダプターを設定します。
コマンド実行フロー
git操作を呼び出すと:
flowchart TD
A["repo.status()"] --> B["WorktreeRepoImpl.status()"]
B -->|"引数を構築"| C["CliRunner.run(context, args)"]
C -->|"コンテキスト・環境変数を追加"| D["ExecAdapter.spawn(options)"]
D -->|"プロセスを実行"| E["CliRunner"]
E -->|"終了コードをマッピング"| F["WorktreeRepoImpl"]
F -->|"stdoutをパース"| G["StatusPorcelain (型付き結果)"]
cwdに依存しない設計
type-gitは作業ディレクトリを変更する代わりにgit -C <path>を使用します:
// type-gitの内部実行:// git -C /path/to/repo status
// こうではない:// cd /path/to/repo && git statusメリット:
- スレッドセーフ(グローバル状態なし)
- 予測可能な動作
- あらゆる実行コンテキストで動作