npm package: @mybe/sdk
Description: A TypeScript SDK for fetching content from Mybe CMS.
Latest version: 1.1.2

# Mybe CMS SDK

A TypeScript SDK for fetching content from Mybe CMS.

## Installation

```bash
npm install @mybe/sdk
```

## Features

- ✅ **Zero Dependencies** - Uses native fetch API
- ✅ **TypeScript Support** - Full type safety with TypeScript
- ✅ **Environment Support** - Query content from different environments (master, staging, development)
- ✅ **Error Handling** - Custom error classes for better error handling
- ✅ **Pagination Support** - Built-in pagination for large datasets
- ✅ **Status Filtering** - Filter content by status (draft, published, archived)
- ✅ **Locale Filtering** - Filter content by locale for multi-language support
- ✅ **GraphQL Support** - Powerful GraphQL API for flexible data querying

## Quick Start

```typescript
import { MybeSDK } from '@mybe/sdk';

// Initialize the SDK
const sdk = new MybeSDK({
  apiKey: 'your-api-key'
});

// Fetch a single content entry
const content = await sdk.getContent('content-id');

// Fetch content by type with filters
const contentList = await sdk.getContentByType('content-type-id', {
  status: 'published',
  limit: 10
});
```

## Configuration

### Basic Configuration

```typescript
const sdk = new MybeSDK({
  apiKey: 'your-api-key'
  // Defaults to 'master' environment
});
```

### Environment Configuration

Mybe CMS supports multiple environments for content isolation. Each SDK instance is locked to a specific environment.

```typescript
// Production environment (default)
const prodClient = new MybeSDK({
  apiKey: 'your-api-key',
  environment: 'master' // or omit for default
});

// Staging environment
const stagingClient = new MybeSDK({
  apiKey: 'your-api-key',
  environment: 'staging'
});

// Development environment
const devClient = new MybeSDK({
  apiKey: 'your-api-key',
  environment: 'development'
});

// Custom environment (by slug or UUID)
const customClient = new MybeSDK({
  apiKey: 'your-api-key',
  environment: 'my-custom-env'
});
```

**Environment Best Practices:**

1. **Master Environment** - Your production content (default)
2. **Staging Environment** - Test changes before production
3. **Development Environment** - Active development work
4. **Custom Environments** - Feature branches, testing, etc.

**Important:** Each client instance is locked to its environment. To query different environments, create separate client instances.

```typescript
// ✅ Correct: Separate clients for different environments
const prodPosts = await prodClient.getContentByType('blog-post-id');
const stagingPosts = await stagingClient.getContentByType('blog-post-id');

// ❌ Incorrect: Cannot switch environments on the same client
// You must create a new client instance
```

### Custom Base URL (Optional)

```typescript
const sdk = new MybeSDK({
  apiKey: 'your-api-key',
  baseUrl: 'https://custom-api.example.com/api/v1',
  environment: 'staging'
});
```

### Getting Current Environment

```typescript
const sdk = new MybeSDK({
  apiKey: 'your-api-key',
  environment: 'staging'
});

console.log(sdk.getEnvironment()); // 'staging'
```

## API Reference

### Content Models (Content Types)

#### `getContentModels(projectId: string)`

Get all content models for a project.

```typescript
const contentModels = await sdk.getContentModels('project-id');
```

**Returns:** `Promise<ContentType[]>`

#### `getContentModel(contentTypeId: string)`

Get a specific content model by ID.

```typescript
const contentModel = await sdk.getContentModel('content-type-id');
```

**Returns:** `Promise<ContentType>`

### Content Entries

#### `getContent(contentId: string)`

Get a single content entry by ID.

```typescript
const content = await sdk.getContent('content-id');
```

**Returns:** `Promise<ContentEntry>`

#### `getContentByType(contentTypeId: string, options?: ContentFilterOptions)`

Get all content entries for a specific content type.

```typescript
const result = await sdk.getContentByType('content-type-id', {
  status: 'published',
  limit: 20,
  lastKey: 'pagination-key' // For pagination
});

console.log(result.data); // Array of content entries
console.log(result.pagination.hasMore); // Boolean
console.log(result.pagination.lastEvaluatedKey); // For next page
```

**Options:**
- `status?: 'draft' | 'published' | 'archived'` - Filter by status
- `locale?: string` - Filter by locale (e.g., 'en-US', 'bn-BD', 'fr-FR', 'es-ES')
- `limit?: number` - Number of items per page
- `lastKey?: string` - Pagination key from previous response

**Returns:** `Promise<ContentListResponse>`

#### `getContentByProject(projectId: string, options?: ContentFilterOptions)`

Get all content entries for a project.

