Getting Started

Adding solair to your Javascript project takes a single command:

npm i solair

And that’s it. You can use solair to persist data right in the browser, sync it with a remote server, or subscribe to real-time changes―a zero-config distributed data layer.

But that’s not all. solair also includes authentication, role-based authorization, data validation, and multiple backend integrations to build any software you can think of.

Give me 5 minutes and I’ll show you how to save days of development.

Database

If you’re like me and have been developping on the web for a while, you are probably tired of creating Create-Read-Update-Delete layers: REST API endpoints, database tables, models, entities… It never ends, and it’s frankly boring. What if I told you it doesn’t have to be this way?

Creating a document

With solair, creating a document is a two-line task you can do right in the browser:

import { db } from 'solair'

let doc = await db.set({ title: 'hello world!' })

Reading a document

Refresh your webpage and you can still query your document by id:

let doc = await db.get(doc.id)

Updating a document

Update your doc and save it:

const updated_doc = await db.set({ title: 'changed title' })

assert(doc.id == updated_doc.id)

Deleting a document

Or just delete it:

await db.delete(doc.id)

Data validation

Because documents can be serialized to JSON, it’s incredibly easy to validate data with a library like zod:

const BlogPost = z.object({
  title: z.string()
});

BlogPost.parse(doc)

Scaling with collections

What about indexing, searching, and bulk operations, you might ask? Well, let’s talk about collections.

A collection is just a set of documents following a specific schema. You can define its name and indexes:

const collection = defineCollection('blog', {
    indexes: "++id, title",
    schema: z.object({
        id: z.number(),
        title: z.string()
    })
})

You can attach a document to a collection any time:

await db.blog.set(doc)

And then query the collection with advanced indexes:

let doc = await db.blog.get(doc.id)

const allBlogs = await db.blog.all()
const searchedBlogs = await db.blog.search({ title: 'changed'})

Remote Server

Ok so we can do some CRUD in the browser. That’s cool and all but what about storing the data in the cloud so that I don’t lose it all when logging out or changing devices?

We have your back(end).

Deploy

Run this command:

npx solair deploy server

Congratulations, your server is ready and you can now back up local data and even interact with integrations. Yes, seriously. Let’s try it:

Sync

After deploying, solair reserves a subdomain you can use as a remote backend. Pushing data upstream is as simple as git push:

const db = new Database({
  remote: 'https://my-project-server.solairjs.dev'
})
await db.push(doc.id)

Similarly, you can pull changes:

const update = await db.pull(doc.id)

if(schema.parse(update)) {
  db.import(doc.id, update)
}

Or do both at the same time:

const update = await db.sync(doc.id)

if(schema.parse(update)) {
  db.import(doc.id, update)
}

If you want to enable real-time operations, you can use the emit, listen, and subscribe methods:

db.emit(doc.id) // auto push changes to remote in real-time

db.listen(doc.id, update => { ... }) // auto pull changes from remote in real-time

db.subscribe(doc.id, update => { ... }) // auto sync changes in real-time

Local development

Of course, it wouldn’t be much fun if you can’t test things, so we already prepared a local development environment you can use to test remote features:

npx solair dev

State Management

We now know how to do CRUD operations with solair, but what’s the best way to consume this data in a frontend framework to create the view?

In solair, the source of truth is not the document itself, it’s the local database. It’s important to understand this separation of concerns.

A solair document is part of the data access layer (DAL). It handles storing and manipulating JSON data for the distributed system. It’s not responsible for displaying this data or handling business logic.

Reactive stores

To create reactive frontends, solair proposes live queries―stores that automatically update when the underlying data changes:

export const displayedDocuments = liveQuery(async () => (await db.blog.all()));

Auth

Authentication and authorization are not an afterthought either.

Authentication (login / logout)

Passkey authentication is available out of the box:

const user = await getCurrentUser()

if(!user) {
	await login()
}

...

await logout()

Authorization

Authorization is also supremely easy to handle thanks to document-level role-based access control:

await db.addUser([doc.id], user.id, "ROLE")

const users = await db.getUsers(doc.id)

await db.removeUser([doc.id], user.id)

Integrations

(coming soon)

Payment