@rayhanadev/notion-fs
v0.1.0
Published
A filesystem-like API for reading and writing Notion pages as Markdown
Maintainers
Readme
@rayhanadev/notion-fs
A filesystem-like API for reading and writing Notion pages as Markdown. Built with Effect.
Treats Notion pages like files: read a page as Markdown, write Markdown back, append content, readdir to list child pages, mkdir to create new pages, and walk to traverse a page tree.
Install
bun add @rayhanadev/notion-fsPeer dependencies: effect (>=3.0.0), typescript (^5).
Usage
import { createClient } from "@rayhanadev/notion-fs";
const notion = createClient({ token: "ntn_..." });
// Read a page as Markdown
const md = await notion.read("page-id");
// Replace a page's content with new Markdown
await notion.write("page-id", "# Hello\n\nWorld");
// Append Markdown to the end of a page
await notion.append("page-id", "\n## New section");
// List child pages and databases
const entries = await notion.readdir("page-id");
// Create a child page
const newPageId = await notion.mkdir("parent-id", "New Page");
// Get page metadata
const meta = await notion.stat("page-id");
// Archive (soft-delete) a page
await notion.trash("page-id");
// Permanently delete a page
await notion.remove("page-id");
// Move a page to a new parent
await notion.move("page-id", "new-parent-id");
// Walk the page tree
const tree = await notion.walk("root-page-id");Error Handling
All methods throw typed errors that extend Error:
| Error | When | Fields |
| ------------------- | ------------------------------------ | ------------------- |
| FileNotFoundError | The page does not exist | pageId, message |
| FileReadError | A read operation failed | pageId, message |
| FileWriteError | A write/delete/move operation failed | pageId, message |
import { createClient, FileNotFoundError } from "@rayhanadev/notion-fs";
const notion = createClient({ token: "ntn_..." });
try {
await notion.read("page-id");
} catch (error) {
if (error instanceof FileNotFoundError) {
console.log(`Page ${error.pageId} not found`);
}
}The union type FsError is also exported for convenience:
import type { FsError } from "@rayhanadev/notion-fs";Effect API
For direct Effect usage with services and layers, import from @rayhanadev/notion-fs/effect:
import { NotionApiClient, NotionFs } from "@rayhanadev/notion-fs/effect";
import { Effect, Layer } from "effect";
const ApiLayer = NotionApiClient.make("ntn_...");
const FsLayer = NotionFs.Default.pipe(Layer.provide(ApiLayer));
// Using static accessors
const program = NotionFs.read("page-id").pipe(
Effect.tap((md) => Effect.log(md)),
Effect.catchTag("FileNotFoundError", (e) => Effect.log(`Page ${e.pageId} not found`)),
);
Effect.runPromise(program.pipe(Effect.provide(FsLayer)));See DOCUMENTATION.md for the full API reference.
Project Structure
src/
├── index.ts # Public exports (createClient, error types)
├── client.ts # Promise-based client factory
├── effect.ts # Effect re-exports (services, errors)
├── types.ts # Public types (ClientOptions, NotionFsClient)
├── api/
│ ├── index.ts # NotionApiClient (Notion SDK wrapper)
│ ├── types.ts # API types (NotionApiClientShape, NotionError)
│ └── errors.ts # Notion API error classes
└── fs/
├── index.ts # NotionFs service (filesystem-like API)
├── types.ts # FS types (PageMeta, DirEntry, TreeNode)
└── errors.ts # Filesystem error classesArchitecture
The project is organized into two layers:
api— Low-level wrapper around@notionhq/clientwith typed error handling.fs— High-level filesystem-like operations built on the API layer.
The public API (createClient) wraps the Effect internals in promises. For direct Effect usage, import from @rayhanadev/notion-fs/effect.
Testing
# Unit tests
bun test
# With coverage
bun test --coverageE2E tests require NOTION_API_TOKEN and NOTION_TEST_PAGE_ID set in .env.local.
Scripts
| Command | Description |
| ------------------- | ------------------- |
| bun run lint | Lint with oxlint |
| bun run format | Format with oxfmt |
| bun run typecheck | Type-check with tsc |