```typescript
const result = await sdk.getContentByProject('project-id', {
  status: 'published',
  limit: 20
});
```

**Options:**
- `status?: 'draft' | 'published' | 'archived'` - Filter by status
- `locale?: string` - Filter by locale (e.g., 'en-US', 'bn-BD', 'fr-FR', 'es-ES')
- `limit?: number` - Number of items per page
- `lastKey?: string` - Pagination key from previous response

**Returns:** `Promise<ContentListResponse>`

## Pagination Example

```typescript
let lastKey: string | undefined;
let allContent: ContentEntry[] = [];

do {
  const result = await sdk.getContentByType('content-type-id', {
    status: 'published',
    limit: 50,
    lastKey: lastKey ? JSON.stringify(result.pagination.lastEvaluatedKey) : undefined
  });

  allContent = [...allContent, ...result.data];
  lastKey = result.pagination.hasMore 
    ? JSON.stringify(result.pagination.lastEvaluatedKey) 
    : undefined;

} while (lastKey);

console.log(`Fetched ${allContent.length} total items`);
```

## Error Handling

The SDK provides custom error classes for better error handling:

```typescript
import { 
  MybeSDK, 
  NotFoundError, 
  UnauthorizedError,
  ValidationError,
  ServerError 
} from '@mybe/sdk';

try {
  const content = await sdk.getContent('content-id');
} catch (error) {
  if (error instanceof NotFoundError) {
    console.error('Content not found');
  } else if (error instanceof UnauthorizedError) {
    console.error('Invalid API key');
  } else if (error instanceof ValidationError) {
    console.error('Validation error:', error.message);
  } else if (error instanceof ServerError) {
    console.error('Server error:', error.message);
  } else {
    console.error('Unknown error:', error);
  }
}
```

### Error Types

- `MybeSDKError` - Base error class
- `NotFoundError` - Resource not found (404)
- `UnauthorizedError` - Invalid API key (401)
- `ForbiddenError` - Insufficient permissions (403)
- `ValidationError` - Validation failed (400)
- `ServerError` - Internal server error (500)

## TypeScript Types

The SDK exports all TypeScript types for your convenience:

```typescript
import type {
  ContentType,
  ContentEntry,
  ContentFilterOptions,
  PaginationResponse,
  APIResponse,
  ContentListResponse
} from '@mybe/sdk';
```

### ContentType

```typescript
interface ContentType {
  id: string;
  project_id: string;
  name: string;
  slug: string;
  description?: string;
  created_at: string;
  updated_at: string;
}
```

### ContentEntry

```typescript
interface ContentEntry {
  id: string;
  content_type_id: string;
  project_id: string;
  slug?: string;
  status: 'draft' | 'published' | 'archived';
  data: Record<string, any>;
  locale?: string;
  created_by: string;
  updated_by?: string;
  published_at?: string;
  created_at: string;
  updated_at: string;
}
```

## Examples

### Environment-based Workflows

#### Development → Staging → Production

```typescript
import { MybeSDK } from '@mybe/sdk';

// Create separate clients for each environment
const devClient = new MybeSDK({
  apiKey: 'your-api-key',
  environment: 'development'
});

const stagingClient = new MybeSDK({
  apiKey: 'your-api-key',
  environment: 'staging'
});

const prodClient = new MybeSDK({
  apiKey: 'your-api-key',
  environment: 'master'
});

// Development: Work with draft content
const draftPosts = await devClient.getContentByType('blog-post-id', {
  status: 'draft',
  limit: 10
});

// Staging: Test published content
const stagingPosts = await stagingClient.getContentByType('blog-post-id', {
  status: 'published',
  limit: 10
});

// Production: Serve live content
const livePosts = await prodClient.getContentByType('blog-post-id', {
  status: 'published',
  limit: 10
});
```

#### Environment-aware Application

```typescript
// Automatically select environment based on NODE_ENV
const getSDKClient = () => {
  const apiKey = process.env.MYBE_API_KEY!;
  
  switch (process.env.NODE_ENV) {
    case 'production':
      return new MybeSDK({ apiKey, environment: 'master' });
    case 'staging':
      return new MybeSDK({ apiKey, environment: 'staging' });
    case 'development':
    default:
      return new MybeSDK({ apiKey, environment: 'development' });
  }
};

const sdk = getSDKClient();
console.log(`Using environment: ${sdk.getEnvironment()}`);
```

#### Preview Mode (Staging vs Production)

