JS20

Code reduction calculation

Here you can find information about how the claims related to code reductions are calculated. We can not guarantee your code reduction will be exactly as calculated here, but hope you will see a large reduction in your codebase by using JS20.

Methodology

We will calculate code reduction like this:

  1. Define example scenario
  2. Write JS20 code for scenario
  3. Use AI to generate traditional dev code based on the scenario
  4. Compare number of characters (excluding whitespace) between the two codebases

What we mean with traditional dev

We mean including barebones frameworks to get a web server & db connection up and running, but not any advanced tooling like schema generation or similar.


Included in our traditional dev definition:

Not included:

Example scenario / prompt

Your job is to build a web server backend in Node using Express and MySQL.

Scenario: meeting-room reservations.


Do not include any schema libraries like Zod or similar.

Use BetterAuth email & password. Leave send email out of scope for now.

Don't use any Relationships between SQL models, just use ids & where statements.

Use Sequelize for ORM. Sync & generate tables.


Models:

  1. Booking
  2. Room

Room should have a room type, with enum values: Small, Medium, Large.


Business rules:

  1. No overlapping bookings in the same room
  2. Attendees ≤ room capacity
  3. Duration ≥ 1 hour
  4. Start must be in the future and within 30 days

Authentication:

Database

For every endpoint:

Define clear typescript types, e.g. interface Room {}, interface Booking {} etc.


Endpoints:

Write a Frontend API client:

Answer with two files:

  1. server.ts with all backend TypeScript code
  2. client.ts with all frontend API client TypeScript code

Traditional dev code

Written by ChatGPT-5 based on the scenario/prompt above.

Backend code

// server.ts
import express from "express";
import { Sequelize, DataTypes, Model, Optional } from "sequelize";
import mysql from "mysql2/promise";
import { betterAuth } from "better-auth";
import { fromNodeHeaders, toNodeHandler } from "better-auth/node";

const app = express();
app.use(express.json());

const DB_HOST = process.env.DB_HOST || "127.0.0.1";
const DB_USER = process.env.DB_USER || "root";
const DB_PASS = process.env.DB_PASS || "";
const DB_NAME = process.env.DB_NAME || "meetings";
const DB_PORT = Number(process.env.DB_PORT || 3306);

const sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASS, {
    host: DB_HOST,
    port: DB_PORT,
    dialect: "mysql",
    logging: false
});

const pool = mysql.createPool({
    host: DB_HOST,
    port: DB_PORT,
    user: DB_USER,
    password: DB_PASS,
    database: DB_NAME,
    waitForConnections: true,
    connectionLimit: 10,
    namedPlaceholders: true
});

const auth = betterAuth({
    emailAndPassword: {
        enabled: true,
    },
    database: { type: "sequelize", client: sequelize as any }
});

app.use(toNodeHandler(auth));

enum RoomType {
    Small = "Small",
    Medium = "Medium",
    Large = "Large"
}

interface ModelBase {
    id: number;
    ownerId: string;
    createdAt: Date;
    updatedAt: Date;
}

interface RoomAttributes extends ModelBase {
    name: string;
    type: RoomType;
    capacity: number;
}
interface RoomCreationAttributes extends Optional<RoomAttributes, "id" | "createdAt" | "updatedAt"> {}
class Room extends Model<RoomAttributes, RoomCreationAttributes> implements RoomAttributes {
    public id!: number;
    public ownerId!: string;
    public name!: string;
    public type!: RoomType;
    public capacity!: number;
    public readonly createdAt!: Date;
    public readonly updatedAt!: Date;
}
Room.init(
    {
        id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true },
        ownerId: { type: DataTypes.STRING(191), allowNull: false },
        name: { type: DataTypes.STRING(191), allowNull: false },
        type: { type: DataTypes.ENUM(...Object.values(RoomType)), allowNull: false },
        capacity: { type: DataTypes.INTEGER.UNSIGNED, allowNull: false },
        createdAt: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
        updatedAt: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW }
    },
    { sequelize, tableName: "rooms", timestamps: true, indexes: [{ fields: ["ownerId"] }, { fields: ["type"] }] }
);

