Skip to content

๐Ÿ’ƒ A type-safe, secure SQLite query builder with D1/Turso support with built-in migrations and security features.

Notifications You must be signed in to change notification settings

Uiedbook/SQLiteBruv

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

54 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

SQLiteBruv Query Builder

A type-safe, secure SQLite query builder with D1/Turso support with built-in migrations and security features.

npm version License: MIT npm TypeScript

Features

  • ๐Ÿ›ก๏ธ Security-first design with SQL injection prevention
  • ๐Ÿ“ก JSON interface for http no sql queries
  • ๐Ÿ”„ Automatic schema migrations
  • ๐Ÿƒโ€โ™‚๏ธ In-memory caching
  • ๐ŸŒ Cloudflare D1 & Turso support
  • ๐Ÿ“ Type-safe queries
  • ๐Ÿ” Query validation & sanitization
  • ๐Ÿ“Š Schema management
  • ๐ŸŒ  Bunjs Support 100%

## Installation
npm install sqlite-bruv

๐Ÿš€ Updates

  • Light weight: Zero dependency and small size.
  • Bun-Ready: built for Bunjs
  • Platform Support:
    • Cloudflare D1
    • Turso
    • Local SQLite
    • raw query output
  • Security: SQL injection prevention, query validation, parameter sanitization
  • Type Safety: Full TypeScript support with inferred types
  • Migrations: Automatic schema diff detection and migration generation
  • Caching: Built-in memory caching with invalidation
  • Relations: Support for one-to-one and one-to-many relationships

๐Ÿ“ฆ Installation

# bun
bun add sqlite-bruv

# npm
npm install sqlite-bruv

Usage/Examples

import { SqliteBruv, Schema } from "sqlite-bruv";

// Define your schema
const UserSchema = new Schema<{
  name: string;
  email: string;
  role: "admin" | "user";
  createdAt: Date;
}>({
  name: "users",
  columns: {
    name: { type: "TEXT", required: true },
    email: { type: "TEXT", unique: true },
    role: { type: "TEXT", default: () => "user" },
    createdAt: { type: "DATETIME", default: () => new Date() },
  },
});

const PostSchema = new Schema({
  name: "posts",
  columns: {
    title: { type: "TEXT", required: true },
    content: { type: "TEXT" },
    userId: {
      type: "TEXT",
      target: "users",
      relationType: "ONE",
    },
  },
});

const CommentSchema = new Schema({
  name: "comments",
  columns: {
    content: { type: "TEXT", required: true },
    postId: {
      type: "TEXT",
      target: "posts",
      relationType: "MANY",
    },
  },
});

// Initialize database

const db = new SqliteBruv({
  schema: [UserSchema],
});

Platform-Specific Setup Cloudflare D1

const db = new SqliteBruv({
  D1: {
    accountId: process.env.CF_ACCOUNT_ID,
    databaseId: process.env.D1_DATABASE_ID,
    apiKey: process.env.CF_API_KEY,
  },
  schema: [UserSchema, PostSchema, CommentSchema],
});

Turso;

const db = new SqliteBruv({
  turso: {
    url: process.env.TURSO_URL,
    authToken: process.env.TURSO_AUTH_TOKEN,
  },
  schema: [UserSchema, PostSchema, CommentSchema],
});

Example usage:

const queryBuilder = new SqliteBruv({
  schema: [UserSchema, PostSchema, CommentSchema],
});

// Insert
await queryBuilder
  .from("users")
  .insert({ name: "John Doe", email: "[email protected]" })
  .then((changes) => {
    // console.log({ changes });
  });

// Update
await queryBuilder
  .from("users")
  .where("id = ?", 1)
  .update({ name: "Jane Doe" })
  .then((changes) => {
    // console.log({ changes });
  });

// Search
await queryBuilder
  .from("users")
  .where("id = ?", 1)
  .andWhere("name LIKE ?", `%oh%`)
  .get()
  .then((changes) => {
    // console.log({ changes });
  });

// Delete
await queryBuilder
  .from("users")
  .where("id = ?", 1)
  .delete()
  .then((changes) => {
    console.log({ changes });
  });

// Get all users
queryBuilder
  .from("users")
  .get()
  .then((changes) => {
    // console.log({ changes });
  });

// Get one user
await queryBuilder
  .from("users")
  .where("id = ?", 1)
  .getOne()
  .then((changes) => {
    // console.log({ changes });
  });

// Select specific columns
await queryBuilder
  .from("users")
  .select("id", "name")
  .get()
  .then((changes) => {
    // console.log({ changes });
  });

// Where conditions
await queryBuilder
  .from("users")
  .where("age > ?", 18)
  .get()
  .then((changes) => {
    // console.log({ changes });
  });

// AndWhere conditions
await queryBuilder
  .from("users")
  .where("age > ?", 18)
  .andWhere("country = ?", "USA")
  .get()
  .then((changes) => {
    // console.log({ changes });
  });

// OrWhere conditions
await queryBuilder
  .from("users")
  .where("age > ?", 18)
  .orWhere("country = ?", "Canada")
  .get()
  .then((changes) => {
    // console.log({ changes });
  });

