Markdown ベースのドキュメントを型安全に管理する TypeScript ライブラリ。
- Markdown ファイルの読み書きと FrontMatter メタデータ管理
- Zod スキーマによる型安全なバリデーション
- ドキュメント間のリレーション
- 複数ストレージバックエンド(Node.js, GitHub, JSON, Mock)
- アーカイブ/リストア
- ファイルツリー生成
.safeプロキシによるエラーハンドリング
bun add @interactive-inc/docs-client
# or
npm install @interactive-inc/docs-clientimport { DocClient } from "@interactive-inc/docs-client"
import { DocFileSystemNode } from "@interactive-inc/docs-client/file-system-node"
const fileSystem = new DocFileSystemNode({ basePath: "./docs" })
const client = new DocClient({ fileSystem })
// .safe を使うと throw の代わりに Error を返す
const entity = await client.safe.mdFile("articles/hello.md").read()
if (!(entity instanceof Error)) {
console.log(entity.content().title)
console.log(entity.content().body)
}
// try/catch でも使える
try {
const entity = await client.mdFile("articles/hello.md").read()
console.log(entity.content().title)
} catch (error) {
console.error(error)
}メインのエントリポイント。
const client = new DocClient({
fileSystem,
config: {
defaultIndexIcon: "📃",
indexFileName: "index.md",
archiveDirectoryName: "_",
defaultDirectoryName: "Directory",
indexMetaIncludes: [],
directoryExcludes: [".vitepress"],
metaFileName: ".meta.json",
},
})ファイルやディレクトリへの遅延参照。read() を呼ぶまで I/O は発生しない。
const fileRef = client.mdFile("articles/hello.md")
const dirRef = client.directory("articles")
const indexRef = client.indexFile("articles")読み取り後の不変データオブジェクト。
const entity = await fileRef.read()
const title = entity.content().title
const body = entity.content().body
const description = entity.content().description
const meta = entity.content().meta()
// with*() で新しいインスタンスを作成(不変)
const updated = entity.withTitle("New Title")Reference や DocClient のメソッドは通常 throw でエラーを伝播する。.safe プロキシを使うと、全 async メソッドが T | Error を返す。
// throw する(try/catch が必要)
const entity = await fileRef.read()
// Error を返す(instanceof チェック)
const entity = await fileRef.safe.read()
if (entity instanceof Error) {
console.error(entity.message)
return
}
console.log(entity.content().title)const fileRef = client.mdFile("articles/hello.md")
const entity = await fileRef.read()
// ディレクトリ内の全ファイルを読み取り
const dirRef = client.directory("articles")
const entities = await dirRef.readMdFiles()
// 生テキストとして読み取り
const text = await fileRef.readText()const entity = fileRef.empty()
const updated = entity.withTitle("Hello World").withDescription("My first article")
await fileRef.write(updated)
// 生テキストで書き込み
await fileRef.writeText("# Hello\n\nContent here")
// デフォルトコンテンツで新規ファイル作成
await dirRef.createMdFile("new-article.md")ファイルをアーカイブディレクトリ(デフォルト: _/)に移動。
const archivedRef = await fileRef.archive()
const restoredRef = await archivedRef.restore()const exists = await fileRef.exists()
const size = await fileRef.size()
const lastModified = await fileRef.lastModified()
const createdAt = await fileRef.createdAt()FrontMatter メタデータの構造をスキーマで定義する。
ディレクトリに .meta.json を配置:
{
"icon": "📄",
"schema": {
"title": {
"type": "text",
"required": true,
"title": "Title",
"description": "Document title",
"default": null
},
"category": {
"type": "select-text",
"required": false,
"title": "Category",
"description": null,
"options": ["tutorial", "reference", "guide"],
"default": null
},
"tags": {
"type": "multi-text",
"required": false,
"title": "Tags",
"description": null,
"default": []
},
"author": {
"type": "relation",
"required": false,
"title": "Author",
"description": null,
"path": "authors",
"default": null
}
}
}.meta.json がなければ index.md の FrontMatter からスキーマを読み取る:
---
icon: 📄
schema:
title:
type: text
required: true
category:
type: select-text
options:
- tutorial
- reference
---
# Articles
Directory description here.defineSchema で型安全にスキーマを定義:
import { defineSchema, docCustomSchemaField } from "@interactive-inc/docs-client"
const articleSchema = defineSchema({
title: { type: "text", required: true },
views: { type: "number", required: false },
published: { type: "boolean", required: true },
category: { type: "select-text", required: false },
tags: { type: "multi-text", required: false },
author: docCustomSchemaField.relation(false, "authors"),
relatedArticles: docCustomSchemaField.multiRelation(false, "articles"),
})
// スキーマ付きでディレクトリを取得
const dirRef = client.directory("articles", articleSchema)
const fileRef = dirRef.mdFile("hello")
const entity = await fileRef.read()
// 型安全なメタデータアクセス
const title = entity.content().meta().field("title") // string
const views = entity.content().meta().field("views") // number | null| Type | Value Type | Description |
|---|---|---|
text |
string |
単一テキスト |
number |
number |
数値 |
boolean |
boolean |
真偽値 |
select-text |
string |
テキスト選択肢から単一選択 |
select-number |
number |
数値選択肢から単一選択 |
multi-text |
string[] |
複数テキスト |
multi-number |
number[] |
複数数値 |
multi-select-text |
string[] |
テキスト選択肢から複数選択 |
multi-select-number |
number[] |
数値選択肢から複数選択 |
relation |
string |
他ドキュメントへの参照 |
multi-relation |
string[] |
他ドキュメントへの複数参照 |
ドキュメント間のリレーション。
const pageSchema = defineSchema({
features: docCustomSchemaField.multiRelation(true, "features"),
})
const dirRef = client.directory("pages", pageSchema)
const pageRef = dirRef.mdFile("dashboard")
// 関連ドキュメントの参照を取得
const featureRefs = await pageRef.relations("features")
for (const featureRef of featureRefs) {
const feature = await featureRef.read()
console.log(feature.content().title)
}const dirRef = client.directory("articles")
// 一覧取得
const fileNames = await dirRef.fileNames()
const dirNames = await dirRef.directoryNames()
// 参照取得
const files = await dirRef.files()
const mdFiles = await dirRef.mdFiles()
const subdirs = await dirRef.directories()
// 全ファイル読み取り
const entities = await dirRef.readMdFiles()
// インデックスファイル読み取り
const indexEntity = await dirRef.readIndexFile()
// サブディレクトリ
const subDirRef = dirRef.directory("tutorials")
// 新規ファイル作成
const newFileRef = await dirRef.createMdFile("new-article.md")ドキュメントの階層ツリー構造を生成。
const tree = await client.fileTree()
const dirTree = await client.directoryTree()
// ツリーノードの型
type DocTreeFileNode = {
type: "file"
name: string
path: string
icon: string
title: string
}
type DocTreeDirectoryNode = {
type: "directory"
name: string
path: string
icon: string
title: string
children: DocTreeNode[]
}
type DocTreeNode = DocTreeFileNode | DocTreeDirectoryNodeローカルファイル操作用:
import { DocFileSystemNode } from "@interactive-inc/docs-client/file-system-node"
const fileSystem = new DocFileSystemNode({ basePath: "./docs" })Read/Write を個別に指定する場合:
import { DocFileSystemNodeRead, DocFileSystemNodeWrite } from "@interactive-inc/docs-client/file-system-node"
import { DocFileSystem } from "@interactive-inc/docs-client/file-system"
const reader = new DocFileSystemNodeRead({ basePath: "./docs" })
const writer = new DocFileSystemNodeWrite({ basePath: "./docs", reader })
const fileSystem = new DocFileSystem({ basePath: "./docs", reader, writer })GitHub リポジトリからの読み取り:
import { DocFileSystemOctokitRead } from "@interactive-inc/docs-client/file-system-octokit"
import { DocFileSystemNodeWrite } from "@interactive-inc/docs-client/file-system-node"
import { DocFileSystem } from "@interactive-inc/docs-client/file-system"
const reader = new DocFileSystemOctokitRead({
token: process.env.GITHUB_TOKEN,
owner: "your-org",
repo: "your-repo",
basePath: "docs",
branch: "main",
})
const writer = new DocFileSystemNodeWrite({
basePath: "/tmp/docs-output",
})
const fileSystem = new DocFileSystem({
basePath: "docs",
reader,
writer,
})インメモリ操作用:
import { DocFileSystemJson } from "@interactive-inc/docs-client/file-system-json"
const fileSystem = new DocFileSystemJson({
basePath: "docs",
data: {
"index.md": "# Home\n\nWelcome!",
"articles/hello.md": "# Hello\n\nContent here.",
},
})テスト用:
import { DocFileSystemMock } from "@interactive-inc/docs-client/file-system-mock"
const fileSystem = new DocFileSystemMock({ basePath: "docs" })MIT