interface BookingAttributes extends ModelBase {
    roomId: number;
    title: string;
    start: Date;
    end: Date;
    attendees: number;
}
interface BookingCreationAttributes extends Optional<BookingAttributes, "id" | "createdAt" | "updatedAt"> {}
class Booking extends Model<BookingAttributes, BookingCreationAttributes> implements BookingAttributes {
    public id!: number;
    public ownerId!: string;
    public roomId!: number;
    public title!: string;
    public start!: Date;
    public end!: Date;
    public attendees!: number;
    public readonly createdAt!: Date;
    public readonly updatedAt!: Date;
}
Booking.init(
    {
        id: { type: DataTypes.INTEGER.UNSIGNED, autoIncrement: true, primaryKey: true },
        ownerId: { type: DataTypes.STRING(191), allowNull: false },
        roomId: { type: DataTypes.INTEGER.UNSIGNED, allowNull: false },
        title: { type: DataTypes.STRING(191), allowNull: false },
        start: { type: DataTypes.DATE, allowNull: false },
        end: { type: DataTypes.DATE, allowNull: false },
        attendees: { type: DataTypes.INTEGER.UNSIGNED, allowNull: false },
        createdAt: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW },
        updatedAt: { type: DataTypes.DATE, allowNull: false, defaultValue: DataTypes.NOW }
    },
    { sequelize, tableName: "bookings", timestamps: true, indexes: [{ fields: ["ownerId"] }, { fields: ["roomId"] }, { fields: ["start"] }, { fields: ["end"] }] }
);

async function syncDb() {
    await sequelize.authenticate();
    await sequelize.sync({ alter: true });
}
syncDb().catch((e) => {
    console.error(e);
    process.exit(1);
});

type AuthedRequest = express.Request & { auth?: { user: { id: string } } };

function requireAuth(req: AuthedRequest, res: express.Response, next: express.NextFunction) {
    try {
        const session = (req as any).auth;
        if (!session?.user?.id) return res.status(401).json({ error: "Unauthorized" });
        next();
    } catch {
        return res.status(401).json({ error: "Unauthorized" });
    }
}

function userId(req: AuthedRequest): string {
    return (req as any).auth.user.id as string;
}

function isNonEmptyString(x: unknown): x is string {
    return typeof x === "string" && x.trim().length > 0;
}
function isPositiveInt(x: unknown): x is number {
    return Number.isInteger(x) && (x as number) > 0;
}
function parseIntParam(x: unknown): number {
    const n = Number(x);
    if (!Number.isInteger(n) || n <= 0) throw new Error("Invalid id");
    return n;
}
function isRoomType(x: unknown): x is RoomType {
    return typeof x === "string" && (Object.values(RoomType) as string[]).includes(x);
}
function isISODateString(x: unknown): x is string {
    return typeof x === "string" && !Number.isNaN(Date.parse(x));
}
function validateRoomOutput(r: any): r is RoomAttributes {
    return (
        r &&
        isPositiveInt(r.id) &&
        isNonEmptyString(r.ownerId) &&
        isNonEmptyString(r.name) &&
        isRoomType(r.type) &&
        isPositiveInt(r.capacity) &&
        new Date(r.createdAt).toString() !== "Invalid Date" &&
        new Date(r.updatedAt).toString() !== "Invalid Date"
    );
}
function validateBookingOutput(b: any): b is BookingAttributes {
    return (
        b &&
        isPositiveInt(b.id) &&
        isNonEmptyString(b.ownerId) &&
        isPositiveInt(b.roomId) &&
        isNonEmptyString(b.title) &&
        new Date(b.start).toString() !== "Invalid Date" &&
        new Date(b.end).toString() !== "Invalid Date" &&
        isPositiveInt(b.attendees) &&
        new Date(b.createdAt).toString() !== "Invalid Date" &&
        new Date(b.updatedAt).toString() !== "Invalid Date"
    );
}