// Limit and Offset
await queryBuilder
  .from("users")
  .limit(10)
  .offset(5)
  .get()
  .then((changes) => {
    // console.log({ changes });
  });

// OrderBy
await queryBuilder
  .from("users")
  .orderBy("name", "ASC")
  .get()
  .then((changes) => {
    // console.log({ changes });
  });

await queryBuilder
  .from("users")
  .orderBy("name", "ASC")
  .get()
  .then((changes) => {
    // console.log({ changes });
  });

๐Ÿ’ก Advanced Usage

Complex Queries

// Relations and joins
const posts = await db
  .from("posts")
  .select("posts.*", "users.name as author")
  .where("posts.published = ?", true)
  .andWhere("posts.views > ?", 1000)
  .orderBy("posts.createdAt", "DESC")
  .limit(10)
  .get();

// Transactions
await db.transaction(async (trx) => {
  await trx.from("users").insert({ name: "John" });
  await trx.from("profiles").insert({ userId: 1 });
});

// Raw queries with safety
await db.raw("SELECT * FROM users WHERE id = ?", [userId]);

// Cache usage
const users = await db
  .from("users")
  .select("*")
  .where("active = ?", true)
  .cacheAs("active-users")
  .get();

// Cache invalidation
db.invalidateCache("active-users");

Using from over the network via JSON interface

//  JSON interface structure
interface Query {
  from: string;
  select?: string[];
  where?: {
    condition: string;
    params: any[];
  }[];
  andWhere?: {
    condition: string;
    params: any[];
  }[];
  orWhere?: {
    condition: string;
    params: any[];
  }[];
  orderBy?: {
    column: string;
    direction: "ASC" | "DESC";
  };
  limit?: number;
  offset?: number;
  cacheAs?: string;
  invalidateCache?: string;
  action?: "get" | "getOne" | "insert" | "update" | "delete" | "count";
  /**
  ### For insert and update only
  */
  data?: any;
}
// Example usage in an Express.js route
import express from "express";
const app = express();
app.use(express.json());

app.post("/execute-query", async (req, res) => {
  try {
    const queryInput = req.body;
    // do your role authentication here,
    // use query.from to know the table being accessed
    const result = await qb.executeJsonQuery(queryInput);
    res.json(result);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

๐Ÿ”„ Migrations

Migrations are automatically generated when schema changes are detected:

-- Generated in ./Bruv-migrations/timestamp_add_user_role.sql:
-- Up
ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'user';

-- Down
ALTER TABLE users DROP COLUMN role;

Setting up your schema

This if your DB is new and your are not using any orm, just call toString and query your db with the queryBuilder.raw() method.

Note: raw is not secured, it can be used to apply migrations too. be careful what you do with queryBuilder.raw().

console.log(user.toString());
const raw = await qb.raw(user.toString());
console.log({ raw });

๐Ÿ›ก๏ธ Security Features

The query builder implements several security measures to prevent SQL injection and malicious queries:

  • Parameter validation (max 100 params)
  • SQL injection prevention
  • Query timeout limits
  • Rate limiting
  • String length validation
  • Dangerous pattern detection
  • Allowed parameter types: string, number, boolean, null

Condition Validation

  • Whitelisted operators: =, >, <, >=, <=, LIKE, IN, BETWEEN, IS NULL, IS NOT NULL
  • Blocked dangerous patterns: ; DROP, DELETE, UPDATE, INSERT, ALTER, EXEC, UNION
  • Parameterized queries enforced

Security Examples

// โœ… Safe queries
db.from("users")
  .where("email LIKE ?", "%@example.com") // โœ… Safe
  .andWhere("role = ?", "admin") // โœ… Safe
  .get();
db.from("users")
  .where("age > ?", 18)
  .andWhere("status = ?", "active")
  .orWhere("role IN (?)", ["admin", "mod"]);

// โŒ These will throw security errors:
db.where("1=1; DROP TABLE users;"); // Dangerous pattern
db.where("col = (SELECT ...)"); // Complex subqueries blocked
db.where("name = ?", "a".repeat(1001)); // String too long
db.where("email = " + userInput);

๐ŸŽฎ Platform-Specific Features

Cloudflare D1

  • Automatic edge deployment
  • D1 API integration
  • Built-in caching Turso
  • HTTP API support
  • Connection pooling ๐Ÿ“Š Performance
  • Prepared statements
  • Connection pooling
  • Query caching ๐Ÿค Contributing
  1. Fork the repository
  2. Create feature branch (git checkout -b feature/amazing)
  3. Commit changes (git commit -am 'Add amazing feature')
  4. Push branch (git push origin feature/amazing)
  5. Open a Pull Request

๐Ÿ“ License

MIT License - see LICENSE file

๐Ÿ†˜ Support

Contributions are always welcome! creates issues and pull requests. Documentation GitHub Issues Discord Community

About

๐Ÿ’ƒ A type-safe, secure SQLite query builder with D1/Turso support with built-in migrations and security features.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published