npm package: @rayhanadev/notion-fs
Description: A filesystem-like API for reading and writing Notion pages as Markdown
Author: Rayhan Noufal Arayilakath
Latest version: 0.1.0

# @rayhanadev/notion-fs

A filesystem-like API for reading and writing Notion pages as Markdown. Built with [Effect](https://effect.website).

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

```bash
bun add @rayhanadev/notion-fs
```

Peer dependencies: `effect` (>=3.0.0), `typescript` (^5).

## Usage

```ts
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` |

```ts
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:

```ts
import type { FsError } from "@rayhanadev/notion-fs";
```

### Effect API

For direct Effect usage with services and layers, import from `@rayhanadev/notion-fs/effect`:

```ts
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](./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 classes
```

## Architecture

The project is organized into two layers:

- **`api`** — Low-level wrapper around `@notionhq/client` with 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

```bash
# Unit tests
bun test

# With coverage
bun test --coverage
```

E2E 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 |