Skip to content

aerostackdev/open-function-spec

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Open Function Spec

A universal, composable standard for writing backend functions that run anywhere.

License: MIT Registry Discord


The Problem

Backend developers rewrite the same login flow, file upload, Stripe webhook, and notification system for every project. Every time — with small but painful platform-specific differences.

Existing solutions (npm packages, GitHub snippets) share code but not structure. You still have to adapt them to your project's routing, database, and runtime manually.

Open Function Spec (OFS) fixes this by defining a module structure that is agnostic of the runtime, framework, and database client — and ships with a CLI to install pre-built functions in one command.


The Three-Layer Architecture

Every OFS-compliant module contains exactly three files:

src/modules/<module-name>/
  schema.ts     — Database tables (Drizzle ORM)
  core.ts       — Business logic (framework-free)
  adapter.ts    — Framework connector (Hono, Express, etc.)

Layer 1: schema.ts — The Data Contract

Defines the database tables using Drizzle ORM. No logic, no imports from other layers.

// src/modules/notes/schema.ts
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core';

export const notes = sqliteTable('notes', {
    id:        text('id').primaryKey(),
    userId:    text('user_id').notNull(),
    title:     text('title').notNull(),
    content:   text('content').notNull().default(''),
    slug:      text('slug').notNull(),
    createdAt: integer('created_at').notNull(),
    updatedAt: integer('updated_at').notNull(),
});

export type Note = typeof notes.$inferSelect;

Layer 2: core.ts — Pure Business Logic

Contains ALL business rules. No HTTP framework. No Request/Response. No process.env.

// src/modules/notes/core.ts
import type { DrizzleDB } from '../../db/client';
import { notes } from './schema';
import { eq, desc } from 'drizzle-orm';

interface CoreContext {
    db: DrizzleDB;
    env?: Record<string, string>;
}

export async function listNotes(ctx: CoreContext, userId: string) {
    return ctx.db
        .select()
        .from(notes)
        .where(eq(notes.userId, userId))
        .orderBy(desc(notes.createdAt));
}

export async function createNote(ctx: CoreContext, input: { userId: string; title: string; content?: string }) {
    const id = crypto.randomUUID();
    const now = Date.now();
    const [note] = await ctx.db
        .insert(notes)
        .values({ id, userId: input.userId, title: input.title, content: input.content ?? '', slug: `note-${id.slice(0,8)}`, createdAt: now, updatedAt: now })
        .returning();
    return note;
}

The Cardinal Rules of core.ts:

✅ Allowed ❌ Forbidden
import from 'drizzle-orm' import { Hono } from 'hono'
throw new Error(...) return c.json({ error: '...' }, 400)
return rawDataObject return new Response(...)
ctx.env.MY_API_KEY process.env.MY_API_KEY

Why this matters: A core.ts following these rules can be unit tested with standard Vitest in Node.js even if production runs on Cloudflare Workers.


Layer 3: adapter.ts — The Platform Bridge

Connects core.ts to a specific web framework. Only handles request parsing and response formatting.

// src/modules/notes/adapter.ts — Hono (Cloudflare Workers)
import { Hono } from 'hono';
import { createDb } from '../../db/client';
import { listNotes, createNote } from './core';

const notesRoute = new Hono<{ Bindings: { DB: D1Database } }>();

notesRoute.get('/', async (c) => {
    const userId = c.req.query('userId');
    if (!userId) return c.json({ error: 'userId required' }, 400);
    const db = createDb(c.env.DB);
    return c.json({ notes: await listNotes({ db }, userId) });
});

notesRoute.post('/', async (c) => {
    const body = await c.req.json<{ userId: string; title: string }>();
    const db = createDb(c.env.DB);
    return c.json({ note: await createNote({ db }, body) }, 201);
});

export { notesRoute };

Swapping from Hono to Express only requires changing this file. core.ts has zero changes.


CLI Toolchain

OFS is supported by the Aerostack CLI. Functions that follow this spec can be installed, tested, and published through it.

Scaffold a new project

npx aerostack@latest init my-api
cd my-api
npx drizzle-kit push   # Create database tables from schemas
npx wrangler dev       # Start local Cloudflare Worker dev server

Install a community function in one command

npx aerostack add stripe-checkout

What happens automatically:

  1. Downloads src/modules/stripe-checkout/ (schema, core, adapter)
  2. Patches src/index.ts to mount the route: app.route('/checkout', stripeCheckoutRoute)
  3. Patches src/db/schema.ts to export the new tables
  4. Runs npm install stripe if needed

Publish your function

npx aerostack publish

Project Structure

my-api/
├── src/
│   ├── index.ts                    # Entry: mounts all module routes
│   ├── db/
│   │   ├── client.ts               # Drizzle factory: createDb(d1)
│   │   └── schema.ts               # Re-exports all module schemas
│   ├── modules/
│   │   ├── _core/                  # Base tables: users, sessions
│   │   │   └── schema.ts
│   │   ├── notes/                  # Feature module (you built this)
│   │   │   ├── schema.ts
│   │   │   ├── core.ts
│   │   │   └── adapter.ts
│   │   └── stripe-checkout/        # Installed via `aerostack add`
│   │       ├── schema.ts
│   │       ├── core.ts
│   │       └── adapter.ts
│   └── lib/
│       └── string/
│           └── slugify.ts          # Utility: no db, no http
├── drizzle.config.ts               # Glob-discovers all module schemas
├── wrangler.toml
└── package.json

Module Types

Type Location Description
Feature src/modules/<name>/ Has schema + core + adapter. Adds a new API route.
Utility src/lib/<name>/ Stateless helper function. No database, no HTTP.

Runtime Compatibility

An OFS-compliant core.ts is runtime-agnostic:

Runtime schema.ts core.ts adapter.ts
Cloudflare Workers ✅ D1/sqlite ✅ Hono
Node.js 18+ ✅ libsql/pg ✅ Express
Bun ✅ libsql ✅ Hono/Elysia
Deno ⚠️ ⚠️

Community Registry

All OFS-compliant functions are listed at hub.aerostack.dev

The registry is searchable by category, tags, and runtime. Each function page shows its source, documentation, and a one-line install command.


Contributing

We welcome contributions! See CONTRIBUTING.md for the full guide.

Quick checklist for a new function:

  • schema.ts uses Drizzle ORM (no raw SQL strings)
  • core.ts has no framework imports (hono, express, etc.)
  • core.ts has no process.env — uses ctx.env only
  • adapter.ts has no business logic
  • README.md documents endpoints, env vars, and tables
  • Passes npx tsc --noEmit

Spec

Read the formal specification: SPEC.md


License

MIT. Build anything.


Built by the Aerostack team. The Aerostack Registry is the reference implementation of this standard.

About

Open specification for shareable, installable backend modules. Write once, deploy anywhere. npx aerostack add <function>

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors