@sharedan/cms-sdk
v0.7.2
Published
TypeScript SDK for the ShareDan 簡易CMS public API — posts (by post type), post types, FAQs, static contents, categories, and form delivery/submission (with spam protection).
Maintainers
Readme
@sharedan/cms-sdk
ShareDan CMS の公開API を扱う TypeScript SDK です。 投稿(種別ごと)・FAQ・固定コンテンツ・カテゴリの取得、本文(ProseMirror / Tiptap JSON)の HTML レンダリング、およびフォームの配信・送信(スパム対策込み)を、型付き・依存ゼロで提供します。 ブラウザと Node.js 18+ の両方で動作します。
投稿は「種別(post type)」でスコープします。 投稿系メソッドは第1引数に種別のslug (既定種別は
'news')を取ります(例cms.posts.list('news'))。利用可能な種別はcms.postTypes.list()で取得できます。
特長
- 🧩 型付きクライアント — 各エンドポイントを
cms.posts/cms.postTypes/cms.faqs/cms.contents/cms.forms/cms.maintenance/cms.changesで呼び出し、レスポンスは完全に型付け - 🖋️ リッチテキスト対応 — 本文/回答の ProseMirror JSON を
renderProseMirror()で HTML 文字列へ変換 - 📨 フォーム送信 — 問い合わせ等フォームの配信(項目定義)と送信を
cms.formsでラップ。Turnstile・レートリミット・ハニーポット・送信速度・重複防止の多層スパム対策に対応 - 🔁 増分SSG(変更検知) — 前回ビルド以降に変更があった公開コンテンツの差分を
cms.changesで取得し、変更分だけ再生成。大量コンテンツサイトの増分ビルドに対応 - 🚦 明快なエラー型 —
ShareDanCmsApiError(status/code/isNotFound/isRateLimited等)とShareDanCmsNetworkError - 📦 依存ゼロ・軽量 —
fetchベース。ブラウザ / Node / エッジランタイムで動作 - 🔒 主に閲覧専用 — 公開API(
X-API-KEYでテナント、X-SITE-CODEでサイトを特定)をラップ。書き込みはフォーム送信(cms.forms.submit)とビルド完了通知(cms.changes.complete)のみ
インストール
npm install @sharedan/cms-sdkリポジトリを直接クローンして使う場合は、
npm install後にnpm run buildでdist/(ESM + 型定義)を生成してから読み込んでください。
クイックスタート
import { ShareDanCmsClient, renderProseMirror } from '@sharedan/cms-sdk'
const cms = new ShareDanCmsClient({
baseUrl: 'https://cms.example.com', // CMSのオリジン(/api/v1 は自動付与)
apiKey: 'YOUR_API_KEY', // X-API-KEY
siteCode: 'your-site', // X-SITE-CODE
})
// 種別一覧(discovery。既定種別は slug 'news')
const { items: postTypes } = await cms.postTypes.list()
// 投稿一覧(種別 'news'・カテゴリ絞り込み・ページネーション)
const news = await cms.posts.list('news', { category: 'event', page: 1, perPage: 20 })
console.log(news.meta.total, news.items)
// 投稿詳細(本文あり)→ HTML 化
const detail = await cms.posts.get('news', news.items[0]!.slug)
element.innerHTML = renderProseMirror(detail.body)
// FAQ / 固定コンテンツ / カテゴリ(投稿カテゴリは種別スコープ)
const faqs = await cms.faqs.list({ category: 'product' })
const content = await cms.contents.get('top-message')
const categories = await cms.posts.categories('news')設定
| オプション | 必須 | 説明 |
|-----------|:---:|------|
| baseUrl | ✓ | CMSのオリジン(例 https://cms.example.com)。/api/v1 は自動付与 |
| apiKey | ✓ | テナントのAPIキー(X-API-KEY) |
| siteCode | ✓ | サイト識別子(X-SITE-CODE / m_sites.code) |
| apiVersion | | APIバージョン(既定 'v1') |
| fetch | | 使用する fetch 実装(既定 globalThis.fetch) |
| timeoutMs | | タイムアウト(ミリ秒)。AbortController で中断 |
| headers | | 全リクエストに付与する追加ヘッダ |
API
| メソッド | エンドポイント | 戻り値 |
|---------|---------------|--------|
| cms.postTypes.list() | GET /api/v1/post-types | ListResponse<PostTypeSummary> |
| cms.posts.list(postType, params?) | GET /api/v1/posts/{postType} | Paginated<PostSummary> |
| cms.posts.get(postType, slug, params?) | GET /api/v1/posts/{postType}/{slug} | PostDetail |
| cms.posts.getPreview(postType, slug, token) | GET /api/v1/posts/{postType}/{slug}/preview | PostDetail(下書き版・X-PREVIEW-TOKEN・非キャッシュ) |
| cms.posts.categories(postType, params?) | GET /api/v1/posts/{postType}/categories | ListResponse<Category> |
| cms.faqs.list(params?) | GET /api/v1/faqs | ListResponse<Faq> |
| cms.faqs.categories(params?) | GET /api/v1/faqs/categories | ListResponse<Category> |
| cms.contents.get(key, params?) | GET /api/v1/contents/{key} | Content |
| cms.forms.get(key, params?) | GET /api/v1/forms/{key} | Form(項目定義・非キャッシュ) |
| cms.forms.submit(key, input) | POST /api/v1/forms/{key}/submissions | FormSubmissionResult |
| cms.maintenance.get(params?) | GET /api/v1/maintenance | Maintenance(非キャッシュ) |
| cms.changes.fetch() | GET /api/v1/changes | ChangeFeed(増分SSG差分・冪等・非キャッシュ) |
| cms.changes.complete(builtAsOf) | POST /api/v1/changes/complete | ChangeCompleteResult(ビルド完了通知) |
postType は種別のslug(既定種別は 'news')。存在しない種別は 404 になります。
PostDetail.body / Faq.answer / Content.body は ProseMirror JSON(ProseMirrorNode)です。
PostDetail.meta(PostMeta)にはメタタイトル・ディスクリプション・OGP画像URL(meta_title / meta_description / og_image_url)が入り、公開ページのSEO/OGP出力に使えます(未設定時は null)。
多言語(locale)
多言語オプション(有料オプション)が有効なサイトでは、コンテンツ取得系メソッドの params.locale で言語別の公開版を取得できます。対応言語は en(英語)・zh-Hans(簡体)・zh-Hant(繁体)・ko(韓国語)・vi(ベトナム語)。未指定時は原文(日本語) を返します。
// 英語の投稿一覧・個別・固定コンテンツ・FAQ・フォーム・メンテナンス状態
const news = await cms.posts.list('news', { locale: 'en' })
const detail = await cms.posts.get('news', news.items[0]!.slug, { locale: 'en' })
const about = await cms.contents.get('about', { locale: 'en' })
const faqs = await cms.faqs.list({ locale: 'en' })
const form = await cms.forms.get('contact', { locale: 'en' })
const status = await cms.maintenance.get({ locale: 'en' })locale対応メソッド:posts.list/posts.get/posts.unlock/posts.categories/faqs.list/faqs.categories/contents.get/forms.get/maintenance.get(種別一覧postTypes.listは言語共通のため非対応)。パスワード保護投稿の本文をposts.unlock(type, slug, password, { locale })で取得する場合も、タイトルと言語を揃えるためposts.getと同じlocaleを渡します。- 対象言語の公開版がまだ無いコンテンツは 原文を返し、レスポンスに
translation_available: falseが含まれます(locale未指定時は省略)。slug・key等の識別子は言語共通です。 - 多言語オプションが無効、または未設定・未対応の言語を指定すると 400(
ShareDanCmsApiError.isBadRequest・code === 'E001008') になります。
増分SSG(変更検知)
大量コンテンツのサイトで、前回ビルド以降に変更があった公開コンテンツだけを再生成するための差分配信API(cms.changes)です。サイト単位の「最終ビルド日時」はCMS側が保持し、fetch() はそれ以降の差分を返します(最終ビルド日時は進めない=冪等)。ビルド成功後に complete(as_of) を呼んで前進させます。
// 1. 差分を取得(CMSが保持する最終ビルド日時以降の変更)
const feed = await cms.changes.fetch()
if (feed.full_resync) {
// 初回/カテゴリ・タグの変更/手動ビルド時は true。サイト全体を再生成する
// (changes は現在公開中の全コンテンツを upsert で列挙)
await buildEverything()
} else {
for (const c of feed.changes) {
if (c.type === 'post' && c.action === 'upsert') {
// 現在公開中=再生成(post は c.post_type が種別slug、c.key が投稿slug)
const post = await cms.posts.get(c.post_type!, c.key)
await writePage(post)
} else if (c.type === 'post' && c.action === 'delete') {
// 非公開・削除・未公開=出力から除去(元々無ければ無視)
await removePage('news', c.key)
}
// faq / content / category / form も type + action で同様に扱う
}
}
// 2. ビルド/デプロイが成功したら、取得した as_of で最終ビルド日時を前進させる
// (失敗時は呼ばない=差分を取りこぼさず次回に再取得できる)
await cms.changes.complete(feed.as_of)ChangeItemはtype(post/faq/content/category/form)・post_type(post のときの種別slug・他は null)・key(slug/key/id)・action(upsert/delete)・updated_atを持ちます。fetch()は最終ビルド日時を進めません(同じ差分を何度でも返す=冪等)。ビルド成功後に必ずcomplete(feed.as_of)を呼んで前進させてください。ビルド失敗時に呼ばなければ、次回に同じ差分を再取得できます。- 既存のサイト外部連携(GitHub Actions/Webhook)でビルドを起動し、その中で
cms.changesを使ってどのページを再生成するかを決める、という補完関係です。 - 実装例は
apps/samples/html(Astro 静的サイトの増分ビルド)を参照してください。
フォーム(配信・送信)
cms.forms.get(key) でフォームの項目定義(fields)と、Turnstile 設定・送信速度チェック用トークン(speed_check_token)を取得し、cms.forms.submit(key, input) で送信します。
// 1. 配信:項目定義とトークンを取得(都度発行のためキャッシュ不可)
const form = await cms.forms.get('contact')
// 2. 送信:各項目 name をキーに値を渡す
const result = await cms.forms.submit('contact', {
values: {
name: '山田 太郎',
email: '[email protected]',
message: '製品について問い合わせたいことがあります。',
},
// 送信速度チェックが有効なフォームでは、配信で受け取ったトークンをそのまま返す
speedCheckToken: form.speed_check_token,
// Turnstile 有効フォームでは、ウィジェットが発行したトークンを渡す
turnstileToken: form.turnstile.enabled ? widgetToken : undefined,
})
console.log(result.id, result.submitted_at)- スパム対策(Turnstile/レートリミット/ハニーポット/送信速度/重複送信)はサーバ側で適用されます。ハニーポット項目は SDK が空値で自動付与します。
- ファイル項目(
type: 'file') は値にFile/Blobを渡します。File/Blob を含む送信は自動でmultipart/form-dataになり、ファイルはサーバ側でS3に保存されます(送信データにはファイル参照が格納されます)。1ファイル最大10MB/許可形式は画像・PDF・Word・Excel・テキスト。 - Turnstile 有効時は
form.turnstile.site_keyでフロントにウィジェットを描画し、得たトークンをturnstileTokenに渡してください(secret_keyはAPIから返りません)。
メンテナンス状態
cms.maintenance.get() でサイトのメンテナンス状態を取得します。投稿・FAQ 等の利用コンテンツ設定に関わらず常に応答し、状態を即時反映するためキャッシュされません(表示のたびに取得してください)。
const status = await cms.maintenance.get()
if (status.maintenance) {
// メンテナンス中:見出し・案内・再開予定を表示
console.log(status.title, status.message, status.resumes_at)
} else {
// 通常表示
}maintenance を判別子とする直和型(Maintenance)です。maintenance: false のときは他のフィールドを持ちません。
リッチテキストのレンダリング
import { renderProseMirror, extractText } from '@sharedan/cms-sdk'
const html = renderProseMirror(detail.body) // HTML文字列
const html2 = renderProseMirror(faq.answer, { // リンク属性のカスタマイズ
linkTarget: '_self',
linkRel: null,
})
const excerpt = extractText(detail.body).slice(0, 120) // プレーンテキスト抜粋対応ノード: paragraph / heading / bulletList / orderedList / listItem /
taskList / taskItem / blockquote / codeBlock / horizontalRule /
hardBreak / image / fileAttachment / customBlock
対応マーク: bold / italic / underline / strike / code / textStyle(文字色) /
highlight(ハイライト) / link
画像(image)は、管理側で表示サイズを設定した画像の <img> に width / height(px)属性を
付与します。これによりブラウザが読み込み前に縦横比を確保でき、画像読み込み時のレイアウトのずれ(CLS)を
防げます(サイズ未設定の画像・SVG では属性を付けません)。レスポンシブ表示には、サイト側のCSSで
img { max-width: 100%; height: auto; } を併用してください。
React などで使う場合は、出力を dangerouslySetInnerHTML に渡すか、renderProseMirror
を参考に独自レンダラを実装してください。
カスタムブロック(customBlock)
テナントが投稿・固定コンテンツの本文中に挿入した「カスタムブロック」です。ブロックの種別は管理画面で
定義(ブロック名・ブロックID)し、本文はテナントがそのブロックの中に直接書きます。本文JSONには
{ type: 'customBlock', attrs: { blockId, name }, content: [ …本文… ] } として格納され、
renderProseMirror() は中の本文を <div data-custom-block data-block-id="…" data-name="…">…</div>
として描画します。そのまま表示できるほか、data-block-id(ブロック種別)ごとに装飾したり、独自
コンポーネントへ差し替えたり(hydrate)することもできます。
const html = renderProseMirror(detail.body)
element.innerHTML = html // 各カスタムブロックの本文はそのまま描画される
// 種別ごとに装飾・差し替えたい場合は data 属性で判別する
for (const el of element.querySelectorAll('[data-custom-block]')) {
const blockId = el.dataset.blockId // 例: 'pricing-table'
el.classList.add(`block--${blockId}`) // 種別ごとにスタイルを当てる など
}エラーハンドリング
import { ShareDanCmsApiError, ShareDanCmsNetworkError } from '@sharedan/cms-sdk'
try {
const content = await cms.contents.get('top-message')
} catch (err) {
if (err instanceof ShareDanCmsApiError) {
if (err.isNotFound) { /* 404: 未存在/利用しない設定 */ }
console.error(err.status, err.code, err.details)
} else if (err instanceof ShareDanCmsNetworkError) {
// 接続不可・タイムアウト・CORS など
}
}フォーム送信では、スパム対策・バリデーションの結果を判定できます。
try {
await cms.forms.submit('contact', { values, speedCheckToken: form.speed_check_token })
} catch (err) {
if (err instanceof ShareDanCmsApiError) {
if (err.isBadRequest) { /* 400: Turnstile検証失敗など */ }
if (err.isValidationError) { /* 422: 入力不正・ハニーポット・送信速度・重複送信 */ }
if (err.isRateLimited) {
// 429: レート制限。err.retryAfter 秒後に再試行
console.warn('retry after', err.retryAfter, 'seconds')
}
}
}開発
npm install # typescript を取得
npm run build # dist/ に ESM + 型定義を出力
node examples/node.mjs # ローカルCMS(http://localhost:8091)に対するスモークテストライセンス
MIT © ShareDan CO. ,Ltd.
