Functions, tables and indexes
use rxferrix::prelude::*;
#[table(name = "todos")]
#[index(by_done = [done])]
pub struct Todo { pub text: String, pub done: bool }
#[query]
pub async fn list(ctx: &QueryCtx, done: Option<bool>) -> Result<Vec<Doc<Todo>>> {
let q = match done {
Some(d) => ctx.db.query::<Todo>().with_index(Todo::by_done, |q| q.eq(Todo::done, d)),
None => ctx.db.query::<Todo>(),
};
q.order(Order::Desc).take(500).await
}
#[mutation]
pub async fn add(ctx: &MutationCtx, text: String) -> Result<Id<Todo>> {
ctx.db.insert(Todo { text, done: false }).await
}
fn main() -> rxferrix::Result<()> { rxferrix::RxFerrix::new().run() }Actions call other functions through the runtime with turbofish: ctx.run_mutation::<add>(("hi".into(),)).await? (also run_query and run_action). Queries and mutations have no run_*: they share code through plain Rust functions.
Indexes end in _creationTime, _id: documents with equal indexed fields are in creation order, so with_index(Message::by_channel, |q| q.eq(Message::channel, c)).order(Order::Desc) is newest first, and the creation time can be bounded after the declared fields: q.eq(Message::channel, c).gt(Message::_creation_time, since). .count() counts an index range without reading the documents (unless the query has a filter).
Types that aren't tables (a function's output "view" struct, an enum argument) use #[object]: it derives serde and ts-rs through rxferrix (camelCase JSON), so the app needs neither as a dependency. Codegen declares them in dataModel.ts by name (Array<MessageView>), with field doc comments as JSDoc. A document can be embedded whole:
#[object]
pub struct MessageView {
#[serde(flatten)]
pub message: Doc<Message>, // _id, _creationTime and the message's fields, at the top level
pub author_name: String, // TS: { authorName: string } & Doc<"messages">
}
#[query]
pub async fn list(ctx: &QueryCtx, channel: Id<Channel>, pagination_opts: PaginationOpts)
-> Result<PaginationResult<MessageView>> {
let page = ctx.db.query::<Message>()
.with_index(Message::by_channel, |q| q.eq(Message::channel, channel))
.order(Order::Desc)
.paginate(pagination_opts)
.await?;
page.try_map(|message| view(ctx, message)).await // also map_async, try_map_concurrent
}Dev mode. ctx.is_dev() tells whether the server runs with --dev. #[action(dev_only)] (also queries and mutations) marks a function for development (seeding, reading a dev inbox): the server refuses it outside --dev, and codegen puts it in devApi instead of api. In --dev without SMTP, auth emails are printed to the log by DevLogMailer; AuthConfig::dev_mailer(m) replaces it (and can forward to it).