This system is best thought of as:
- a typed query engine
- a schema and migration system
- a compiler that lowers custom queries to SQLite
- a runtime that reconstructs nested object results
- an application-facing database service with CLI and web tooling
SQLite is the storage engine. The new system is responsible for language, typing, planning, lowering, execution orchestration, and developer experience.
Recommended top-level structure:
core/astcore/parsercore/schemacore/ircore/compilercore/runtimecore/storage-sqliteservercliweb
If implemented as a Rust workspace, these would map naturally to separate crates, with the frontend living as a separate Solid app.
- Define query AST
- Define schema AST
- Keep syntax-layer structures separate from semantic-layer structures
- Tokenization
- Parsing for schema and query syntax
- Source span tracking
- Syntax diagnostics
- Catalog model
- Type definitions
- Link/relation definitions
- Name resolution inputs
- Schema validation
- Migration application model
- Typed intermediate representation
- Cardinality-aware expressions
- Resolved paths and relation traversal
- Backend-independent query representation
- Semantic analysis
- Name resolution
- Type checking
- Cardinality checking
- IR construction
- IR to SQLite-lowering rules
- SQL emission
- Transaction boundaries
- Query execution against SQLite
- Parameter binding
- Result decoding
- Nested result shaping
- Error translation
- Metadata schema
- Physical table mapping
- Join table strategy
- Index creation
- Constraint representation
- Migration persistence
- HTTP API
- Session/auth handling
- Query endpoint
- Schema apply endpoint
- Migration/status endpoints
- Admin and diagnostics endpoints
- Project init
- Schema apply
- Migration create/apply
- Query execution
- Explain/debug output
- Query playground
- Schema browser
- Migration viewer
- Status and diagnostics screens
These questions should be resolved early:
- How object types map to SQLite tables
- How links map to columns vs join tables
- How multi links are represented
- How optionality is enforced
- How computed fields, if any, are modeled
- How enums and custom scalar types are stored
- How schema metadata is versioned
- How migration history is stored
Recommended pipeline:
- Parse query text into AST
- Resolve names against the schema catalog
- Perform type and cardinality analysis
- Produce typed IR
- Lower IR to backend-specific plan
- Emit SQLite SQL
- Execute SQL
- Shape flat rows into nested results
This staged pipeline is worth preserving even in the MVP because it creates clear separation between language concerns and backend concerns.
A typed IR prevents several predictable problems:
- SQL lowering logic becoming tightly coupled to surface syntax
- difficulty supporting diagnostics and explain output
- duplication between validation and execution
- brittle behavior when extending the language later
The IR should carry:
- resolved schema references
- inferred types
- inferred cardinalities
- filter/order/limit semantics
- shape information for nested output
Start with one table per object type. For links:
- use foreign keys for singular relations where possible
- use join tables for multi relations
This keeps the first lowering model straightforward.
SQLite naturally returns flat rows. The engine should own reconstruction into nested objects. This can be implemented by:
- generating joined SQL for simple cases
- batching secondary queries when a single query becomes too complex
- merging rows into object graphs in the runtime layer
Maintain explicit migration files and a metadata table that records:
- applied migration id
- checksum
- applied timestamp
- schema version
Assume SQLite WAL mode and moderate write concurrency. Document limits rather than hiding them. The server should serialize or retry writes where needed.
Support a small schema language such as:
type User {
required name: str
posts: multi Post
}
type Post {
required title: str
author: User
}
Support:
selectinsertupdatedelete- basic filtering
- ordering
- limit/offset
- nested shape selection
Anything beyond that should be added only after IR and lowering are stable.
- HTTP:
axum - Runtime:
tokio - DB access: binding-neutral runner traits with native and WASM SQLite
backends;
vlcn-io/sqlite-rs-embeddedremains the first candidate only if it satisfies prepared statements, bind values, stepping, and metadata reads - Error reporting:
miette - Logging/tracing:
tracing - Config: environment-based plus local project config file
solid-jsviteorSolidStartoptiquefor TypeScript-side demo command parsing if the browser tooling needs a CLI-like command palette- query editor/playground UI
- schema exploration UI
- migration/status UI
- optional in-browser SQL demo after the SQLite runner works in a WASM target
The frontend should start as tooling for developers, not as a consumer app.
- workspace layout
- AST and parser
- schema catalog
- semantic analysis
- typed IR
- SQLite lowering
- runtime execution
- migrations
- CLI
- HTTP server
- Solid playground
- explain/debug tooling
- Keep backend abstraction narrow and explicit
- Do not promise storage-engine portability before it is needed
- Separate syntax, semantics, and execution layers
- Favor debuggability over early optimization
- Prefer a small coherent language to a broad inconsistent one
Gelite uses project-specific file extensions during the 0.x series:
.gelifor declarative schema source files.geliqlfor query, script, and future migration files
This follows Gel's separation between schema source files and EdgeQL migration
or script files, but does not reuse Gel's .gel and .edgeql extensions.
Gelite is not a Gel-compatible implementation, and the 0.x language may change
as the parser, resolver, and storage layers become more precise.
The initial schema convention should stay simple:
schema.geli
When migration workflows are introduced, the project can move toward a directory layout such as:
dbschema/default.geli
dbschema/migrations/00001.geliql
queries/list_posts.geliql
The extension choice is not a semantic guarantee before 1.0. If the language changes enough to require a different convention, the extension may be changed while the project is still in the 0.x line.
The first end-to-end deliverable should be:
- a schema file
- a migration apply command
- a query command
- a Rust HTTP endpoint for query execution
- a simple Solid playground that can run a query and show shaped JSON results
That is small enough to finish, but complete enough to validate the engine's core architecture.