function nowUtc(): Date {
    return new Date();
}
function addDays(d: Date, n: number): Date {
    const x = new Date(d.getTime());
    x.setUTCDate(x.getUTCDate() + n);
    return x;
}
function diffMs(a: Date, b: Date) {
    return a.getTime() - b.getTime();
}
async function assertRoomExists(roomId: number): Promise<RoomAttributes> {
    const r = await Room.findByPk(roomId);
    if (!r) throw new Error("Room not found");
    return r.toJSON() as RoomAttributes;
}
async function assertNoOverlap(roomId: number, start: Date, end: Date, ignoreBookingId?: number) {
    const [rows] = await pool.query<any>(
        `SELECT id FROM bookings
         WHERE roomId = :roomId
         AND start < :end
         AND end > :start
         ${ignoreBookingId ? "AND id <> :ignoreId" : ""}
         LIMIT 1`,
        { roomId, start, end, ignoreId: ignoreBookingId }
    );
    if (Array.isArray(rows) && rows.length > 0) throw new Error("Overlapping booking exists");
}
function ensureBookingBusinessRules(room: RoomAttributes, startIso: string, endIso: string, attendees: number) {
    if (!isISODateString(startIso) || !isISODateString(endIso)) throw new Error("Invalid dates");
    const start = new Date(startIso);
    const end = new Date(endIso);
    const n = nowUtc();
    if (diffMs(start, n) <= 0) throw new Error("Start must be in the future");
    if (start > addDays(n, 30)) throw new Error("Start must be within 30 days");
    if (diffMs(end, start) < 60 * 60 * 1000) throw new Error("Duration must be at least 1 hour");
    if (!Number.isInteger(attendees) || attendees <= 0) throw new Error("Invalid attendees");
    if (attendees > room.capacity) throw new Error("Attendees exceed room capacity");
    return { start, end };
}

app.use(requireAuth);

app.post("/rooms", async (req: AuthedRequest, res) => {
    try {
        const ownerId = userId(req);
        const { name, type, capacity } = req.body ?? {};
        if (!isNonEmptyString(name)) throw new Error("Invalid name");
        if (!isRoomType(type)) throw new Error("Invalid type");
        if (!isPositiveInt(capacity)) throw new Error("Invalid capacity");
        const created = await Room.create({ ownerId, name: name.trim(), type, capacity });
        const out = created.toJSON();
        if (!validateRoomOutput(out)) throw new Error("Invalid output");
        return res.status(200).json(out);
    } catch (e: any) {
        return res.status(e.message === "Unauthorized" ? 401 : 500).json({ error: e.message || "Server error" });
    }
});

app.get("/rooms", async (req: AuthedRequest, res) => {
    try {
        const ownerId = userId(req);
        const rows = await Room.findAll({ where: { ownerId }, order: [["id", "ASC"]] });
        const out = rows.map((r) => r.toJSON());
        if (!out.every(validateRoomOutput)) throw new Error("Invalid output");
        return res.status(200).json(out);
    } catch (e: any) {
        return res.status(e.message === "Unauthorized" ? 401 : 500).json({ error: e.message || "Server error" });
    }
});

app.get("/rooms/:id", async (req: AuthedRequest, res) => {
    try {
        const ownerId = userId(req);
        const id = parseIntParam(req.params.id);
        const row = await Room.findOne({ where: { id, ownerId } });
        if (!row) throw new Error("Not found");
        const out = row.toJSON();
        if (!validateRoomOutput(out)) throw new Error("Invalid output");
        return res.status(200).json(out);
    } catch (e: any) {
        return res.status(e.message === "Unauthorized" ? 401 : 500).json({ error: e.message || "Server error" });
    }
});

