Database layer for Atscript
Your schema is your entire backend
Tables, relations, views, sync, and REST — from a single .as file. No ORM configuration. No migration files. No boilerplate.
Stop scattering your data definitions across ORM configs, migration scripts, and validation layers. One .as file holds your table name, columns, types, indexes, defaults, and constraints. Nothing else to maintain.
@db.table 'products'
export interface Product {
@meta.id
@db.default.increment
id: number
@db.index.fulltext 'search_idx', 3
name: string
description?: string
@db.index.unique 'sku_idx'
sku: string
price: number
@db.default 'active'
status: 'active' | 'archived' | 'draft'
createdAt: number.timestamp.created
}Foreign keys, navigation properties, and cascade rules belong with the data they describe — not in a separate config file you have to keep in sync by hand.
import { Customer } from './customer'
import { OrderItem } from './order-item'
@db.table 'orders'
export interface Order {
@meta.id
@db.default.uuid
id: string
@db.rel.FK
@db.rel.onDelete 'cascade'
customerId: Customer.id
// navigate to parent
@db.rel.to
customer?: Customer
// navigate to children
@db.rel.from
items?: OrderItem[]
total: number
createdAt: number.timestamp.created
} JOINs, filters, and GROUP BY aggregations — declared in your schema, not buried in SQL strings or query builder chains. Schema sync generates the CREATE VIEW for you.
import { Order } from './order'
import { Customer } from './customer'
@db.view 'order_stats'
@db.view.for Order
@db.view.joins Customer, `Customer.id = Order.customerId`
@db.view.filter `Order.status = 'completed'`
export interface OrderStats {
// GROUP BY column
region: Customer.region
@db.agg.sum
revenue: Order.total
@db.agg.count
orderCount: Order.id
@db.agg.avg
avgOrder: Order.total
}Insert, query, update, and delete with a clean API that knows your schema. Filters, sorting, pagination, and full-text search — all type-checked against your model.
import { Product } from "./schema/product.as";
const products = db.getTable(Product);
// insert
await products.insertOne({
name: "Wireless Keyboard",
sku: "KB-200",
price: 79.99,
});
// query with filters and pagination
const results = await products.findMany({
filter: { status: "active", price: { $lte: 100 } },
controls: { $sort: { price: 1 }, $limit: 20 },
});
// full-text search
const matches = await products.search("wireless keyboard"); Migration files accumulate, drift, and break. Schema sync compares your .as definitions against the live database and applies the difference. Hash-gated — zero cost when nothing changed. Safe mode blocks destructive changes in production.
Writing CRUD controllers by hand is work that adds no value. Extend AsDbController, point it at a table, and get filtering, sorting, pagination, and search endpoints with zero boilerplate.
import { AsDbController, TableController } from "@atscript/moost-db";
import { productsTable } from "./db";
import { Product } from "./schema/product.as";
@TableController(productsTable)
export class ProductController extends AsDbController<typeof Product> {}
// GET, POST, PUT, PATCH, DELETE — ready.GET /products/query # list, filter, sort, search
GET /products/pages # paginated results
GET /products/one/:id # read one
GET /products/meta # table metadata
POST /products # create
PUT /products # replace
PATCH /products # partial update
DELETE /products/:id # deletePrototype with SQLite. Ship with PostgreSQL. Switch to MongoDB. Your schema, queries, and controllers stay the same — only the adapter changes.