```typescript
// Next.js example: Preview mode with environments
export async function getStaticProps({ preview = false }) {
  const sdk = new MybeSDK({
    apiKey: process.env.MYBE_API_KEY!,
    environment: preview ? 'staging' : 'master'
  });
  
  const posts = await sdk.getContentByType('blog-post-id', {
    status: preview ? 'draft' : 'published',
    limit: 20
  });
  
  return {
    props: {
      posts: posts.data,
      preview
    },
    revalidate: 60
  };
}
```

### Fetch Published Blog Posts

```typescript
const sdk = new MybeSDK({
  apiKey: 'your-api-key',
  environment: 'master' // Production content
});

const posts = await sdk.getContentByType('blog-post-type-id', {
  status: 'published',
  limit: 10
});

posts.data.forEach(post => {
  console.log(post.data.title);
  console.log(post.data.content);
});
```

### Fetch All Content Models

```typescript
const sdk = new MybeSDK({ apiKey: 'your-api-key' });

const contentModels = await sdk.getContentModels('project-id');

contentModels.forEach(model => {
  console.log(`${model.name} (${model.slug})`);
});
```

### Fetch Single Content Entry

```typescript
const sdk = new MybeSDK({ apiKey: 'your-api-key' });

const content = await sdk.getContent('content-entry-id');

console.log(content.data); // Your content data
console.log(content.status); // draft | published | archived
```

### Compare Content Across Environments

```typescript
const masterClient = new MybeSDK({
  apiKey: 'your-api-key',
  environment: 'master'
});

const stagingClient = new MybeSDK({
  apiKey: 'your-api-key',
  environment: 'staging'
});

// Fetch from both environments in parallel
const [masterContent, stagingContent] = await Promise.all([
  masterClient.getContentByType('blog-post-id', { limit: 10 }),
  stagingClient.getContentByType('blog-post-id', { limit: 10 })
]);

console.log(`Master has ${masterContent.data.length} posts`);
console.log(`Staging has ${stagingContent.data.length} posts`);

// Find differences
const masterIds = new Set(masterContent.data.map(p => p.id));
const stagingOnly = stagingContent.data.filter(p => !masterIds.has(p.id));
console.log(`${stagingOnly.length} posts only in staging`);
```

### Multi-locale Content

Fetch content in different languages using the locale filter. **TypeScript will autocomplete all supported locale codes** when you type `locale: ''`:

```typescript
const sdk = new MybeSDK({ apiKey: 'your-api-key' });

// Fetch English content
// TypeScript autocompletes: 'en-US', 'en-GB', 'en-CA', 'en-AU'
const englishPosts = await sdk.getContentByType('blog-post-type-id', {
  status: 'published',
  locale: 'en-US', // ✨ Autocomplete suggests all 31 supported locales!
  limit: 10
});

// Fetch Bangla content
const banglaPosts = await sdk.getContentByType('blog-post-type-id', {
  status: 'published',
  locale: 'bn-BD', // ✨ Type-safe locale codes
  limit: 10
});

// Fetch French content
const frenchPosts = await sdk.getContentByType('blog-post-type-id', {
  status: 'published',
  locale: 'fr-FR',
  limit: 10
});

// Fetch Spanish content
const spanishPosts = await sdk.getContentByType('blog-post-type-id', {
  status: 'published',
  locale: 'es-ES',
  limit: 10
});
```

**TypeScript Autocomplete Support:**

The SDK provides full TypeScript autocomplete for all 31 supported locales:

```typescript
import { SUPPORTED_LOCALES, type SupportedLocale } from '@mybe/sdk';

// View all supported locales
console.log(SUPPORTED_LOCALES);
// ['en-US', 'bn-BD', 'fr-FR', 'es-ES', 'de-DE', 'it-IT', ...]

// Use the type for type-safe locale handling
const userLocale: SupportedLocale = 'ja-JP'; // ✅ Valid
const invalidLocale: SupportedLocale = 'invalid'; // ❌ TypeScript error!
```

**Building a Multi-language Website:**

```typescript
// Get user's preferred language
const userLocale = getUserPreferredLocale(); // e.g., 'bn-BD'

// Fetch content in user's language
const localizedContent = await sdk.getContentByType('content-type-id', {
  status: 'published',
  locale: userLocale,
  limit: 20
});

// Display localized content
localizedContent.data.forEach(item => {
  console.log(item.data.title); // Title in user's language
  console.log(item.metadata.locale); // e.g., 'bn-BD'
});
```

**All Supported Locales:**

The SDK supports 31 locales with full TypeScript autocomplete:

- **English**: `en-US`, `en-GB`, `en-CA`, `en-AU`
- **European**: `fr-FR`, `es-ES`, `de-DE`, `it-IT`, `pt-PT`, `nl-NL`, `sv-SE`, `pl-PL`, `ru-RU`
- **Americas**: `pt-BR`, `es-MX`, `es-AR`, `fr-CA`
- **Asia-Pacific**: `ja-JP`, `ko-KR`, `zh-CN`, `zh-TW`, `hi-IN`, `th-TH`, `vi-VN`, `id-ID`, `ms-MY`, `fil-PH`, `bn-BD`
- **Middle East & Africa**: `ar-SA`, `tr-TR`, `he-IL`

**Note:** When you type `locale: ''` in your IDE, TypeScript will show all available options!

## GraphQL API

Mybe CMS provides a powerful GraphQL API for flexible data querying. Use GraphQL when you need advanced filtering, want to request specific fields only, or prefer the GraphQL query language.

### Quick Start with GraphQL

```typescript
import { MybeSDK } from '@mybe/sdk';

const sdk = new MybeSDK({ apiKey: 'your-api-key' });
const PROJECT_ID = 'your-project-id';

// Execute a GraphQL query
const result = await sdk.graphql(PROJECT_ID, `
  query {
    blogPostCollection(limit: 10, where: { status: "published" }) {
      items {
        id
        slug
        data
      }
      total
    }
  }
`);

console.log(result.blogPostCollection.items);
```

### GraphQL Method

#### `graphql<T>(projectId: string, query: string, variables?: Record<string, any>)`

Execute a GraphQL query against your project.

**Parameters:**
- `projectId` - Your project ID
- `query` - GraphQL query string
- `variables` - Optional query variables (recommended for dynamic queries)

**Returns:** `Promise<T>` - The data from your GraphQL query

### GraphQL Examples

#### Basic Collection Query

```typescript
const { blogPostCollection } = await sdk.graphql(PROJECT_ID, `
  query {
    blogPostCollection(limit: 5) {
      items {
        id
        slug
        status
        data
        published_at
      }
      total
    }
  }
`);

console.log(`Total posts: ${blogPostCollection.total}`);
blogPostCollection.items.forEach(post => {
  console.log(post.data.title);
});
```

#### Filter by Status

```typescript
const { blogPostCollection } = await sdk.graphql(PROJECT_ID, `
  query {
    blogPostCollection(where: { status: "published" }) {
      items {
        id
        slug
        data
      }
    }
  }
`);
```

#### Get Single Entry by ID

```typescript
const { blogPost } = await sdk.graphql(PROJECT_ID, `
  query {
    blogPost(id: "entry-123") {
      id
      slug
      status
      data
      created_at
      published_at
    }
  }
`);
```

#### Using Variables (Recommended)

```typescript
const result = await sdk.graphql(
  PROJECT_ID,
  `
    query GetPostBySlug($slug: String!) {
      blogPostCollection(where: { slug: $slug }) {
        items {
          id
          slug
          data
        }
      }
    }
  `,
  { slug: 'my-blog-post' }
);

const post = result.blogPostCollection.items[0];
```

#### Pagination with GraphQL

```typescript
// Page 1
const page1 = await sdk.graphql(PROJECT_ID, `
  query {
    blogPostCollection(limit: 10, skip: 0) {
      items { id slug }
      total
    }
  }
`);

// Page 2
const page2 = await sdk.graphql(PROJECT_ID, `
  query {
    blogPostCollection(limit: 10, skip: 10) {
      items { id slug }
      total
    }
  }
`);
```

### REST vs GraphQL - When to Use

**Use REST When:**
- ✅ Simple queries (get by ID, get all)
- ✅ You want straightforward API calls
- ✅ You prefer SDK helper methods
- ✅ You need full object responses

**Use GraphQL When:**
- ✅ Complex filtering requirements
- ✅ You need specific fields only (reduce payload size)
- ✅ Multiple queries in one request
- ✅ You want flexible, ad-hoc queries
- ✅ Your frontend uses Apollo Client or similar

### Complete GraphQL Guide

For comprehensive GraphQL documentation, examples, and best practices, see:
- **[GraphQL API Guide](./GRAPHQL_GUIDE.md)** - Complete GraphQL documentation
- **[Test Examples](./test-graphql.ts)** - Working code examples

### GraphQL Schema

Your GraphQL schema is automatically generated from your content types. For example:

**Content Type:** "Blog Post"  
**Generates:**
- `blogPost(id: ID!)` - Get single entry
- `blogPostCollection(limit, skip, where)` - Get collection

**Naming:** Content type names are converted to camelCase (e.g., "Blog Post" → `blogPost`)

## Requirements

- Node.js 18+ (for native fetch support)
- TypeScript 5.0+ (for development)

## License

MIT

## Support

For issues and questions, please visit the [GitHub repository](https://github.com/MyBeeInovationLabs/mybe-cms).