app.put("/rooms/:id", async (req: AuthedRequest, res) => {
    try {
        const ownerId = userId(req);
        const id = parseIntParam(req.params.id);
        const { name, type, capacity } = req.body ?? {};
        if (name !== undefined && !isNonEmptyString(name)) throw new Error("Invalid name");
        if (type !== undefined && !isRoomType(type)) throw new Error("Invalid type");
        if (capacity !== undefined && !isPositiveInt(capacity)) throw new Error("Invalid capacity");
        const row = await Room.findOne({ where: { id, ownerId } });
        if (!row) throw new Error("Not found");
        if (name !== undefined) row.name = name.trim();
        if (type !== undefined) row.type = type;
        if (capacity !== undefined) row.capacity = capacity;
        await row.save();
        const out = row.toJSON();
        if (!validateRoomOutput(out)) throw new Error("Invalid output");
        return res.status(200).json(out);
    } catch (e: any) {
        return res.status(e.message === "Unauthorized" ? 401 : 500).json({ error: e.message || "Server error" });
    }
});

app.delete("/rooms/:id", async (req: AuthedRequest, res) => {
    try {
        const ownerId = userId(req);
        const id = parseIntParam(req.params.id);
        const row = await Room.findOne({ where: { id, ownerId } });
        if (!row) throw new Error("Not found");
        await row.destroy();
        return res.status(200).json({ ok: true });
    } catch (e: any) {
        return res.status(e.message === "Unauthorized" ? 401 : 500).json({ error: e.message || "Server error" });
    }
});

app.post("/bookings", async (req: AuthedRequest, res) => {
    try {
        const ownerId = userId(req);
        const { roomId, title, start, end, attendees } = req.body ?? {};
        if (!isPositiveInt(roomId)) throw new Error("Invalid roomId");
        if (!isNonEmptyString(title)) throw new Error("Invalid title");
        const room = await assertRoomExists(roomId);
        const { start: s, end: e } = ensureBookingBusinessRules(room, start, end, attendees);
        await assertNoOverlap(roomId, s, e);
        const created = await Booking.create({
            ownerId,
            roomId,
            title: title.trim(),
            start: s,
            end: e,
            attendees
        });
        const out = created.toJSON();
        if (!validateBookingOutput(out)) throw new Error("Invalid output");
        return res.status(200).json(out);
    } catch (e: any) {
        return res.status(e.message === "Unauthorized" ? 401 : 500).json({ error: e.message || "Server error" });
    }
});

app.get("/bookings", async (req: AuthedRequest, res) => {
    try {
        const ownerId = userId(req);
        const rows = await Booking.findAll({ where: { ownerId }, order: [["start", "ASC"]] });
        const out = rows.map((r) => r.toJSON());
        if (!out.every(validateBookingOutput)) throw new Error("Invalid output");
        return res.status(200).json(out);
    } catch (e: any) {
        return res.status(e.message === "Unauthorized" ? 401 : 500).json({ error: e.message || "Server error" });
    }
});

app.get("/bookings/:id", async (req: AuthedRequest, res) => {
    try {
        const ownerId = userId(req);
        const id = parseIntParam(req.params.id);
        const row = await Booking.findOne({ where: { id, ownerId } });
        if (!row) throw new Error("Not found");
        const out = row.toJSON();
        if (!validateBookingOutput(out)) throw new Error("Invalid output");
        return res.status(200).json(out);
    } catch (e: any) {
        return res.status(e.message === "Unauthorized" ? 401 : 500).json({ error: e.message || "Server error" });
    }
});

app.put("/bookings/:id", async (req: AuthedRequest, res) => {
    try {
        const ownerId = userId(req);
        const id = parseIntParam(req.params.id);
        const { roomId, title, start, end, attendees } = req.body ?? {};
        const row = await Booking.findOne({ where: { id, ownerId } });
        if (!row) throw new Error("Not found");
        const newRoomId = roomId !== undefined ? (isPositiveInt(roomId) ? roomId : (() => { throw new Error("Invalid roomId"); })()) : row.roomId;
        const newTitle = title !== undefined ? (isNonEmptyString(title) ? title.trim() : (() => { throw new Error("Invalid title"); })()) : row.title;
        const newAttendees = attendees !== undefined ? (isPositiveInt(attendees) ? attendees : (() => { throw new Error("Invalid attendees"); })()) : row.attendees;
        const room = await assertRoomExists(newRoomId);
        const sIso = start !== undefined ? (isISODateString(start) ? start : (() => { throw new Error("Invalid dates"); })()) : row.start.toISOString();
        const eIso = end !== undefined ? (isISODateString(end) ? end : (() => { throw new Error("Invalid dates"); })()) : row.end.toISOString();
        const { start: s, end: e } = ensureBookingBusinessRules(room, sIso, eIso, newAttendees);
        await assertNoOverlap(newRoomId, s, e, id);
        row.roomId = newRoomId;
        row.title = newTitle;
        row.attendees = newAttendees;
        row.start = s;
        row.end = e;
        await row.save();
        const out = row.toJSON();
        if (!validateBookingOutput(out)) throw new Error("Invalid output");
        return res.status(200).json(out);
    } catch (e: any) {
        return res.status(e.message === "Unauthorized" ? 401 : 500).json({ error: e.message || "Server error" });
    }
});

