2026/06/04

@funbrew/pdf-client SDKでPDF生成・アップロード・進捗購読を1コードで繋ぐ

sdkjavascripttypescriptnpmclient

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.jsonpaths/// <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.keyupload_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 の詳細は 再開可能アップロードガイド をあわせて参照してください。

ユースケース別の活用例はユースケース一覧も参考にしてください。

Powered by FUNBREW PDF