Manual Database At a Glance

At a Glance

elements man database/at-a-glance Read as markdown

A schema, a migration, and the queries that read and write it.

import { sql, tx, session } from "@elements/app";

interface User { id: string; name: string; email: string; createdAt: Date; }
interface Order { id: string; userId: string; total: number; }

/** @rpc */
export function placeOrder(items: { sku: string; qty: number }[]): Order {
  session.isLoggedInOrThrow();

  let userId = session.getOrThrow("userId");

  return tx(() => {
    let order = sql<Order>(`
      insert into orders (userId, total) values (${userId}, ${total(items)}) returning *
    `).firstOrThrow();

    for (let item of items) {
      sql(`
        insert into orderItems (orderId, sku, qty) values (${order.id}, ${item.sku}, ${item.qty})
      `);
    }

    return order;
  });
}

function total(items: { sku: string; qty: number }[]): number {
  let result = 0;
  for (let row of sql<{ sku: string; price: number }>(
    `select sku, price from products where sku = any(${items.map(i => i.sku)})`
  )) {
    let item = items.find(i => i.sku === row.sku);
    if (item) {
      result += row.price * item.qty;
    }
  }
  return result;
}

The code reads as a sequence of statements with no await keywords. Column names stay camelCase throughout. Each ${value} is extracted to a positional parameter at build time, so the query is parameterized at runtime. The entire body runs inside a Postgres transaction because of the surrounding tx().