npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@aether-official/sites-sdk

v1.3.4

Published

Type-safe headless CMS SDK for Astro with visual editing

Readme

@aether-official/sites-sdk

Official JavaScript SDK for integrating Aether's Visual Headless CMS into your static sites.

npm version License: 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 | Developer Guide

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

# 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

npm install @aether-official/sites-sdk
npm install -D @aether-official/cli

2. Run Interactive Setup

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

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

---
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):

<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):

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

# 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:

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:

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:

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

# 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

import { aether } from '@aether-official/sites-sdk/astro';

const helper = aether.section('section-id');

Helper Methods

helper.section()

Returns attributes for section wrapper:

<section {...helper.section()}>
  <!-- Content -->
</section>

helper.field(name, value?)

Returns attributes for editable fields. Pass the value for the editor to track changes:

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

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

<div {...helper.repeaterItem(0)}>
  <!-- Repeater item content -->
</div>

Check Editor Mode

import { aether } from '@aether-official/sites-sdk/astro';

if (aether.isEditorMode()) {
  // Currently in visual editor
}

Configuration Options

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

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

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

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

// astro.config.mjs
export default defineConfig({
  integrations: [
    aetherSites({
      autoMiddleware: false,
      // ... other config
    })
  ]
});

Then manually create src/middleware.ts:

import { onRequest as aetherMiddleware } from '@aether-official/sites-sdk/middleware';

export const onRequest = aetherMiddleware;

Custom Middleware Sequence

Combine Aether middleware with your own:

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

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:

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

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

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

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

# 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

    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 for step-by-step upgrade instructions.

Links

License

MIT © Aether Team