@funbrew/pdf-client SDKでPDF生成・アップロード・進捗購読を1コードで繋ぐ
Node.js向けの @funbrew/pdf とは別に、**ブラウザ専用の統合SDK「@funbrew/pdf-client」**が2026年5月31日にリリースされました。このSDKは次の3つを1パッケージに統合しています。
- tus.io 再開可能アップロード — ネットワーク断でも自動リカバリ
- SSE リアルタイム進捗購読 —
EventSourceベースのジョブ監視 - PDF 生成・変換 API の呼び出し — HTML/URL/Markdown から生成、圧縮・マージ・テキスト抽出も
@funbrew/pdf と @funbrew/pdf-client の違い
まずパッケージを選ぶ際の混乱を防ぐため、用途別の対応表を示します。
| 比較項目 | @funbrew/pdf |
@funbrew/pdf-client |
|---|---|---|
| 動作環境 | Node.js(サーバーサイド) | ブラウザ(クライアントサイド) |
| インストール | npm install @funbrew/pdf |
CDN または ESM URL(npm 公開予定) |
| tus アップロード | なし | あり(再開可能・並列対応) |
| SSE 進捗購読 | なし | あり(subscribeToJob()) |
| 主な用途 | Next.js API Route、Lambda | フォーム送信、ファイルドロップUI |
| 型定義 | 同梱(npm パッケージ) | CDN 配信(/sdk/types/index.d.ts) |
サーバーサイドで動かすなら @funbrew/pdf、ブラウザで動かすなら @funbrew/pdf-client が正解です。APIキーをクライアントに置くセキュリティ上の考慮は後述します。
インストール・読み込み
CDN(script タグ)
HTMLページにそのまま埋め込む場合は UMD ビルドを使います。
<script src="https://pdf.funbrew.cloud/sdk/pdf-client.umd.js"></script>
<script>
const client = new PdfClient.PdfClient({
baseUrl: 'https://pdf.funbrew.cloud',
apiKey: 'sk-...',
});
</script>
ESM(バンドラー / モジュール環境)
Vite、webpack、Rollup などバンドラーを使う場合は ESM ビルドを import します。
import { PdfClient } from 'https://pdf.funbrew.cloud/sdk/pdf-client.esm.js';
const client = new PdfClient({
baseUrl: 'https://pdf.funbrew.cloud',
apiKey: 'sk-...',
});
TypeScript 型定義
型定義は CDN で配信されています。tsconfig.json の paths や /// <reference types="..."> で読み込めます。
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@funbrew/pdf-client": ["https://pdf.funbrew.cloud/sdk/types/index.d.ts"]
}
}
}
または dts ファイルを手動でダウンロードしてローカルに配置するのがシンプルです。
初期化
import { PdfClient } from 'https://pdf.funbrew.cloud/sdk/pdf-client.esm.js';
const client = new PdfClient({
baseUrl: 'https://pdf.funbrew.cloud', // 末尾スラッシュ不要
apiKey: 'sk-...', // X-API-Key ヘッダで送信
});
PdfClientOptions の全フィールド:
interface PdfClientOptions {
baseUrl: string; // 例: 'https://pdf.funbrew.cloud'
apiKey: string; // 発行された PDF API キー
fetch?: typeof fetch; // テスト用に fetch 実装を差し替え可能
}
PDF 生成
HTML・URL・Markdown の各エンドポイントをラップしたメソッドが用意されています。
// HTML から PDF を生成
const result = await client.generateFromHtml({
html: '<h1>請求書</h1><p>¥10,000</p>',
options: { page_size: 'A4' },
});
console.log(result.download_url); // https://pdf.funbrew.cloud/downloads/...
// URL を指定して生成
const fromUrl = await client.generateFromUrl({
url: 'https://example.com/report',
options: { page_size: 'A4', landscape: true },
});
// Markdown から生成
const fromMd = await client.generateFromMarkdown({
markdown: '# レポート\n\n本文...',
options: { theme: 'github' },
});
GenerateResult 型:
interface GenerateResult {
filename: string;
download_url: string;
file_size: number;
expires_at?: string | null;
job_id?: string;
}
大容量ファイルアップロード(uploadFile)
uploadFile() は tus.io プロトコルで再開可能アップロードを行います。ネットワークが途切れても指数バックオフで自動リトライし、切れた地点から再開します。
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const upload = await client.uploadFile(file, {
chunkSize: 5 * 1024 * 1024, // 5MB チャンク(デフォルト)
onProgress: (sent, total) => {
const pct = Math.round((sent / total) * 100);
document.querySelector('#progress').textContent = `${pct}%`;
},
});
console.log(upload.key); // UUID — 後続の merge/compress に upload_id として渡す
console.log(upload.url); // tus Location URL
console.log(upload.size); // ファイルサイズ(バイト)
UploadOptions の全フィールド:
interface UploadOptions {
chunkSize?: number; // デフォルト: 5MB
retryDelays?: number[]; // デフォルト: [0, 1000, 3000, 5000, 10000]
metadata?: Record<string, string>; // Upload-Metadata に追加
onProgress?: (sent: number, total: number) => void;
signal?: AbortSignal; // 中断可能
parallelUploads?: number; // 並列チャンク数(デフォルト: 1)
}
並列アップロード(高 RTT 環境向け)
モバイルや衛星回線など RTT が高い環境では parallelUploads を指定するとスループットが向上します。tus Concatenation 拡張でファイルを N 分割して並列送信し、サーバー側で結合します。
const upload = await client.uploadFile(file, {
parallelUploads: 4, // 4 つのチャンクを並列送信
onProgress: (sent, total) => console.log(`${sent}/${total}`),
});
注意: サーバー側のストレージが s3-multipart モードの場合は並列アップロードは使用できません(400 エラー)。local モードのみ対応です。
AbortSignal で中断
const controller = new AbortController();
const uploadPromise = client.uploadFile(file, {
signal: controller.signal,
onProgress: (sent, total) => console.log(`${sent}/${total}`),
});
// キャンセルボタン
document.querySelector('#cancel').addEventListener('click', () => {
controller.abort();
});
try {
const upload = await uploadPromise;
} catch (err) {
if (err.name === 'AbortError') console.log('アップロードをキャンセルしました');
}
進捗購読(subscribeToJob)
マージ・圧縮・大量ページのToImage変換など、処理時間がかかるジョブはSSEで進捗をリアルタイム購読できます。詳細はSSE進捗ガイドも参照してください。
const jobId = crypto.randomUUID(); // UUIDv4 を自前で生成
// 先に購読を開始してからジョブをキック
const sub = client.subscribeToJob(jobId, {
onProgress: (event) => {
console.log(event.phase, event.progress, event.message);
// phase: 'queued' | 'preprocessing' | 'rendering' | 'uploading' | 'done' | 'failed'
},
onDone: (event) => {
console.log('完了!', event.data?.download_url);
// sub は自動で close される
},
onError: (err) => {
console.warn('SSE エラー', err);
},
});
// jobId を X-Job-Id ヘッダに付けてリクエスト
await client.compress(
{ upload_id: upload.key, quality: 'medium' },
{ jobId },
);
// 必要に応じて手動停止
// sub.close();
subscribeToJob() の返値:
{
close: () => void; // SSE 接続を手動で閉じる
source: EventSource; // 生の EventSource インスタンス
}
phase の遷移:
queued → preprocessing → rendering → uploading → done
↘ failed
パイプライン例: アップロード → 圧縮 → 完了通知
アップロードからジョブ完了まで一連の処理を1つのコードブロックにまとめた例です。tus再開可能アップロードガイドもあわせて参照してください。
async function compressPdfWithProgress(file, qualityLevel = 'medium') {
const jobId = crypto.randomUUID();
// 1. 進捗UIを更新するヘルパー
const updateUI = (phase, pct) => {
document.querySelector('#status').textContent = `${phase} ${pct}%`;
};
// 2. SSE 購読を先に開始
const sub = client.subscribeToJob(jobId, {
onProgress: ({ phase, progress }) => updateUI(phase, progress),
onDone: ({ data }) => {
updateUI('done', 100);
document.querySelector('#download').href = data.download_url;
document.querySelector('#download').style.display = 'block';
},
onError: () => updateUI('error', 0),
});
try {
// 3. tus アップロード
updateUI('uploading', 0);
const upload = await client.uploadFile(file, {
onProgress: (sent, total) =>
updateUI('uploading', Math.round((sent / total) * 100)),
});
// 4. 圧縮ジョブをキック(X-Job-Id 付き)
await client.compress(
{ upload_id: upload.key, quality: qualityLevel },
{ jobId },
);
} catch (err) {
sub.close();
throw err;
}
}
// 使用
const fileInput = document.querySelector('#pdf-file');
await compressPdfWithProgress(fileInput.files[0], 'high');
アップロード済みファイルを他の操作に流す
upload.key は upload_id として merge・extractText・toImage にも渡せます。filename(サーバー保管済み)と upload_id(tusアップロード)は排他で、どちらか一方を指定します。
const upload = await client.uploadFile(file);
// マージ(複数 PDF を結合)
const merged = await client.merge({
upload_ids: [upload.key, anotherUpload.key],
});
// テキスト抽出
const extracted = await client.extractText({
upload_id: upload.key,
});
// PDF → 画像変換
const images = await client.toImage({
upload_id: upload.key,
format: 'png',
dpi: 150,
});
エラーハンドリング
API エラーは Error としてスローされ、err.status(HTTP ステータス)と err.response(レスポンスJSON)が付与されます。
try {
const result = await client.compress({
upload_id: 'invalid-id',
quality: 'medium',
});
} catch (err) {
console.error(err.message); // 例: "HTTP 404"
console.error(err.status); // 404
console.error(err.response); // { error: 'Not found', ... }
}
アップロードの自動リトライ
uploadFile() はデフォルトで [0, 1000, 3000, 5000, 10000] ms の指数バックオフでリトライします。カスタマイズする場合:
const upload = await client.uploadFile(file, {
retryDelays: [0, 2000, 5000, 10000, 30000], // より長い待機
});
TypeScript 型サポート
SDKはTypeScriptで書かれており、主要な型がエクスポートされています。
import type {
PdfClientOptions,
UploadResult,
UploadOptions,
GenerateResult,
ProgressEventPayload,
SubscribeOptions,
} from 'https://pdf.funbrew.cloud/sdk/types/index.d.ts';
// ProgressEventPayload の構造
interface ProgressEventPayload {
phase: string; // 'queued' | 'preprocessing' | 'rendering' | 'uploading' | 'done' | 'failed'
progress: number; // 0〜100
message?: string | null;
data?: Record<string, unknown>;
occurred_at: string; // ISO 8601
}
型定義を利用することで、onProgress コールバックや GenerateResult.download_url へのアクセスがIDEの補完付きで書けます。
セキュリティ上の注意
@funbrew/pdf-client はブラウザで動作するため、APIキーがユーザーに見える点に注意が必要です。本番環境での推奨パターン:
- スコープ限定キー: アップロードと圧縮のみ許可するAPIキーを発行する(ダッシュボードの権限設定を参照)
- 署名付きURL: バックエンドで一時的な操作トークンを発行し、フロントエンドはそのトークンのみ持つ
- 自社バックエンド経由: フロントエンド → 自社API → FUNBREW PDFという構成にすると、APIキーをサーバー側に隠蔽できる
デモ環境
SDKプレイグラウンド(/sdk-playground)でブラウザ上から動作を確認できます。ファイルドロップ → tus アップロード → 圧縮 + SSE 進捗 → ダウンロードURLの一連の流れを1画面で体験できます。
プレイグラウンドではHTMLテンプレートを試してからSDKに組み込むこともできます。
まとめ
@funbrew/pdf-client は次の3つを1パッケージにまとめたブラウザ向けSDKです。
| 機能 | メソッド / 特徴 |
|---|---|
| 再開可能アップロード | uploadFile() — tus.io、自動リトライ、並列対応 |
| SSE 進捗購読 | subscribeToJob() — EventSource ベース、phase 付き |
| PDF 生成・変換 | generateFromHtml() / compress() / merge() など |
Node.js サーバーサイドで使う場合は @funbrew/pdf を使ったTypeScriptガイド を、SSE の詳細は SSE進捗ストリーミングガイド を、tus の詳細は 再開可能アップロードガイド をあわせて参照してください。
ユースケース別の活用例はユースケース一覧も参考にしてください。