npm package: @southwellmedia/aether-sdk
Description: Type-safe headless CMS SDK for Astro with visual editing
Author: Aether Team
Latest version: 1.0.5

# @aether-official/sites-sdk

Official JavaScript SDK for integrating Aether's Visual Headless CMS into your static sites.

[![npm version](https://img.shields.io/npm/v/@aether-official/sites-sdk.svg)](https://www.npmjs.com/package/@aether-official/sites-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## ✨ What's New in v1.3

- **🎯 TypeScript Type Generation** - Auto-generate types from your CMS schema with the CLI tool
- **📝 Type-Safe Content Fetching** - Full autocomplete and type checking for all content fields
- **🔧 Unified Fetch API** - `fetch` namespace for cleaner, more intuitive API
- **🛠️ CLI Tool** - Interactive setup wizard and schema synchronization
- **🌐 Global Settings** - Access site-wide settings (contact, hours, social)
- **🔍 SEO Metadata** - Complete page-level SEO management

[See Changelog](./CHANGELOG.md) | [Developer Guide](./DEVELOPER_GUIDE.md)

## Features

- 🎯 **Type Generation** - Auto-generate TypeScript types from CMS schema
- 📝 **Type-Safe API** - Full autocomplete and type checking for content
- 🔧 **CLI Tool** - Interactive setup and schema synchronization
- 🎨 **Helper API** - Reduce boilerplate with intuitive helper functions
- 🖼️ **Visual Editor** - Enable inline content editing with postMessage communication
- 📦 **Content Fetching** - Server-side and build-time content retrieval
- 🚀 **Astro First** - First-class Astro integration with auto-configuration
- ⚡ **Optimized** - Minified bundle with tree-shaking support

## Installation

```bash
# Install the SDK
npm install @aether-official/sites-sdk

# Install the CLI (for type generation)
npm install -D @aether-official/cli
```

**Requirements**: Astro 3.5.0+ (for auto-middleware injection)

## Quick Start (5 Minutes)

### 1. Install Packages

```bash
npm install @aether-official/sites-sdk
npm install -D @aether-official/cli
```

### 2. Run Interactive Setup

```bash
npx aether-sdk init
```

This will:
- Prompt for your credentials (Site ID, Org ID, API Token)
- Test the connection
- Create `.env` file
- Fetch your schema
- Generate TypeScript types

### 3. Add Astro Integration

```javascript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import aetherSites from '@aether-official/sites-sdk/astro';

export default defineConfig({
  integrations: [
    aetherSites({
      siteId: process.env.AETHER_SITE_ID,
      organizationId: process.env.AETHER_ORG_ID,
      editorOrigin: process.env.PUBLIC_AETHER_EDITOR_ORIGIN || 'https://myaether.cloud',
      enabled: true,
    })
  ]
});
```

### 4. Use Type-Safe Fetching

```astro
---
import { fetch, aether, getConfig } from '@aether-official/sites-sdk/astro';
import { SECTIONS } from '../aether-types';  // Auto-generated!
import type { HeroData } from '../aether-types';

const config = getConfig(import.meta.env);

// Fetch section content
const section = await fetch.section(SECTIONS.HERO, config);
const content = section.data as HeroData;

// Create helper for visual editing
const helper = aether.section(SECTIONS.HERO);
---

<section {...helper.section()}>
  <h1 {...helper.field('title', content.title)}>
    {content.title}
  </h1>
  <p {...helper.field('subtitle', content.subtitle)}>
    {content.subtitle}
  </p>
</section>
```

**That's it!** You now have:
- ✅ Full TypeScript autocomplete for all content fields
- ✅ Visual inline editing with `?aether-editor=true`
- ✅ Auto-generated types that stay in sync with your CMS

## What Just Happened? 🎉

### 1. Auto-Middleware Injection ✨

The SDK automatically:
- Injects middleware for CSP header configuration
- Enables visual editing with `?aether-editor=true` query parameter
- No manual middleware file creation required!

### 2. Helper API Reduces Boilerplate 🎨

**Before (v1.0.x - verbose)**:
```astro
<section data-aether-section="hero-123">
  <h1
    data-aether-field="title"
    data-aether-section="hero-123"
    data-aether-value={content.title}
  >
    {content.title}
  </h1>
</section>
```

**After (v1.1.0+ - concise)**:
```astro
<section {...helper.section()}>
  <h1 {...helper.field('title')}>
    {content.title}
  </h1>
</section>
```

~60% reduction in attribute boilerplate! 🚀

**Note**: `helper.field()` only requires the field name - the value is displayed separately in your template.

### 3. Type Generation Keeps Types in Sync 🎯

When you update fields in the CMS:

```bash
# Sync latest schema and regenerate types
npx aether-sdk sync && npx aether-sdk types
```

Your IDE instantly knows about:
- New sections you created
- New fields you added
- Changed field types
- Renamed fields

**No manual type definitions needed!** The CLI automatically generates TypeScript interfaces from your CMS schema.

## CLI Tool Reference

The Aether CLI provides commands to manage your SDK integration:

### `aether-sdk init`

Interactive setup wizard that configures your project:

```bash
npx aether-sdk init
```

**What it does:**
- Prompts for Site ID, Organization ID, and API Token
- Tests your connection to Aether CMS
- Creates `.env` file with credentials
- Fetches your site's schema
- Generates TypeScript types

### `aether-sdk sync`

Fetch the latest schema from your CMS:

```bash
npx aether-sdk sync
```

**Use when:**
- You created new sections in the CMS
- You added/modified fields
- You want to update your local schema cache

### `aether-sdk types`

Generate TypeScript types from cached schema:

```bash
npx aether-sdk types
```

**Generates:**
- `src/aether-types.d.ts` - Section IDs and data interfaces
- Full autocomplete for all content fields
- Type-safe constants for section IDs

### Workflow Example

```bash
# Initial setup
npx aether-sdk init

# After creating sections in CMS
npx aether-sdk sync && npx aether-sdk types

# Use in your code with full type safety
```

## Helper API Reference

### Creating a Helper

```typescript
import { aether } from '@aether-official/sites-sdk/astro';

const helper = aether.section('section-id');
```

### Helper Methods

#### `helper.section()`
Returns attributes for section wrapper:
```astro
<section {...helper.section()}>
  <!-- Content -->
</section>
```

#### `helper.field(name, value?)`
Returns attributes for editable fields. Pass the value for the editor to track changes:
```astro
<h1 {...helper.field('title', content.title)}>
  {content.title}
</h1>

<!-- For images -->
{content.hero_image && (
  <img
    src={content.hero_image.url}
    alt={content.hero_image.alt || 'Alt text'}
    {...helper.field('hero_image', content.hero_image)}
  />
)}
```

**Note**: Image fields are returned as ImageField objects with `{url, alt, width, height}` properties.

#### `helper.repeater(name, items)`
Returns attributes for repeater fields:
```astro
<div {...helper.repeater('items', content.items)}>
  {content.items.map((item, index) => (
    <div {...helper.repeaterItem(index)}>
      <h3 {...helper.field('name', item.name)}>{item.name}</h3>
    </div>
  ))}
</div>
```

#### `helper.repeaterItem(index)`
Returns attributes for repeater items:
```astro
<div {...helper.repeaterItem(0)}>
  <!-- Repeater item content -->
</div>
```

### Check Editor Mode

```typescript
import { aether } from '@aether-official/sites-sdk/astro';

if (aether.isEditorMode()) {
  // Currently in visual editor
}
```

## Configuration Options

```typescript
interface AetherAstroConfig {
  siteId: string;              // Required: Site ID from Aether dashboard
  organizationId: string;      // Required: Organization ID
  editorOrigin?: string;       // Optional: Editor URL (default: https://myaether.cloud)
  enabled?: boolean;           // Optional: Enable SDK (default: true)
  debug?: boolean;             // Optional: Enable debug logging (default: false)
  editableSelector?: string;   // Optional: Custom selector (default: '[data-aether-editable]')
  autoMiddleware?: boolean;    // Optional: Auto-inject middleware (default: true)
}
```

## Working with Components

Component instances are reusable content blocks that can be attached to sections. Here's how to fetch and render them:

### Fetching Components

```astro
---
import { fetch, aether, getConfig } from '@aether-official/sites-sdk/astro';
import { SECTIONS } from '../aether-types';
import type { FoundationOfExcellenceData } from '../aether-types';

const config = getConfig(import.meta.env);

// Fetch section content
const section = await fetch.section(SECTIONS.FOUNDATION_OF_EXCELLENCE, config);
const content = section.data as FoundationOfExcellenceData;

// Fetch component instances attached to this section
const components = await fetch.components(SECTIONS.FOUNDATION_OF_EXCELLENCE, config);

// Filter components by type/slug
const statComponents = components.filter(c => c.component.slug === 'stat');
const buttonComponents = components.filter(c => c.component.slug === 'button');

const helper = aether.section(SECTIONS.FOUNDATION_OF_EXCELLENCE);
---
```

### Rendering Components with React/Vue

For components that need client-side interactivity, create a React/Vue component:

```tsx
// src/components/StatCard.tsx
import React from 'react';

interface StatCardProps {
  number: string;
  label: string;
  'data-aether-component'?: string;
  'data-aether-section'?: string;
}

export const StatCard: React.FC<StatCardProps> = ({
  number,
  label,
  'data-aether-component': dataAetherComponent,
  'data-aether-section': dataAetherSection,
}) => {
  return (
    <>
      <div
        data-aether-component={dataAetherComponent}
        data-aether-section={dataAetherSection}
        data-aether-field="numeric_stat"
      >
        {number}
      </div>
      <div
        data-aether-component={dataAetherComponent}
        data-aether-section={dataAetherSection}
        data-aether-field="stat_desc"
      >
        {label}
      </div>
    </>
  );
};
```

### Using Components in Astro

```astro
---
import { StatCard } from '../components/StatCard.tsx';
// ... component fetching code from above
---

<section {...helper.section()}>
  <!-- Render component instances -->
  {statComponents.map(comp => (
    <StatCard
      number={comp.data.numeric_stat}
      label={comp.data.stat_desc}
      data-aether-component={comp.id}
      data-aether-section={SECTIONS.FOUNDATION_OF_EXCELLENCE}
      client:load
    />
  ))}
</section>
```

**Key Points:**
- Use `data-aether-component={comp.id}` to enable component editing
- Use `data-aether-field="fieldName"` on individual field elements
- Include `data-aether-section` for proper editor context
- Use `client:load` (or `client:visible`, etc.) for hydration

## Advanced Usage

### Disable Auto-Middleware

If you need custom middleware or want to disable auto-injection:

```javascript
// astro.config.mjs
export default defineConfig({
  integrations: [
    aetherSites({
      autoMiddleware: false,
      // ... other config
    })
  ]
});
```

Then manually create `src/middleware.ts`:

```typescript
import { onRequest as aetherMiddleware } from '@aether-official/sites-sdk/middleware';

export const onRequest = aetherMiddleware;
```

### Custom Middleware Sequence

Combine Aether middleware with your own:

```typescript
import { sequence } from 'astro:middleware';
import { onRequest as aetherMiddleware } from '@aether-official/sites-sdk/middleware';
import { myCustomMiddleware } from './my-middleware';

export const onRequest = sequence(
  aetherMiddleware,      // Aether runs first
  myCustomMiddleware,    // Your middleware runs second
);
```

### Content Fetching

```typescript
import { getAetherSection, getAetherPage } from '@aether-official/sites-sdk/astro';

// Fetch single section
const hero = await getAetherSection('section-id', config);

// Fetch entire page with all sections
const page = await getAetherPage('page-slug', config);
```

## Common Patterns

### Handling Image Fields

Image fields are returned as ImageField objects with `url`, `alt`, `width`, and `height` properties:

```astro
---
const section = await fetch.section(SECTIONS.HERO, config);
const content = section.data as HeroData;
---

<!-- Image fields are objects -->
{content.hero_image && (
  <img
    src={content.hero_image.url}
    alt={content.hero_image.alt || 'Hero image'}
    width={content.hero_image.width}
    height={content.hero_image.height}
    {...helper.field('hero_image')}
  />
)}
```

### Conditional Content Rendering

```astro
<!-- Only render if field has content -->
{content.optional_text && (
  <p {...helper.field('optional_text')}>
    {content.optional_text}
  </p>
)}

<!-- With fallback content -->
<h1 {...helper.field('title')}>
  {content.title || 'Default Title'}
</h1>
```

### Filtering Component Types

```astro
---
const components = await fetch.components(SECTIONS.MY_SECTION, config);

// Get specific component types
const buttons = components.filter(c => c.component.slug === 'button');
const cards = components.filter(c => c.component.slug === 'card');
const stats = components.filter(c => c.component.slug === 'stat');

// Check if components exist
const hasButtons = buttons.length > 0;
---

{hasButtons && (
  <div class="button-group">
    {buttons.map(btn => (
      <a href={btn.data.url} {...helper.field('url')}>
        {btn.data.label}
      </a>
    ))}
  </div>
)}
```

### Complete Section Example

```astro
---
import { fetch, aether, getConfig } from '@aether-official/sites-sdk/astro';
import { SECTIONS } from '../aether-types';
import type { AboutSectionData } from '../aether-types';
import { StatCard } from '../components/StatCard.tsx';

const config = getConfig(import.meta.env);

let section;
let content: AboutSectionData;
let components: any[] = [];

try {
  section = await fetch.section(SECTIONS.ABOUT, config);
  content = section.data as AboutSectionData;
  components = await fetch.components(SECTIONS.ABOUT, config);
} catch (e) {
  console.error('Failed to fetch section:', e);
  // Fallback content
  content = {
    headline: 'Default Headline',
    subheadline: 'Default subheadline',
  };
}

const helper = aether.section(SECTIONS.ABOUT);
const statComponents = components.filter(c => c.component.slug === 'stat');
---

<section {...helper.section()}>
  <h2 {...helper.field('headline')}>{content.headline}</h2>
  <p {...helper.field('subheadline')}>{content.subheadline}</p>

  {statComponents.length > 0 && (
    <div class="stats">
      {statComponents.map(comp => (
        <StatCard
          number={comp.data.value}
          label={comp.data.label}
          data-aether-component={comp.id}
          data-aether-section={SECTIONS.ABOUT}
          client:load
        />
      ))}
    </div>
  )}
</section>
```

## Troubleshooting

### Types Not Updating

**Problem:** Generated types don't reflect changes made in Aether CMS

**Solution:**
```bash
# Clear cache and regenerate
rm -rf .aether
npx aether-sdk sync && npx aether-sdk types

# Restart dev server
npm run dev
```

### Connection Failed

**Problem:** `Connection failed: Unauthorized` during `aether-sdk init`

**Solution:**
- Verify API token in Aether dashboard → Sites → Your Site → API
- Check API URL is correct (`http://localhost:3000` for development)
- Ensure API token hasn't expired

### Visual Editor Not Loading

1. **Verify Astro version**: Requires Astro 3.5.0+ for auto-middleware
   ```bash
   npm list astro
   ```

2. **Check browser console**: Look for CSP errors

3. **Verify query parameter**: URL must include `?aether-editor=true`

4. **Test manually**: Try adding middleware manually to verify it's a middleware issue

### Migration from v1.0.x

See detailed [Migration Guide](./MIGRATION.md) for step-by-step upgrade instructions.

## Links

- 📖 [Setup Guide](./SETUP.md) - Detailed setup instructions
- 🔄 [Migration Guide](./MIGRATION.md) - Upgrade from v1.0.x
- 📝 [Changelog](./CHANGELOG.md) - Version history
- 🐛 [Issues](https://github.com/southwellmedia-dev/aether-platform/issues)
- 📚 [Documentation](https://myaether.cloud/docs/sdk)

## License

MIT © Aether Team