npm package: @qyubit/bridge-sdk
Description: SDK for implementing the CMS Bridge API on your website
Author: qyubit
Latest version: 1.0.0

# @cms/bridge-sdk

SDK for implementing the CMS Bridge API on your website. This allows your website to connect with the CMS dashboard for centralized content management.

## Installation

```bash
npm install @cms/bridge-sdk
# or
pnpm add @cms/bridge-sdk
```

## Quick Start

### With Hono

```typescript
import { Hono } from 'hono'
import { serve } from '@hono/node-server'
import { createHonoBridge } from '@cms/bridge-sdk/hono'
import { myRepository } from './repository'

const app = new Hono()

// Create bridge routes
const bridgeRoutes = createHonoBridge(
  {
    apiKey: process.env.BRIDGE_API_KEY!,
    apiSecret: process.env.BRIDGE_API_SECRET!,
    siteName: 'My Blog',
  },
  myRepository
)

// Mount at /bridge
app.route('/bridge', bridgeRoutes)

serve({ fetch: app.fetch, port: 3000 })
```

### With Express

```typescript
import express from 'express'
import { createExpressBridge } from '@cms/bridge-sdk/express'
import { myRepository } from './repository'

const app = express()
app.use(express.json())

// Create bridge routes
const bridgeRouter = createExpressBridge(
  {
    apiKey: process.env.BRIDGE_API_KEY!,
    apiSecret: process.env.BRIDGE_API_SECRET!,
    siteName: 'My Blog',
  },
  myRepository
)

// Mount at /bridge
app.use('/bridge', bridgeRouter)

app.listen(3000)
```

## Implementing the Repository

You need to implement the `BridgeRepository` interface to connect the SDK to your database:

```typescript
import type { BridgeRepository } from '@cms/bridge-sdk'
import { db } from './db' // Your database client

export const myRepository: BridgeRepository = {
  // Posts
  async getPosts({ page, limit, status, search }) {
    const offset = (page - 1) * limit

    // Build your query
    let query = db.select().from(posts)

    if (status) {
      query = query.where(eq(posts.status, status))
    }

    if (search) {
      query = query.where(like(posts.title, `%${search}%`))
    }

    const data = await query.limit(limit).offset(offset)
    const total = await db.select({ count: count() }).from(posts)

    return {
      data,
      meta: {
        page,
        limit,
        total: total[0].count,
        totalPages: Math.ceil(total[0].count / limit),
      },
    }
  },

  async getPost(id) {
    const post = await db.select().from(posts).where(eq(posts.id, id)).limit(1)
    return post[0] || null
  },

  async createPost(data) {
    const slug = data.slug || slugify(data.title)
    const [post] = await db.insert(posts).values({
      ...data,
      slug,
    }).returning()
    return post
  },

  async updatePost(id, data) {
    const [post] = await db.update(posts)
      .set(data)
      .where(eq(posts.id, id))
      .returning()
    return post || null
  },

  async deletePost(id) {
    const result = await db.delete(posts).where(eq(posts.id, id))
    return result.rowCount > 0
  },

  // Pages - similar to posts
  async getPages({ page, limit, status }) {
    // ... implement
  },

  async getPage(id) {
    // ... implement
  },

  async createPage(data) {
    // ... implement
  },

  async updatePage(id, data) {
    // ... implement
  },

  async deletePage(id) {
    // ... implement
  },

  // Media
  async getMedia({ page, limit }) {
    // ... implement
  },

  async getMediaItem(id) {
    // ... implement
  },

  async deleteMedia(id) {
    // ... implement
  },

  // Categories
  async getCategories() {
    return db.select().from(categories)
  },

  async createCategory(data) {
    const slug = data.slug || slugify(data.name)
    const [category] = await db.insert(categories).values({
      ...data,
      slug,
    }).returning()
    return category
  },

  async deleteCategory(id) {
    const result = await db.delete(categories).where(eq(categories.id, id))
    return result.rowCount > 0
  },

  // Tags
  async getTags() {
    return db.select().from(tags)
  },

  async createTag(data) {
    const slug = data.slug || slugify(data.name)
    const [tag] = await db.insert(tags).values({
      ...data,
      slug,
    }).returning()
    return tag
  },

  async deleteTag(id) {
    const result = await db.delete(tags).where(eq(tags.id, id))
    return result.rowCount > 0
  },
}
```

## API Endpoints

Once mounted, the following endpoints are available:

### Authentication
- `POST /bridge/auth/verify` - Verify API credentials

### Posts
- `GET /bridge/posts` - List posts (supports `?page=`, `?limit=`, `?status=`, `?search=`)
- `GET /bridge/posts/:id` - Get single post
- `POST /bridge/posts` - Create post
- `PUT /bridge/posts/:id` - Update post
- `DELETE /bridge/posts/:id` - Delete post

### Pages
- `GET /bridge/pages` - List pages
- `GET /bridge/pages/:id` - Get single page
- `POST /bridge/pages` - Create page
- `PUT /bridge/pages/:id` - Update page
- `DELETE /bridge/pages/:id` - Delete page

### Media
- `GET /bridge/media` - List media
- `GET /bridge/media/:id` - Get single media item
- `DELETE /bridge/media/:id` - Delete media

### Categories
- `GET /bridge/categories` - List categories
- `POST /bridge/categories` - Create category
- `DELETE /bridge/categories/:id` - Delete category

### Tags
- `GET /bridge/tags` - List tags
- `POST /bridge/tags` - Create tag
- `DELETE /bridge/tags/:id` - Delete tag

## Authentication

All endpoints (except `/bridge/auth/verify`) require authentication via headers:

```
X-Bridge-Key: your-api-key
X-Bridge-Secret: your-api-secret
```

## Configuration

```typescript
interface BridgeConfig {
  // Required: API credentials (generate secure random strings)
  apiKey: string
  apiSecret: string

  // Optional: Site name returned in auth verification
  siteName?: string

  // Optional: Permissions to grant (default: ['read', 'write', 'delete'])
  permissions?: string[]
}
```

## TypeScript Support

All types are fully exported:

```typescript
import type {
  Post,
  Page,
  Media,
  Category,
  Tag,
  BridgeRepository,
  PaginatedResponse,
} from '@cms/bridge-sdk'
```

## License

MIT