app.delete("/bookings/:id", async (req: AuthedRequest, res) => {
    try {
        const ownerId = userId(req);
        const id = parseIntParam(req.params.id);
        const row = await Booking.findOne({ where: { id, ownerId } });
        if (!row) throw new Error("Not found");
        await row.destroy();
        return res.status(200).json({ ok: true });
    } catch (e: any) {
        return res.status(e.message === "Unauthorized" ? 401 : 500).json({ error: e.message || "Server error" });
    }
});

const PORT = Number(process.env.PORT || 3000);
app.listen(PORT, () => {
    console.log(`Server on :${PORT}`);
});

Frontend code

// client.ts
export enum RoomType {
    Small = "Small",
    Medium = "Medium",
    Large = "Large"
}

export interface ModelInstance {
    id: number;
    ownerId: string;
    createdAt: string;
    updatedAt: string;
}

export interface Room extends ModelInstance {
    name: string;
    type: RoomType;
    capacity: number;
}

export interface Booking extends ModelInstance {
    roomId: number;
    title: string;
    start: string;
    end: string;
    attendees: number;
}

type Json = Record<string, any>;

function isNonEmptyString(x: unknown): x is string {
    return typeof x === "string" && x.trim().length > 0;
}
function isPositiveInt(x: unknown): x is number {
    return Number.isInteger(x) && (x as number) > 0;
}
function isRoomType(x: unknown): x is RoomType {
    return typeof x === "string" && (Object.values(RoomType) as string[]).includes(x);
}
function isISODateString(x: unknown): x is string {
    return typeof x === "string" && !Number.isNaN(Date.parse(x));
}
function hasTimestamps(o: any): o is { createdAt: string; updatedAt: string } {
    return isISODateString(o?.createdAt) && isISODateString(o?.updatedAt);
}

function assertRoomOutput(o: any): asserts o is Room {
    if (
        !o ||
        !isPositiveInt(o.id) ||
        !isNonEmptyString(o.ownerId) ||
        !isNonEmptyString(o.name) ||
        !isRoomType(o.type) ||
        !isPositiveInt(o.capacity) ||
        !hasTimestamps(o)
    ) {
        throw new Error("Invalid Room output");
    }
}
function assertBookingOutput(o: any): asserts o is Booking {
    if (
        !o ||
        !isPositiveInt(o.id) ||
        !isNonEmptyString(o.ownerId) ||
        !isPositiveInt(o.roomId) ||
        !isNonEmptyString(o.title) ||
        !isISODateString(o.start) ||
        !isISODateString(o.end) ||
        !isPositiveInt(o.attendees) ||
        !hasTimestamps(o)
    ) {
        throw new Error("Invalid Booking output");
    }
}

function authHeader(): HeadersInit {
    const token = typeof window !== "undefined" ? localStorage.getItem("authToken") : null;
    if (!token) throw new Error("Missing auth token");
    return { Authorization: token, "Content-Type": "application/json" };
}

async function handle<T>(res: Response, validator: (x: any) => void): Promise<T> {
    let data: any = null;
    try {
        data = await res.json();
    } catch {
        throw new Error("Invalid JSON response");
    }
    if (!res.ok) {
        const msg = isNonEmptyString(data?.error) ? data.error : `HTTP ${res.status}`;
        if (res.status === 401) throw new Error(`Unauthorized: ${msg}`);
        throw new Error(msg);
    }
    validator(data);
    return data as T;
}

