airspace

the database you already have.

every Bluesky atmosphere account comes with a personal data server: a cms, login, file storage and a public API that you own.

try the demo get started

pnpm add airspaceNode 22+

define a model

lexicons in TypeScript, in your own namespace.

// lexicons.ts
import { defineLexicons, field, space } from 'airspace/lexicon'

export default defineLexicons('dev.roe', {
  note: {
    title: field.text({ max: 120 }),
    body: field.markdown(),
    createdAt: field.datetime().optional(),
  },
  workspace: space(['note']),
})
// collections.ts
import { defineCollections, defineSpace } from 'airspace'
import lexicons from './lexicons.ts'

export const { note: notes } = defineCollections(lexicons, {
  note: { sort: [['createdAt', 'desc']] },
})

export const workspace = defineSpace(lexicons.workspace, {
  collections: { notes },
})

access your data

a fully typed client over your repo and your drafts.

// airspace.ts
import { createAirspace } from 'airspace'
import { workspace } from './collections.ts'

export const airspace = createAirspace({
  identity: 'roe.dev',
  spaces: { workspace }, // its `notes` is a public collection too
  session, // omit for read-only
})

const published = await airspace.notes.list({ limit: 5 })

const draft = await airspace.workspace.notes.create({
  title: 'Not public yet',
  body: '# hello',
})
await airspace.workspace.notes.publish(draft.rkey)

what you get

you own your data
Records live in your own repo under your own schema. Switch tools or hosts – or even stop using airspace – and everything still works.
no database to run
Reads, writes, auth and images are all handled by your own PDS. airspace is a typed client.
drafts built in
Experimental permissioned spaces for private data, with a single call to publish.
typed from your schema
Define the model once. Records, keys, joins and OAuth scopes are inferred magically. ✨

works with what exists

airspace manages any collection you have a lexicon for, not only the ones you wrote.

Nothing in your repo carries an airspace $type or is proprietary to airspace.

if you already know atproto

airspace sits between @atproto/lex-schema and your site.

Lexicons are TypeScript, and your public repo and your permissioned spaces share one typed API. airspace lexicons emit writes the JSON when you want to publish your schemas. airspace itself defines no content model, renders nothing and hosts nothing.