FrontierJS

Schema-seeded fullstack framework

The schema
is the app.

One .lite file seeds your database, your API, and your UI. Write the schema and a six-line service — CRUD, validation, authorization, pagination and live updates are derived, not written.

// db/schema.lite — the seed
model Lead {
  id        Int      
  name      String   (1, 200) 
  email     String     
  status    LeadStatus (new)
  value     Float    (0)
  createdAt DateTime (now())

  // read · create · update · delete
  (0.4.4.5)
  (read, ownerId == auth().id)
}

The idea

One seed, three realms

Every framework helps you write the glue between database, server, and browser. FrontierJS derives glue you never write — and everything traces back to one file.

Data · Model→API · Service→UI · Resource+ReleaseSuite

Derived, not declared

Other frameworks give you a config file to describe your routes and queries. That is still glue you type. Here the schema already knows the shape, the rules, and who may do what — so the API is read out of it, not written beside it.

Authorization travels with the data

Gates and row policies live on the Model and compile into SQL. A query cannot take a code path that forgot to check, because the check is not in the code path — it is in the data layer, under every caller.

One engine on both sides

Litestone is SQLite. The same schema, the same query API and the same gates can run on the server and in the browser — which is what makes offline-first a direction rather than a rewrite.

See it

Schema → Service → Resource

The whole application below is the schema in the hero plus these three files. Nothing is elided.

api/server.ts — the API

import { createApp, createService, announce, $ } from '@frontierjs/junction'

const app = createApp({ db, auth, config: { port: 3200 } })
const live = announce(() => app.channel('leads'))

app.services.register(createService({
  name:  'leads',
  model: 'lead',
  hooks: { after: { create: [live], patch: [live] } },

  // custom action, dispatched by X-Service-Method
  async getStats() {
    const leads = await $.db.lead.findMany()
    return { count: leads.length }
  },
}))

await app.start()

src/routes/leads/index.mesa — the UI

<script>
  import { createResource } from '@frontierjs/sierra/resource'

  // Same four hook phases as the API realm.
  const leads = createResource('leads', {
    hooks: {
      around: { all: [async (ctx, next) => {
        busy = true; await next(); busy = false
      }] },
    },
  })

  let busy = false
  await leads.find()
</script>

<table class="table">
  {#each leads.data as lead}
    <tr><td>{lead.name}</td><td>{lead.email}</td></tr>
  {/each}
</table>

What you did not write

  • Full CRUD at /api/leads
  • 400s from the schema's own rules
  • 401/403s from the Model's gate
  • Pagination and a stable result envelope
  • Row policies compiled into every query
  • TypeScript types for every Model
  • Live WebSocket events on mutation
  • Migrations diffed from the schema
  • JSON Schema, shared by API and UI

The map

Twelve packages, one direction

Litestone ← Junction ← Sierra, never the reverse. Mesa is a leaf with zero workspace dependencies. Use the whole stack, or one piece.

PackageRealmWhat it isState
litestoneData.lite language, SQLite client, gates & policies, migrations, tenants, Litestone Studiov1.1 · 1241 tests
junctionAPIServices, hook pipeline, HTTP/WS transport, channels, browser client703 tests
sierraUIFile-tree routing, Vite build, createResource, static prerender558 tests
mesaUIReactive compiler + signal runtime. Renders to HTML, email, fragment or JS869 tests
authIdentityNative provider over Litestone, ships schema fragments and /auth/*thin coverage
caravanJobsSQLite job queue and cron, exposed as app.jobs67 tests
conduitIntegrationsThird-party integrations, both ends — declared targets over http, ws or unix169 tests
notificationsSliceIn-app record + WebSocket event + email fan-out via app.notifyworking
cli (fli)ToolingMarkdown-native command runtime, scaffolds, deploy pipeline142 tests
cssUISemantics-first design system. Plain CSS, no build step — this page uses itv0.10 · 202 tests
jettyUIBrowser-extension container — Mesa UI with a service-worker relaypre-production
vscodeToolingLitestone language server and Mesa editor supportearly

Extend

Four concepts, everywhere

Learn these once and they hold across every realm. That is the whole design constraint: keep the mental model small enough to fit in your head.

Declaration change the data's own rules

model Invoice {
  total Float (0)
  (0.4.4.8)
  (read, ownerId == auth().id)
  (audit)
}

Hook step into a pipeline

// Same four phases in the API and the UI.
hooks: {
  around: { all:    [timing] },
  before: { create: [setDefaults] },
  after:  { create: [broadcast] },
  error:  { all:    [report] },
}

Plugin attach a capability

export function audit(opts) {
  return {
    name: 'audit',
    register(app) { app.provide('audit', inst) },
    async boot(app)     { await inst.init() },
    async shutdown(app) { await inst.close() },
  }
}

app.configure(audit({ … }))

Provider swap an implementation

// A Plugin adds. A Provider replaces.
const auth: IAuth = {
  verifySession, login, logout,
  createUser, deleteUser, createApiKey,
}

createApp({ db, auth })

Direction

Where this is going

Stated plainly as direction, not as shipped features. Everything above this section runs today; everything in it does not yet.

Rigs — installable full-stack modules

One package that ships a Model, a Service, a Resource and its own conformance tests — install only the parts you want. Take the API without the frontend, and have that be checkable rather than hopeful.

Offline-first, small, self-hosted

The same SQLite engine on both sides means the schema, the queries and the gates come along. Local enforcement, a sync queue, and an app you can run as one binary next to one file.

One target axis

One component set, many outputs: SPA, static, widget, extension — and later desktop and mobile. A new platform should be an entry on a list, not a new framework to learn.

MIT licensed. No account, no hosted service, no phone-home — self-hosting is the default path, not the fallback.

Start

Five minutes to a running API

# scaffold a new app
$ npx /cli@0.1 new my-app
$ cd my-app

# add a model, apply it, run both halves
$ fli scaffold Note  "title:string done:boolean"
$ fli db:push
$ bun run dev

  → api :3000   web :5173
# or add pieces to an existing project
$ npm i /litestone   # Data
$ npm i /junction    # API
$ npm i /sierra      # UI

# batteries, when you want them
$ npm i /auth /caravan