TypeScript
composepdf はこの API の型つきクライアントです。依存パッケージを持たず、レイアウト
エンジンも積んでいません。レンダリングはサーバー側のままなので、返る PDF は
デザイナーがプレビューで見たものと同じです。
pnpm add composepdfnpm i composepdfyarn add composepdfbun add composepdfNode 18 以降・Bun・Deno・Cloudflare Workers など、fetch がある場所で動きます。
レンダリングする
Section titled “レンダリングする”import { writeFile } from 'node:fs/promises';import { createClient } from 'composepdf';
const composepdf = createClient({ apiKey: process.env.COMPOSEPDF_API_KEY! });
const { bytes } = await composepdf.render('cv_…', { data: { customer: { name: '株式会社Acme' }, items: [ { name: 'デザイン作業', price: 1200 }, { name: 'ホスティング', price: 90 }, ], },});
await writeFile('invoice.pdf', bytes);キーは持っている人がそのまま使える資格情報なので、サーバー側に置いてください。 自社の画面にプレビューを出したい場合は埋め込みライブプレビューを 使います。あちらは範囲を限定した埋め込みトークンで動きます。
テンプレートから型を生成する
Section titled “テンプレートから型を生成する”公開したテンプレートは、どのデータを読むかを
自分で宣言しています。composepdf types はその宣言を1つのファイルにまとめます。
pnpm exec composepdf types --out src/composepdf-types.tsnpx composepdf types --out src/composepdf-types.tsyarn composepdf types --out src/composepdf-types.tsbunx composepdf types --out src/composepdf-types.ts生成された型をクライアントに渡すと、テンプレートの id と送るデータの形の両方が コンパイル時に検査されます。
import { createClient } from 'composepdf';import type { Templates } from './composepdf-types';
const composepdf = createClient<Templates>({ apiKey: process.env.COMPOSEPDF_API_KEY! });
await composepdf.render('cv_…', { data: { customer: { name: '株式会社Acme' } } });// ^ このキーで焼ける id だけ// ^ このテンプレートが宣言した欄だけ手で書き足すものはありません。中身はテンプレート自身のデータスキーマなので、 欄を足して公開し直したら生成し直す、それが移行作業の全部です。生成物は コミットしてください —— 日時を持たないので、変わっていないワークスペースを 生成し直しても差分は出ません。
コマンドは環境変数 COMPOSEPDF_API_KEY を読みます(--key で上書き)。出力先は
--out を省くと composepdf-types.ts です。
エラーは、どの経路が違うかを名指しする
Section titled “エラーは、どの経路が違うかを名指しする”テンプレートが受け取れないデータはレンダリングが始まる前に弾かれます。間違った 呼び出しに費用はかからず、どこが違うかがそのまま返ります。
import { ComposePdfError } from 'composepdf';
try { await composepdf.render('cv_…', { data });} catch (e) { if (e instanceof ComposePdfError && e.code === 'data_contract_violation') { for (const issue of e.issues) { console.error(issue.path, issue.message); // items[3].price expected number } }}ComposePdfError は status と requestId(問い合わせの際に伝えてください)、
429・503 のときの retryAfter も持ちます。
複数レコードを1ファイルに
Section titled “複数レコードを1ファイルに”const { bytes, records } = await composepdf.render('cv_…', { dataList: [invoiceA, invoiceB, invoiceC],});1レコードにつき1文書を、順番どおりに1つのファイルへ綴じます。詳しくは まとめて1ファイルに。
時間のかかるレンダリング
Section titled “時間のかかるレンダリング”const job = await composepdf.renderAsync('cv_…', { dataList: manyRecords });
let render = await composepdf.getRender(job.id);while (!render.done) { await new Promise((r) => setTimeout(r, 1000)); render = await composepdf.getRender(job.id);}
const bytes = await composepdf.downloadRender(job.id);受け付けられたジョブは必ず PDF を保持します。同期レンダリングが保持するのは
options: { store: true } を付けたときだけです。
二重課金にならない再送
Section titled “二重課金にならない再送”await composepdf.render('cv_…', { data }, { idempotencyKey: `order-${orderId}` });同じキーをもう一度出すと、保存済みの PDF がそのまま返ります。焼き直しも二重の 計上も起きません。
クライアントが覆う範囲
Section titled “クライアントが覆う範囲”クライアントの面は公開 OpenAPI ドキュメントと
同一です —— listTemplates / render / renderAsync / getSchema /
getSchemaSource / listRenders / getRender / downloadRender / getUsage /
health。curl で届くものは、ここから型つきで届きます。