Nautilus — Rust-powered ORM CLI
Project description
Nautilus
Nautilus is a schema-first ORM toolkit built around a Rust query engine.
This repository currently includes:
- a
.nautilusschema language - generators for Rust, Python, and JavaScript/TypeScript clients
- a
nautilusCLI for validate/format/generate/db/migrate workflows - a JSON-RPC engine over stdin/stdout
- an LSP server and a VS Code extension
- PostgreSQL, MySQL, and SQLite support
Workspace map
| Component | Role |
|---|---|
| crates/nautilus-cli | nautilus CLI (generate, validate, format, db, migrate, engine, python) |
| crates/nautilus-schema | Lexer, parser, validator, formatter, editor analysis for .nautilus |
| crates/nautilus-codegen | Rust / Python / JS client generation |
| crates/nautilus-engine | JSON-RPC engine runtime |
| crates/nautilus-protocol | Wire-format types and method contracts |
| crates/nautilus-core | Query AST, expressions, typed columns, values |
| crates/nautilus-dialect | SQL rendering for Postgres / MySQL / SQLite |
| crates/nautilus-connector | sqlx-backed executors and connector client |
| crates/nautilus-migrate | Schema diffing, DDL generation, migration execution |
| crates/nautilus-lsp | LSP server for schema files |
| tools/vscode-nautilus-schema | VS Code extension wiring syntax + LSP |
Dependency graph
graph LR
core[nautilus-core]
dialect[nautilus-dialect]
connector[nautilus-connector]
schema[nautilus-schema]
codegen[nautilus-codegen]
migrate[nautilus-migrate]
lsp[nautilus-lsp]
protocol[nautilus-protocol]
engine[nautilus-engine]
cli[nautilus-orm]
dialect --> core
connector --> dialect
connector --> core
codegen --> schema
migrate --> schema
lsp --> schema
cli --> schema
engine --> connector
engine --> protocol
cli --> engine
Installation
Python
pip install nautilus-orm
JavaScript / TypeScript
npm install @y0gm4/nautilus-orm
Rust
cargo install nautilus-orm
CLI (all platforms)
# macOS / Linux
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/y0gm4/nautilus/releases/latest/download/nautilus-orm-installer.sh | sh
# Windows
powershell -ExecutionPolicy ByPass -c "irm https://github.com/y0gm4/nautilus/releases/latest/download/nautilus-orm-installer.ps1 | iex"
Define your schema
Create a file *.nautilus and define your schema such as:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "nautilus-client-py" // or "nautilus-client-rs", "nautilus-client-js"
output = "db"
}
enum Role {
USER
ADMIN
MODERATOR
}
enum OrderStatus {
PENDING
CONFIRMED
SHIPPED
DELIVERED
CANCELLED
}
type Address {
street String
city String
zip String
country String
}
model User {
id Uuid @id @default(uuid())
email String @unique
username VarChar(30) @unique
name String
role Role @default(USER)
bio String?
tags String[]
address Address?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
profile Profile?
orders Order[]
@@index([email], type: Hash)
@@index([createdAt], type: Brin, map: "idx_users_created")
@@map("users")
}
model Profile {
id Int @id @default(autoincrement())
userId Uuid @unique @map("user_id")
avatar String?
website VarChar(255)?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("profiles")
}
model Product {
id BigInt @id @default(autoincrement())
name String
slug VarChar(100) @unique
description String?
price Decimal(10, 2) @check(price > 0)
discount Decimal(5, 2) @default(0)
finalPrice Decimal(10, 2) @computed(price - discount, Stored) @map("final_price")
stock Int @default(0) @check(stock >= 0)
tags String[]
metadata Json?
active Boolean @default(true)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
orderItems OrderItem[]
@@index([tags], type: Gin)
@@index([name, slug])
@@map("products")
}
model Order {
id BigInt @id @default(autoincrement())
userId Uuid @map("user_id")
status OrderStatus @default(PENDING)
totalAmount Decimal(12, 2) @map("total_amount")
note String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Restrict)
items OrderItem[]
@@check(totalAmount > 0)
@@index([userId, status])
@@index([createdAt], type: Brin, map: "idx_orders_created")
@@map("orders")
}
model OrderItem {
id BigInt @id @default(autoincrement())
orderId BigInt @map("order_id")
productId BigInt @map("product_id")
quantity Int @check(quantity > 0)
unitPrice Decimal(10, 2) @map("unit_price")
lineTotal Decimal(12, 2) @computed(quantity * unitPrice, Stored) @map("line_total")
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
product Product @relation(fields: [productId], references: [id], onDelete: Restrict)
@@unique([orderId, productId])
@@map("order_items")
}
Then validate, push, and generate:
nautilus validate --schema schema.nautilus
nautilus db push --schema schema.nautilus
nautilus generate --schema schema.nautilus
nautilus db push regenerates the client automatically unless you pass --no-generate.
Generated clients are local build artifacts, not registry packages. If your
schema uses output = "./db", the normal consumption path is to import that
directory directly, for example from db import Nautilus in Python or
import { Nautilus } from "./db/index.js" in JavaScript.
Usage Examples
CRUD Operations
Python
Async context manager:
import asyncio
from db import Nautilus
async def main():
async with Nautilus() as client:
# Create a user with enum, array, and composite type
user = await client.user.create({
"email": "alice@example.com",
"username": "alice",
"name": "Alice Smith",
"role": "ADMIN",
"tags": ["vip", "early-adopter"],
"address": {
"street": "123 Main St",
"city": "Portland",
"zip": "97201",
"country": "US",
},
})
# Find unique by @unique field
found = await client.user.find_unique(where={"email": "alice@example.com"})
# Find many with enum filter
admins = await client.user.find_many(where={"role": "ADMIN"})
# Update — updatedAt is set automatically
updated = await client.user.update(
where={"email": "alice@example.com"},
data={"role": "MODERATOR", "bio": "Hello world"},
)
# Create a product — finalPrice is computed automatically
product = await client.product.create({
"name": "Mechanical Keyboard",
"slug": "mechanical-keyboard",
"price": 149.99,
"discount": 20.00,
# finalPrice = 129.99 (computed: price - discount)
"stock": 50,
"tags": ["electronics", "peripherals"],
"metadata": {"weight_kg": 0.8, "color": "black"},
})
# Delete
await client.user.delete(where={"email": "alice@example.com"})
asyncio.run(main())
Manual connect / disconnect:
client = Nautilus()
await client.connect()
user = await client.user.create({
"email": "alice@example.com",
"username": "alice",
"name": "Alice Smith",
})
await client.disconnect()
Auto-register — call operations directly from model classes:
from db import Nautilus, User, Product
async with Nautilus(auto_register=True) as client:
# No need to go through `client.user` — use User.nautilus directly
user = await User.nautilus.create({"email": "alice@example.com", "username": "alice", "name": "Alice Smith"})
admins = await User.nautilus.find_many(where={"role": "ADMIN"})
products = await Product.nautilus.find_many(where={"active": True})
JavaScript / TypeScript
import { Nautilus } from './db/client';
async function main() {
const client = new Nautilus();
await client.connect();
// Create with enum, array, and composite type
const user = await client.user.create({
data: {
email: 'alice@example.com',
username: 'alice',
name: 'Alice Smith',
role: 'ADMIN',
tags: ['vip', 'early-adopter'],
address: {
street: '123 Main St',
city: 'Portland',
zip: '97201',
country: 'US',
},
},
});
// Find unique
const found = await client.user.findUnique({
where: { email: 'alice@example.com' },
});
// Find many with enum filter
const admins = await client.user.findMany({
where: { role: 'ADMIN' },
});
// Update — updatedAt is set automatically
const updated = await client.user.update({
where: { email: 'alice@example.com' },
data: { role: 'MODERATOR', bio: 'Hello world' },
});
// Create a product — finalPrice is computed automatically
const product = await client.product.create({
data: {
name: 'Mechanical Keyboard',
slug: 'mechanical-keyboard',
price: 149.99,
discount: 20.0,
stock: 50,
tags: ['electronics', 'peripherals'],
metadata: { weight_kg: 0.8, color: 'black' },
},
});
// Delete
await client.user.delete({
where: { email: 'alice@example.com' },
});
await client.disconnect();
}
main();
Rust
Assume provider = "nautilus-client-rs" and interface = "async" in the generator block.
use db::{
Address, Client, Product, ProductCreateInput, Role, User, UserCreateInput, UserDeleteArgs,
UserUpdateArgs, UserUpdateInput,
};
use nautilus_core::{FindManyArgs, FindUniqueArgs, IncludeRelation};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = Client::postgres(&std::env::var("DATABASE_URL")?).await?;
// Create with enum, array, and composite type
let user = User::nautilus(&client)
.create(UserCreateInput {
email: Some("alice@example.com".into()),
username: Some("alice".into()),
name: Some("Alice Smith".into()),
role: Some(Role::Admin),
tags: Some(vec!["vip".into(), "early-adopter".into()]),
address: Some(Some(Address {
street: "123 Main St".into(),
city: "Portland".into(),
zip: "97201".into(),
country: "US".into(),
})),
..Default::default()
})
.await?;
// Find unique with eager-loaded relations
let found = User::nautilus(&client)
.find_unique(
FindUniqueArgs::new(User::email().eq("alice@example.com"))
.with_include("orders", IncludeRelation::plain()),
)
.await?;
// Find many with enum filter
let admins = User::nautilus(&client)
.find_many(FindManyArgs {
where_: Some(User::role().eq(Role::Admin)),
..Default::default()
})
.await?;
// Update — updatedAt is set automatically
let updated = User::nautilus(&client)
.update(UserUpdateArgs {
where_: Some(User::email().eq("alice@example.com")),
data: UserUpdateInput {
role: Some(Role::Moderator),
bio: Some(Some("Hello world".into())),
..Default::default()
},
})
.await?;
// Create a product — finalPrice is computed automatically
let product = Product::nautilus(&client)
.create(ProductCreateInput {
name: Some("Mechanical Keyboard".into()),
slug: Some("mechanical-keyboard".into()),
price: Some(rust_decimal::Decimal::new(14999, 2)),
discount: Some(rust_decimal::Decimal::new(2000, 2)),
stock: Some(50),
tags: Some(vec!["electronics".into(), "peripherals".into()]),
metadata: Some(Some(serde_json::json!({
"weight_kg": 0.8,
"color": "black"
}))),
..Default::default()
})
.await?;
// Delete
User::nautilus(&client)
.delete(UserDeleteArgs {
where_: Some(User::email().eq("alice@example.com")),
})
.await?;
Ok(())
}
Transactions
Python
import asyncio
from db import Nautilus
async def main():
async with Nautilus() as client:
# Context-manager style
async with client.transaction() as tx:
user = await tx.user.create({
"email": "bob@example.com",
"username": "bob",
"name": "Bob Jones",
})
order = await tx.order.create({
"userId": user.id,
"status": "CONFIRMED",
"totalAmount": 149.99,
})
await tx.order_item.create({
"orderId": order.id,
"productId": 1,
"quantity": 1,
"unitPrice": 149.99,
# lineTotal = 149.99 (computed: quantity * unitPrice)
})
# auto-committed on exit; rolled back on exception
# Callback style
async def promote(tx):
sender = await tx.user.update(
where={"email": "alice@example.com"},
data={"role": "USER"},
)
receiver = await tx.user.update(
where={"email": "bob@example.com"},
data={"role": "ADMIN"},
)
return sender, receiver
result = await client.transaction(promote, timeout_ms=10000)
asyncio.run(main())
JavaScript / TypeScript
import { Nautilus } from './db/client';
async function main() {
const client = new Nautilus();
await client.connect();
const result = await client.$transaction(async (tx) => {
const user = await tx.user.create({
data: { email: 'bob@example.com', username: 'bob', name: 'Bob Jones' },
});
const order = await tx.order.create({
data: {
userId: user!.id,
status: 'CONFIRMED',
totalAmount: 149.99,
},
});
await tx.orderItem.create({
data: {
orderId: order!.id,
productId: 1,
quantity: 1,
unitPrice: 149.99,
},
});
return order;
});
await client.disconnect();
}
main();
Rust
use db::{
Client, Order, OrderCreateInput, OrderItem, OrderItemCreateInput, OrderStatus,
TransactionOptions, User, UserCreateInput,
};
use rust_decimal::Decimal;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = Client::postgres(&std::env::var("DATABASE_URL")?).await?;
let order = client.transaction(TransactionOptions::default(), |tx| Box::pin(async move {
let user = User::nautilus(&tx)
.create(UserCreateInput {
email: Some("bob@example.com".into()),
username: Some("bob".into()),
name: Some("Bob Jones".into()),
..Default::default()
})
.await?;
let order = Order::nautilus(&tx)
.create(OrderCreateInput {
user_id: Some(user.id),
status: Some(OrderStatus::Confirmed),
total_amount: Some(Decimal::new(14999, 2)),
..Default::default()
})
.await?;
OrderItem::nautilus(&tx)
.create(OrderItemCreateInput {
order_id: Some(order.id),
product_id: Some(1),
quantity: Some(1),
unit_price: Some(Decimal::new(14999, 2)),
..Default::default()
})
.await?;
Ok(order)
})).await?;
Ok(())
}
Client targets
| Generator provider | Output | Notes |
|---|---|---|
nautilus-client-rs |
Rust source tree | nautilus generate --standalone also emits a Cargo.toml; by default generation integrates the output with the nearest Cargo workspace unless --no-install is used |
nautilus-client-py |
Python package | Default workflow: import the generated output package directly. install = true copies the same generated files into Python site-packages/nautilus for local convenience; it does not publish anything to PyPI. Supports interface = "sync" or interface = "async"; recursive_type_depth is currently Python-only |
nautilus-client-js |
JS runtime + .d.ts typings |
Default workflow: import from the generated output directory. install = true copies the same generated files into the nearest node_modules/nautilus; it does not publish an npm package |
nautilus-client-rs, nautilus-client-py, and nautilus-client-js are schema
provider names that select the generator. They are not necessarily the module
or package names you import at runtime.
CLI surface
| Command group | What it covers |
|---|---|
generate, validate, format |
Schema validation, code generation, canonical formatting |
db push, db status, db pull, db drop, db reset, db seed |
Live-database workflows, introspection, destructive resets, seed scripts |
migrate generate, migrate apply, migrate rollback, migrate status |
Versioned SQL migration workflow |
engine serve |
Starts the JSON-RPC engine used by generated clients |
python install, python uninstall |
Installs or removes a Python shim so python -m nautilus works without pip packaging |
See crates/nautilus-cli/README.md for the command-level breakdown.
Editor support
nautilus-lspprovides diagnostics, completions, hover, go-to-definition, formatting, and semantic tokens.- The VS Code extension in tools/vscode-nautilus-schema (you can also download from vscode marketplace) bundles syntax support and can auto-download the
nautilus-lspbinary on first activation. - If you already manage the binary yourself, set:
{
"nautilus.lspPath": "/absolute/path/to/nautilus-lsp"
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nautilus_orm-0.1.7-py3-none-any.whl.
File metadata
- Download URL: nautilus_orm-0.1.7-py3-none-any.whl
- Upload date:
- Size: 7.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
39b7893e3f28dadc42c5050d153c63ceca37e5c6b00754d1163d0c740a700301
|
|
| MD5 |
4a508d0f17311541bbd5fb01b6a956e1
|
|
| BLAKE2b-256 |
2b0eb722326dedcba95842b3917e9f2fe14471e042b025744a6b040257a24c52
|