An ambitious experiment in server-side Swift calling into WASM blobs to handle requests, with a hopeful mix of Pocketbase/Firebase/BaaS thrown in too. Uses JavaScriptCore as the WASM runtime.
The goals:
- includes an Admin panel for managing WASM blobs/hooks and eventually "collections"
- an API for routing requests to WASM blobs
- an API that auto-generates CRUD operations for "collections"
- serves up any static content in the public/
directory
- stores data in a Sqlite DB that is configured to be compatible with LiteStream and friends
- includes authentication and authorization tooling, configurable through the admin panel
Project Tour
Sources/
,Tests/
- Vapor v4/Swift 5.8 project code and tests. This represents the beating heart of LimeJuiceSources/App/Wasm/
- Wrappers for running WASM blobs inside of JavaScriptCore
Sources/App/AdminUI/
- Build output for the Admin UI project. Not checked in but built into the swift binary as a resourceAdminUI/
- SvelteKit (TypeScript) project code for the Admin UI. Built as a static site that is bundled into the LimeJuice binary.AdminUI/src/lib/ApiClient/
- API client code which is modeled after the PocketBase JavaScript SDK and should be (eventually) usable for both the Admin UI AND external consumers of LimeJuice
zig-wasm-api/
- Zig v0.10.1 project to experiment with fleshing out the WASM side API and building test WASM blobs to run within LimeJuice. May eventually get mutated into the "official" Zig library for LimeJuice WASM.zig-wasm-api/src/limejuice_api/
- A Zig wrapper around the imports from LimeJuice
Server Routes
/api/
- The API base/api/admins
- Admin specific functionality. Currently just includes authentication for Admins./api/functions(/:id)
- WASM Function (currently just for API endpoint blobs) CRUD/api/functions/:name/call
- Calls the named WASM blob. Accepts methods: HEAD, GET, POST, PATCH, PUT, DELETE
/_/
- The Admin UI/*
Serves up anything within theLimeJuice_data/public/
directory as a fallback
Docs
Bare (hah, cause these are barely docs :D) with me here, I'm still fleshing out thoughts and how all of this should work, so these "docs" might not 100% reflect the current system and are by no means the canonical reference yet.
Folder Structure
LimeJuice places all of its data in the current working directories LimeJuice_data/
directory. This makes deployments super easy: just copy up the whole directory to whatever host. This directory has the following structure:
LimeJuice.sqlite
- The SQLite DB that all the Collections and site configuration lives inpublic/
- Anything placed in here will be automatically served up by LimeJuice as is. For example: You can place static HTML or even a rendered JAM Stack site into this directory.hooks/
- The WASM hooks directory. Any WASM blob in this directory will be loaded into the system as a Hook and show up as configurable through the Admin UI.migrations/
- JavaScript scripts representing schema migrations for the Collections. Schema migrations are normally generated automatically by LimeJuice as Collections are modified via the Admin UI, but for advanced cases you can manually write your own migrations as well.- New migrations will automatically be run on application boot or with the
migrations apply
command. - New reverts will automatically be run with the
migrations rollback
command.
- New migrations will automatically be run on application boot or with the
Collections
Collections, like in PocketBase, are SQlite tables which are entirely configurable through the Admin UI. An entry within a Collection is referred to as a Record. Records tend to be the primary unit of interaction for external systems with LimeJuice.
A Note on Schema Migrations
AKA: Why are they JavaScript and not also WASM?
While it'd be fun to have schema migrations for collections to be exposed to WASM-land, they ultimately have a need to be generated by LimeJuice, as a result of changes through the Admin UI so either LimeJuice would need to have a way to generate WASM for new schema migrations or we'll need another system that is friendly to both manual creation and automatic management/generation. Luckily, we're already running our WASM within a JavaScript engine already so we can just leverage that as it's easy enough to template out JavaSript.
Why not SQL scripts like <unique name>.(up/down).sql
? We could do this, but the amount of friction introduced for manually created migrations is a bit too much. Part of the problem is that we need to keep both the collections backing table AND LimeJuice's internal collections metadata table in sync. By using a JavaScript migration system, we can ensure that this happens automatically by exposing a higher level API for changing a collections structure.
Why not JSON/YAML/TOML files representing a set of changes to apply in configuration? Wellllll ... We could do this too but no. While it could solve the problem that SQL scripts have with ensuring the two tables are in sync, it'd provide a less than stellar debugging experience that I'd rather avoid. Honestly JavaScript actually feels like a better fit here.
Authentication
Authorization / Permissions
Hooks
TL;DR Hooks are a way to tie into the LimeJuice runtime and extend it all from any language that can run within WASM. This lets you modify Records, requests, etc and to do things like: - Build a GraphQL endpoint - Validate or modify a Record before it's saved to the database - Implement Webhook or custom HTML Form post handling - Build out a server-rendered frontend - Even revamp the whole permission or authorization system
Hooks are the root of LimeJuice's existence and it's distinguishing feature. While not unique, PocketBase has Go hooks and is currently working on 0.17.0 which has JavaScript hooks, LimeJuice takes a different approach and uses WASM.