const base = (path: string) => path;

export async function createRoom(data: { name: string; type: RoomType; capacity: number }): Promise<Room & ModelInstance> {
    if (!isNonEmptyString(data?.name)) throw new Error("Invalid name");
    if (!isRoomType(data?.type)) throw new Error("Invalid type");
    if (!isPositiveInt(data?.capacity)) throw new Error("Invalid capacity");
    const res = await fetch(base("/rooms"), {
        method: "POST",
        headers: authHeader(),
        body: JSON.stringify({ name: data.name.trim(), type: data.type, capacity: data.capacity })
    });
    return handle<Room>(res, assertRoomOutput);
}

export async function listRooms(): Promise<(Room & ModelInstance)[]> {
    const res = await fetch(base("/rooms"), { headers: authHeader() });
    return handle<Room[]>(res, (arr: any) => {
        if (!Array.isArray(arr)) throw new Error("Invalid output");
        arr.forEach(assertRoomOutput);
    });
}

export async function getRoom(id: number): Promise<Room & ModelInstance> {
    if (!isPositiveInt(id)) throw new Error("Invalid id");
    const res = await fetch(base(`/rooms/${id}`), { headers: authHeader() });
    return handle<Room>(res, assertRoomOutput);
}

export async function updateRoom(id: number, data: Partial<{ name: string; type: RoomType; capacity: number }>): Promise<Room & ModelInstance> {
    if (!isPositiveInt(id)) throw new Error("Invalid id");
    if (data.name !== undefined && !isNonEmptyString(data.name)) throw new Error("Invalid name");
    if (data.type !== undefined && !isRoomType(data.type)) throw new Error("Invalid type");
    if (data.capacity !== undefined && !isPositiveInt(data.capacity)) throw new Error("Invalid capacity");
    const res = await fetch(base(`/rooms/${id}`), {
        method: "PUT",
        headers: authHeader(),
        body: JSON.stringify(data)
    });
    return handle<Room>(res, assertRoomOutput);
}

export async function deleteRoom(id: number): Promise<{ ok: true }> {
    if (!isPositiveInt(id)) throw new Error("Invalid id");
    const res = await fetch(base(`/rooms/${id}`), { method: "DELETE", headers: authHeader() });
    return handle<{ ok: true }>(res, (o: any) => {
        if (o?.ok !== true) throw new Error("Invalid output");
    });
}

export async function createBooking(data: {
    roomId: number;
    title: string;
    start: string;
    end: string;
    attendees: number;
}): Promise<Booking & ModelInstance> {
    if (!isPositiveInt(data?.roomId)) throw new Error("Invalid roomId");
    if (!isNonEmptyString(data?.title)) throw new Error("Invalid title");
    if (!isISODateString(data?.start) || !isISODateString(data?.end)) throw new Error("Invalid dates");
    if (!isPositiveInt(data?.attendees)) throw new Error("Invalid attendees");
    const res = await fetch(base("/bookings"), {
        method: "POST",
        headers: authHeader(),
        body: JSON.stringify({
            roomId: data.roomId,
            title: data.title.trim(),
            start: data.start,
            end: data.end,
            attendees: data.attendees
        })
    });
    return handle<Booking>(res, assertBookingOutput);
}

export async function listBookings(): Promise<(Booking & ModelInstance)[]> {
    const res = await fetch(base("/bookings"), { headers: authHeader() });
    return handle<Booking[]>(res, (arr: any) => {
        if (!Array.isArray(arr)) throw new Error("Invalid output");
        arr.forEach(assertBookingOutput);
    });
}

export async function getBooking(id: number): Promise<Booking & ModelInstance> {
    if (!isPositiveInt(id)) throw new Error("Invalid id");
    const res = await fetch(base(`/bookings/${id}`), { headers: authHeader() });
    return handle<Booking>(res, assertBookingOutput);
}

