# Publish to Hub — Functions

> Share your Aerostack Functions on the community marketplace. Other developers can browse, install, and deploy your Functions with one command.

The [Aerostack Hub](https://hub.aerostack.dev) is the community marketplace for Functions. Publish your Function and other developers can install it with one command. There's no buying or selling — the Hub is free.

---

## Why Publish?

- **Share your work** — other developers can install your Function into their projects as source code
- **Build reputation** — earn Community Reputation points that increase your visibility in search
- **Get installs** — your Function appears in the Hub search and CLI discovery
- **Help the ecosystem** — every Function makes Aerostack more powerful for everyone

---

## What Can You Publish?

Any Function that solves a reusable problem:

| Category | Examples |
|---|---|
| `auth` | OAuth handlers, JWT utilities, rate limiters |
| `payments` | Stripe checkout, invoice generators, subscription managers |
| `ai` | Text summarizers, sentiment analyzers, embedding generators |
| `media` | Image resizers, PDF generators, video processors |
| `email` | Template renderers, SMTP senders, newsletter managers |
| `database` | Migration helpers, seed scripts, query builders |
| `utility` | Slug generators, ID generators, validators |
| `analytics` | Event trackers, dashboard aggregators, report generators |
| `notifications` | Push notifications, Slack/Discord alerts, SMS senders |
| `storage` | File upload handlers, CDN managers, backup utilities |

  **Any size Function works.** From a 20-line slug generator to a 500-line RAG pipeline — if it's useful, publish it.

---

## Function Structure

Every publishable Function needs an `aerostack.json` config and at least one source file.

- my-function/
  - aerostack.json  // Required — metadata and config
  - index.ts  // Required — your function logic
  - README.md  // Recommended — shown on the Hub page

### The `aerostack.json` file

This defines your Function's identity, version, and how it should be installed.

```json title="aerostack.json"
{
  "name": "smart-rate-limiter",
  "version": "1.0.0",
  "description": "AI-powered rate limiter that adapts limits based on request patterns.",
  "category": "auth",
  "language": "typescript",
  "runtime": "cloudflare-worker",
  "license": "MIT",
  "tags": ["rate-limit", "security", "ai", "adaptive"],
  "entrypoint": "index.ts",
  "routePath": "/api/rate-limit",
  "routeExport": "rateLimiterRoute",
  "npmDependencies": [],
  "envVars": ["RATE_LIMIT_MAX", "RATE_LIMIT_WINDOW"],
  "drizzleSchema": false
}
```

#### Field reference

| Field | Required | Description |
|---|---|---|
| `name` | Yes | Function name — becomes the slug in the Hub URL |
| `version` | Yes | SemVer, e.g. `1.0.0`. Bump on each publish |
| `description` | Yes | Short description shown in search results |
| `category` | Yes | One of: `auth`, `payments`, `media`, `email`, `ai`, `database`, `utility`, `analytics`, `notifications`, `storage` |
| `language` | Yes | `typescript` or `javascript` |
| `runtime` | Yes | `cloudflare-worker` |
| `license` | Yes | SPDX identifier: `MIT`, `Apache-2.0`, etc. |
| `tags` | No | Search keywords, max 10 |
| `entrypoint` | Yes | Path to main source file |
| `routePath` | No | Default HTTP route when installed (e.g. `/api/rate-limit`) |
| `routeExport` | No | Named Hono export from your file |
| `npmDependencies` | No | npm packages the consumer must install |
| `envVars` | No | Environment variables the consumer must set |
| `drizzleSchema` | No | `true` if you export a Drizzle schema |

---

## Writing a Publishable Function

Export a **Hono route handler**. This is the convention the CLI uses when installing.

```typescript title="index.ts"

// Named export — matches "routeExport" in aerostack.json

rateLimiterRoute.post('/check', async (c) => {
  const { ip, endpoint } = await c.req.json<{ ip: string; endpoint: string }>()
  const key = `ratelimit:${ip}:${endpoint}`

  const current = parseInt(await c.env.CACHE.get(key) || '0')
  const limit = parseInt(c.env.RATE_LIMIT_MAX || '100')

  if (current >= limit) {
    return c.json({ allowed: false, remaining: 0, resetIn: 60 }, 429)
  }

  await c.env.CACHE.put(key, String(current + 1), { expirationTtl: 60 })

  return c.json({ allowed: true, remaining: limit - current - 1 })
})

rateLimiterRoute.get('/health', (c) =>
  c.json({ module: 'smart-rate-limiter', ok: true })
)
```

  **Tip: separate logic from HTTP.** For complex Functions, put pure logic in `core.ts` and the HTTP adapter in `index.ts`. This makes it easier to test and reuse.

---

## Publishing Methods

The CLI is the fastest way to publish.

1. **Push to create a draft**

   ```bash
   aerostack functions push ./my-function/index.ts
   ```

   Output:

   ```
   Pushing function 'smart-rate-limiter' to Aerostack...
   Pushed successfully!
      Slug: smart-rate-limiter
      Status: draft
      Admin URL: https://app.aerostack.dev/functions/edit/<id>
   ```

2. **Review in Admin (optional)**

   Open the Admin URL to add a README, adjust tags, and preview how it looks on the Hub.

3. **Publish**

   ```bash
   aerostack functions publish <id>
   ```

   Your Function is now live on the Hub.

Auto-publish on every push to `main`.

1. **Add your API key as a GitHub secret**

   Go to **Settings** → **Secrets** → **Actions** → create `AEROSTACK_API_KEY` with your account key (`ac_secret_xxxx`).

2. **Add the workflow**

   ```yaml title=".github/workflows/publish-aerostack.yml"
   name: Publish to Aerostack Hub

   on:
     push:
       branches: [main]

   jobs:
     publish:
       runs-on: ubuntu-latest
       steps:
         - uses: actions/checkout@v4
         - uses: actions/setup-node@v4
           with:
             node-version: '20'

         - name: Read config and publish
           run: |
             CONFIG=$(cat aerostack.json | tr -d '\n')
             ENTRYPOINT=$(echo "$CONFIG" | jq -r '.entrypoint // "index.ts"')
             CODE=$(cat "$ENTRYPOINT" | jq -Rs .)

             PAYLOAD=$(jq -n \
               --argjson config "$CONFIG" \
               --argjson code "$CODE" \
               '$config + {code: $code, publish: true}')

             curl -sf \
               -X POST https://api.aerostack.dev/api/community/functions/ci/publish \
               -H "X-API-Key: ${{ secrets.AEROSTACK_API_KEY }}" \
               -H "Content-Type: application/json" \
               -d "$PAYLOAD" | jq .
   ```

The CI endpoint **upserts by slug** — same name updates the existing Function instead of creating a new one.

Auto-publish from GitLab.

1. **Add your API key as a CI variable**

   **Settings** → **CI/CD** → **Variables** → add `AEROSTACK_API_KEY`.

2. **Add the pipeline**

   ```yaml title=".gitlab-ci.yml"
   stages:
     - publish

   publish-to-aerostack:
     stage: publish
     image: node:20-alpine
     before_script:
       - apk add --no-cache curl jq
     script:
       - |
         CONFIG=$(cat aerostack.json)
         ENTRYPOINT=$(echo "$CONFIG" | jq -r '.entrypoint // "index.ts"')
         CODE=$(cat "$ENTRYPOINT" | jq -Rs .)
         PAYLOAD=$(jq -n --argjson config "$CONFIG" --argjson code "$CODE" '$config + {code: $code, publish: true}')
         curl -sf -X POST "https://api.aerostack.dev/api/community/functions/ci/publish" \
           -H "X-API-Key: $AEROSTACK_API_KEY" \
           -H "Content-Type: application/json" \
           -d "$PAYLOAD" | jq .
     only:
       - main
   ```

Publish directly from the browser — no local tools needed.

1. **Go to My Functions**

   Log in to [app.aerostack.dev](https://app.aerostack.dev) → **My Functions** → **Create**.

2. **Write your code**

   Use the built-in Monaco editor. Add a name, description, category, and tags.

3. **Publish**

   Click **Publish**. Your Function is live on the Hub.

---

## How Others Install Your Function

Once published, any developer can install your Function:

```bash
# Install by slug
aerostack functions install smart-rate-limiter

# Install by username/slug
aerostack functions install alice/smart-rate-limiter
```

**What happens when someone installs:**

1. The CLI downloads your source code
2. Creates `src/modules/smart-rate-limiter/` in their project
3. Auto-registers the route in their `aerostack.toml`
4. Auto-imports the route in their `src/index.ts`
5. Prompts to install npm dependencies (if any)
6. Prompts to set environment variables (if any)

The consumer owns the code. It's copied as source — not linked as a dependency.

---

## Versioning

Every time you publish, a version snapshot is created. Past versions are preserved — consumers who installed an earlier version keep their code.

- Bump `version` in `aerostack.json` before each publish
- You cannot overwrite a published version
- Use SemVer: `1.0.0` → `1.0.1` (patch), `1.1.0` (minor), `2.0.0` (breaking)

---

## Reputation

Publishing and getting installs earns **Community Reputation**:

| Action | Points |
|---|---|
| Publish a Function | +10 |
| Someone installs your Function | +5 |
| Someone stars your Function | +2 |
| Someone clones/forks your Function | +5 |

Higher reputation = better visibility in Hub search results and the developer leaderboard.

---

## Common Errors

| Error | Fix |
|---|---|
| `INVALID_CATEGORY` | Check spelling, use lowercase |
| `MISSING_ENTRYPOINT` | Verify `entrypoint` path in `aerostack.json` |
| `EMPTY_CODE` | Entrypoint file is empty |
| `DUPLICATE_SLUG` | Name already taken by another account — rename your Function |
| `UNAUTHORIZED` | API key invalid — run `aerostack login` again |
| `VERSION_CONFLICT` | Bump `version` in `aerostack.json` |
