database

Access the application's database with an ORM-like interface supporting MySQL and SQLite.

Interface

// Table access
this.database.tableName              // Access table by name
this.database.table(name, fn)        // Table access with optional callback (for migrations)
this.database.union(name)            // Union query across tables with same base type
this.database.singleton(name)        // Access singleton record (auto-created if missing)

// Operations
this.database.run(query)             // Execute raw SQL
this.database.transaction(fn)        // Execute within transaction
this.database.lock(fn)               // Execute with database lock
this.database.migrate()              // Run pending migrations
this.database.drop()                 // Drop all tables
this.database.reset()                // Refresh schema cache

// Properties
this.database.info                   // Object mapping names to types ('table', 'singleton', 'union')
this.database.withoutTenantScope     // Access database without tenant filtering

Table Methods

// Querying
table.where(conditions)              // Add WHERE conditions
table.orderBy(column, direction)     // Add ORDER BY ('asc' or 'desc')
table.paginate(page, pageSize)       // Paginate results (page defaults to 1, pageSize to 10)
table.all()                          // Get all matching records
table.first()                        // Get first matching record
table.count()                        // Count matching records

// CRUD
table.insert(fields, options)        // Insert new record
table.update(fields, options)        // Update matching records
table.delete()                       // Delete matching records

Dynamic Scopes

Tables automatically generate query scopes for each column:

table.where({ column: value })           // WHERE column = value
table.where({ columnNe: value })         // WHERE column != value
table.where({ columnGt: value })         // WHERE column > value
table.where({ columnLt: value })         // WHERE column < value
table.where({ columnGe: value })         // WHERE column >= value
table.where({ columnLe: value })         // WHERE column <= value
table.where({ columnBeginsWith: value }) // WHERE column LIKE 'value%'
table.where({ columnEndsWith: value })   // WHERE column LIKE '%value'
table.where({ columnContains: value })   // WHERE column LIKE '%value%'

// Array values create OR conditions
table.where({ status: ['draft', 'published'] })  // WHERE (status = 'draft' OR status = 'published')

Examples

Basic CRUD Operations

const { users, posts } = this.database;

// Insert
const user = await users.insert({ name: 'John', email: 'john@example.com' });

// Query
const admin = await users.where({ role: 'admin' }).first();
const allUsers = await users.all();
const count = await users.count();

// Update
await users.where({ id: user.id }).update({ name: 'Jane' });

// Delete
await users.where({ id: user.id }).delete();

Filtering and Pagination

const { posts } = this.database;

// Using dynamic scopes
const recentPosts = await posts
    .where({ status: 'published', createdAtGt: '2024-01-01' })
    .orderBy('createdAt', 'desc')
    .paginate(1, 10)
    .all();

// Chained where clauses (AND)
const filtered = await posts
    .where({ status: 'published' })
    .where({ authorIdNe: currentUserId })
    .all();

Transactions

await this.database.transaction(async () => {
    const user = await this.database.users.insert({ name: 'John', email: 'john@example.com' });
    await this.database.posts.insert({ userId: user.id, title: 'First Post' });
    // Rolls back automatically if any operation fails
});

Singleton Records

// Singleton records are auto-created on first access
const settings = await this.database.settings;
await settings.update({ siteName: 'My Site' });

Configuration

Configure in pinstripe.config.js:

export default {
    database: {
        // SQLite (default for development)
        adapter: 'sqlite',
        filename: 'development.db'

        // MySQL (recommended for production)
        // adapter: 'mysql',
        // host: 'localhost',
        // user: 'root',
        // password: '',
        // database: 'myapp_production'
    }
};

Notes

  • All insert, update, and delete operations are automatically wrapped in transactions
  • Dynamic scopes are generated at runtime based on the database schema
  • Use database.reset() after manual schema changes to refresh the cache
  • Multi-tenancy is available via the @pinstripe/multi-tenant package