export async function updateBooking(
    id: number,
    data: Partial<{ roomId: number; title: string; start: string; end: string; attendees: number }>
): Promise<Booking & ModelInstance> {
    if (!isPositiveInt(id)) throw new Error("Invalid id");
    if (data.roomId !== undefined && !isPositiveInt(data.roomId)) throw new Error("Invalid roomId");
    if (data.title !== undefined && !isNonEmptyString(data.title)) throw new Error("Invalid title");
    if (data.start !== undefined && !isISODateString(data.start)) throw new Error("Invalid start");
    if (data.end !== undefined && !isISODateString(data.end)) throw new Error("Invalid end");
    if (data.attendees !== undefined && !isPositiveInt(data.attendees)) throw new Error("Invalid attendees");
    const res = await fetch(base(`/bookings/${id}`), {
        method: "PUT",
        headers: authHeader(),
        body: JSON.stringify(data)
    });
    return handle<Booking>(res, assertBookingOutput);
}

export async function deleteBooking(id: number): Promise<{ ok: true }> {
    if (!isPositiveInt(id)) throw new Error("Invalid id");
    const res = await fetch(base(`/bookings/${id}`), { method: "DELETE", headers: authHeader() });
    return handle<{ ok: true }>(res, (o: any) => {
        if (o?.ok !== true) throw new Error("Invalid output");
    });
}

JS20 Code

Backend code

import { sDate, sEnum, sInteger, sString } from '@js20/schema';
import { Model, App, sMessage, MySqlDatabase, BetterAuth } from '@js20/core';
import { Op } from 'sequelize';

enum RoomType {
    Small = "Small",
    Medium = "Medium",
    Large = "Large"
}

interface Room {
    name: string;
    type: RoomType;
    capacity: number;
}

interface Booking {
    roomId: number;
    title: string;
    start: Date;
    end: Date;
    attendees: number;
}

const sRoom: Room = {
    name: sString().type(),
    type: sEnum<RoomType>(RoomType).type(),
    capacity: sInteger().type(),
}

const sBooking: Booking = {
    roomId: sInteger().type(),
    title: sString().type(),
    start: sDate().type(),
    end: sDate().type(),
    attendees: sInteger().type(),
}

interface Models {
    room: Model<Room>;
    booking: Model<Booking>;
}

const models: Models = {
    room: {
        name: 'Room',
        schema: sRoom,
    },
    booking: {
        name: 'Booking',
        schema: sBooking,
    },
};

const app = new App<Models>();

const verifyBooking = app.action({
    inputSchema: sBooking,
    outputSchema: sMessage,
    run: async (system, input) => {
        const bookings = await system.bypassAcl.models.booking.getAll({
            roomId: input.roomId,
            start: { [Op.lt]: input.end },
            end: { [Op.gt]: input.start },
        });

        const room = await system.bypassAcl.models.room.getById(input.roomId + '');

        const conflict = bookings.length > 0;
        const validDuration = (input.end.getTime() - input.start.getTime()) / (1000 * 60 * 60) >= 1;
        const validStart = input.start > new Date() && input.start < new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
        const capacity = room && input.attendees <= room.capacity;
        const valid = !conflict && validDuration && validStart && capacity;

        if (!valid) {
            throw new Error('Invalid booking');
        }

        return { message: 'Ok' };
    }
});

const database = new MySqlDatabase({
    host: process.env.SQL_HOST || '',
    port: parseInt(process.env.SQL_PORT || '5432'),
    user: process.env.SQL_USER || '',
    password: process.env.SQL_PASSWORD || '',
    database: process.env.SQL_DATABASE || '',
});

database.addModels(models);
app.addDatabase(database);
app.setAuthenticator(new BetterAuth(database));
app.addCrudEndpoints(models.room);
app.addCrudEndpoints(models.booking, { actions: {
    createBefore: verifyBooking,
    updateBefore: verifyBooking,
}});

app.start();

Frontend code

Generated automatically.

await app.generate({
    entryPath: path.resolve('./src/types.ts'),
    outputs: ['./dist/client.ts'],
    baseUrl: 'https://www.example.com',
});

Results

Counting characters:

Traditional dev:

JS20:

Code reduction: