npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hazo_connect

v2.7.2

Published

Module to connect to the data store (postgres via postgrest, supabase, etc)

Readme

Hazo Connect

A fully independent database abstraction layer with query builder interface for Node.js and Next.js applications. Supports multiple database backends including PostgreSQL (via PostgREST), SQLite, and Supabase.

Table of Contents


Features

  • Query Builder API: Fluent interface supporting full PostgREST syntax
  • Multiple Adapters: Support for PostgREST, Supabase, SQLite, and file storage
  • Server-Side Only: Enforced server-side usage with runtime guards and Next.js 'use server' directives
  • Zero Dependencies: Core component has no external dependencies (only Node.js built-ins)
  • Dependency Injection: Logger and configuration injected, not imported
  • Type Safe: Full TypeScript support with comprehensive type definitions
  • SQLite Admin UI: Built-in admin interface for browsing and managing SQLite databases
  • Singleton Pattern: Built-in support for connection pooling across API routes

Installation

npm install hazo_connect

Required Peer Dependencies

npm install hazo_logs@>=1.0.5

Optional Peer Dependencies

For INI config file support:

npm install hazo_config@>=1.3.0

For the SQLite admin UI:

npm install next@>=14.0.0 react@>=18.0.0 react-dom@>=18.0.0 lucide-react@^0.553.0 sonner@^2.0.7

Quick Start

1. Set Environment Variables

# .env.local
HAZO_CONNECT_TYPE=sqlite
HAZO_CONNECT_SQLITE_PATH=./database.sqlite
HAZO_CONNECT_ENABLE_ADMIN_UI=true

2. Use in API Route

import { createHazoConnectFromEnv } from 'hazo_connect/nextjs/setup'
import { QueryBuilder } from 'hazo_connect/server'
import { NextResponse } from 'next/server'

export async function GET() {
  const hazo = createHazoConnectFromEnv()
  const users = await hazo.query(new QueryBuilder().from('users'))
  return NextResponse.json({ data: users })
}

Entry Points

hazo_connect provides multiple entry points for different use cases:

| Entry Point | Purpose | Use Case | |-------------|---------|----------| | hazo_connect | Types only | Client components (type imports) | | hazo_connect/server | Server-side functionality | API routes, Server Components | | hazo_connect/nextjs | Next.js helpers | API route handlers | | hazo_connect/nextjs/setup | Setup utilities | Environment-based config, singleton pattern | | hazo_connect/ui | UI-safe types | Client component type imports |

Import Examples

// Server-side code (API routes, Server Components)
import { createHazoConnect, QueryBuilder, createCrudService } from 'hazo_connect/server'

// Next.js setup helpers
import { createHazoConnectFromEnv, getHazoConnectSingleton } from 'hazo_connect/nextjs/setup'

// API route handlers
import { createApiRouteHandler, getServerHazoConnect } from 'hazo_connect/nextjs'

// Client component types only
import type { HazoConnectConfig, TableSummary } from 'hazo_connect/ui'

Usage Examples

Basic Query Builder

import { createHazoConnect, QueryBuilder } from 'hazo_connect/server'

const hazo = createHazoConnect({
  type: 'sqlite',
  sqlite: { database_path: './database.sqlite' }
})

// Simple select
const users = await hazo.query(
  new QueryBuilder()
    .from('users')
    .select(['id', 'name', 'email'])
)

// With filters
const user = await hazo.query(
  new QueryBuilder()
    .from('users')
    .where('id', 'eq', '123')
)

// With ordering and pagination
const paginatedUsers = await hazo.query(
  new QueryBuilder()
    .from('users')
    .order('created_at', 'desc')
    .limit(10)
    .offset(20)
)

// Multiple conditions
const activeAdmins = await hazo.query(
  new QueryBuilder()
    .from('users')
    .where('status', 'eq', 'active')
    .where('role', 'eq', 'admin')
)

// Nested selects (PostgREST)
const pagesWithImages = await hazo.query(
  new QueryBuilder()
    .from('template_pages')
    .nestedSelect('images', ['id', 'filename'])
)

CRUD Operations

// CREATE - Insert a record
const newUser = await hazo.query(
  new QueryBuilder().from('users'),
  'POST',
  { name: 'John Doe', email: '[email protected]' }
)

// READ - Select records
const users = await hazo.query(
  new QueryBuilder().from('users').where('status', 'eq', 'active')
)

// UPDATE - Uses 'PATCH' method (not 'update')
const updated = await hazo.query(
  new QueryBuilder()
    .from('users')
    .where('id', 'eq', '123'),
  'PATCH',
  { name: 'Jane Doe', email: '[email protected]' }
)

// DELETE - Uses 'DELETE' method (not 'delete')
await hazo.query(
  new QueryBuilder()
    .from('users')
    .where('id', 'eq', '123'),
  'DELETE'
)

Important:

  • Updates use PATCH method, not update()
  • Deletes use DELETE method, not delete()
  • Always use .where() to specify which rows to update/delete

CRUD Service Helper

For simpler CRUD operations, use the createCrudService helper:

import { createHazoConnect, createCrudService } from 'hazo_connect/server'

const hazo = createHazoConnect({ type: 'sqlite', sqlite: { database_path: './db.sqlite' } })
const userService = createCrudService(hazo, 'users')

// List all records
const allUsers = await userService.list()

// Find by ID
const user = await userService.findById('123')

// Find by criteria
const admins = await userService.findBy({ role: 'admin' })

// Insert
const newUser = await userService.insert({ name: 'John', email: '[email protected]' })

// Update by ID - method is updateById() (not update())
await userService.updateById('123', { name: 'Jane' })

// Delete by ID - method is deleteById() (not delete())
await userService.deleteById('123')

// Custom query
const customQuery = userService.query()
  .where('status', 'eq', 'active')
  .order('created_at', 'desc')
  .limit(5)
const result = await customQuery.execute('GET')

Auto-UUID Generation

The createCrudService function automatically generates UUIDs for TEXT primary key columns when inserting records. This feature is particularly useful for SQLite databases where TEXT PRIMARY KEY columns don't auto-generate IDs.

Default Behavior:

  • Auto-ID generation is enabled by default for the 'id' column
  • Uses Node.js built-in crypto.randomUUID()
  • Preserves existing IDs when provided
  • Only generates UUIDs when ID is missing (null or undefined)

Configuration Examples:

// Default - auto-generates UUIDs for 'id' column (enabled by default)
const userService = createCrudService(hazo, 'users')

// Insert without ID - UUID will be auto-generated
await userService.insert({ name: 'John', email: '[email protected]' })
// Result: { id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', name: 'John', ... }

// Insert with ID - provided ID is preserved
await userService.insert({ id: 'custom-id', name: 'Jane' })
// Result: { id: 'custom-id', name: 'Jane' }

// Opt-out for tables with auto-increment integer IDs
const logService = createCrudService(hazo, 'logs', { autoId: false })

// Custom ID column name
const itemService = createCrudService(hazo, 'items', {
  autoId: { enabled: true, column: 'item_id' }
})

AutoIdConfig Interface:

interface AutoIdConfig {
  enabled: boolean  // Enable/disable auto-ID generation
  column?: string   // ID column name (default: 'id')
  type?: 'uuid'     // ID type (currently only UUID supported)
}

Scope Filtering (Multi-Tenant)

When building multi-tenant apps, set scope_id to automatically filter every query and inject the scope into inserts:

const itemService = createCrudService(hazo, 'items', { scope_id: 'tenant-abc' })

// All queries automatically include: WHERE scope_id = 'tenant-abc'
const items = await itemService.list()
const item = await itemService.findById('item-1')

// Inserts automatically include scope_id in the payload
await itemService.insert({ name: 'New Item' })
// Payload sent: { name: 'New Item', scope_id: 'tenant-abc' }

Utility Helpers

hazo_connect/server exports utility functions to reduce API route boilerplate:

import {
  parseJsonbField, wrapResult, ok, err,
  assertFound, assertNotEmpty, AssertionError
} from 'hazo_connect/server'
import type { DbResult } from 'hazo_connect/server'

// Parse JSONB fields returned as strings by PostgREST
const metadata = parseJsonbField<{ tags: string[] }>(row.metadata)

// Wrap async operations in a discriminated union result
const result: DbResult<User> = await wrapResult(async () => {
  const user = await userService.findById(id)
  return assertFound(user, 'user')  // throws 404 if null
})

if (result.ok) {
  return NextResponse.json(result.data)
} else {
  return NextResponse.json(result.error, { status: result.status })
}

// Assert query results are non-empty
const users = assertNotEmpty(await userService.findBy({ role: 'admin' }), 'admin')

// Build results manually
return ok(data)      // { ok: true, data }
return err('fail')   // { ok: false, error: { message: 'fail' }, status: 500 }

DbResult and parseJsonbField are also available from hazo_connect/ui for client components.

Singleton Pattern

For multiple API routes sharing a database connection:

// lib/hazo_connect.ts
import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup'

export const hazo = getHazoConnectSingleton({
  enableAdminUi: true
})
// app/api/users/route.ts
import { hazo } from '@/lib/hazo_connect'
import { QueryBuilder } from 'hazo_connect/server'
import { NextResponse } from 'next/server'

export async function GET() {
  const users = await hazo.query(new QueryBuilder().from('users'))
  return NextResponse.json({ data: users })
}

Next.js API Routes

Using createApiRouteHandler

import { createApiRouteHandler } from 'hazo_connect/nextjs'
import { QueryBuilder } from 'hazo_connect/server'
import { NextResponse } from 'next/server'

export const GET = createApiRouteHandler(
  async (hazo, request) => {
    const users = await hazo.query(
      new QueryBuilder().from('users').select('*')
    )
    return NextResponse.json({ data: users })
  },
  {
    config: {
      type: 'sqlite',
      sqlite: { database_path: './database.sqlite' }
    }
  }
)

Using getServerHazoConnect

import { getServerHazoConnect } from 'hazo_connect/nextjs'
import { QueryBuilder } from 'hazo_connect/server'
import { NextResponse } from 'next/server'

export async function GET() {
  const hazo = getServerHazoConnect({
    type: 'sqlite',
    sqlite: { database_path: './database.sqlite' }
  })
  
  const users = await hazo.query(
    new QueryBuilder().from('users').select('*')
  )
  
  return NextResponse.json({ data: users })
}

Configuration

SQLite Configuration

import { createHazoConnect } from 'hazo_connect/server'

const hazo = createHazoConnect({
  type: 'sqlite',
  enable_admin_ui: true,  // Enable admin UI (default: false)
  sqlite: {
    database_path: '/path/to/database.db',  // Optional - in-memory when omitted
    read_only: false,                        // Prevent writes when true
    initial_sql: [                           // Optional seed statements
      `CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT);`
    ],
    wasm_directory: '/path/to/sql.js/dist'  // Optional WASM override
  }
})

PostgREST Configuration

const hazo = createHazoConnect({
  type: 'postgrest',
  postgrest: {
    base_url: 'http://localhost:3000',
    api_key: 'your-api-key'
  }
})

Supabase Configuration

const hazo = createHazoConnect({
  type: 'supabase',
  supabase: {
    url: 'https://your-project.supabase.co',
    anon_key: 'your-anon-key',
    service_role_key: 'your-service-role-key'  // Optional
  }
})

pg Configuration

Use the pg adapter when you need SQL features that PostgREST does not expose, such as SELECT … FOR UPDATE SKIP LOCKED for job-queue locking or advisory locks. It connects directly to Postgres via the postgres driver (postgres.js).

const hazo = createHazoConnect({
  type: 'pg',
  pg: {
    connection_string: process.env.DATABASE_URL  // e.g. postgres://user:pass@host:5432/db
  }
})

Adapters at a glance:

| Adapter | When to use | |---------|-------------| | postgrest | Default REST-over-Postgres. Covers most CRUD needs. | | supabase | Hosted PostgREST with Supabase auth and realtime. | | sqlite | Embedded SQLite for local dev, tests, or edge deployments. | | pg | Raw Postgres via postgres driver. Use when you need SELECT … FOR UPDATE SKIP LOCKED or other SQL features PostgREST doesn't expose. Requires a direct Postgres connection string. |


Using SQLite with Next.js App Router

This walkthrough covers the bootstrap shape an embedded-SQLite app (single-operator, local-file) typically wants: DATABASE_URL-driven config, migrations applied at boot, Server Components reading directly, route handlers writing inside an atomic transaction with idempotent UPSERT.

1. Configure via DATABASE_URL

# .env.local
DATABASE_URL=sqlite:./data/site-ops.db
HAZO_CONNECT_ENABLE_ADMIN_UI=true   # optional, for the built-in admin UI

Supported URI forms: sqlite:./relative, sqlite:/absolute, sqlite:///absolute, sqlite::memory:. HAZO_CONNECT_SQLITE_PATH (if set) wins over DATABASE_URL.

2. Singleton accessor

// lib/db.ts
import 'server-only'
import { getHazoConnectSingleton } from 'hazo_connect/nextjs/setup'

export const db = getHazoConnectSingleton()

3. Apply migrations at boot

Place migration files as migrations/0001_*.sql, 0002_*.sql, … in your project root.

// lib/migrate.ts
import 'server-only'
import path from 'path'
import { runMigrations } from 'hazo_connect/server'
import { db } from './db'

let applied: Promise<void> | null = null
export function ensureMigrationsApplied() {
  if (!applied) {
    applied = runMigrations(db, {
      directory: path.join(process.cwd(), 'migrations')
    }).then(() => undefined)
  }
  return applied
}

runMigrations is idempotent — pending files are detected via a _migrations tracking table, applied inside an adapter.transaction(), and recorded with a SHA-256 checksum.

4. Read inside Server Components

// app/(dashboard)/runs/page.tsx
import { QueryBuilder } from 'hazo_connect/server'
import { db } from '@/lib/db'
import { ensureMigrationsApplied } from '@/lib/migrate'

export default async function RunsPage() {
  await ensureMigrationsApplied()
  const runs = await db.query(
    new QueryBuilder().from('daily_runs').order('run_date', 'desc').limit(20)
  )
  return <RunsTable runs={runs} />
}

All adapter methods return Promises — Server Components await them directly.

5. Write inside route handlers (idempotent UPSERT + transactions)

// app/api/v1/runs/route.ts
import { NextResponse } from 'next/server'
import { QueryBuilder, type SqliteTransactionContext } from 'hazo_connect/server'
import { SqliteAdapter } from 'hazo_connect/server'
import { db } from '@/lib/db'

export async function POST(req: Request) {
  const payload = await req.json()
  const adapter = db as SqliteAdapter

  const runId = await adapter.transaction(async (tx: SqliteTransactionContext) => {
    // Idempotent on (site_id, run_date). Re-running the same routine on the
    // same day updates the existing row.
    const [run] = await tx.query(
      new QueryBuilder()
        .from('daily_runs')
        .onConflict(['site_id', 'run_date'])
        .doUpdate(),
      'POST',
      {
        site_id: payload.site_id,
        run_date: payload.run_date,
        status: payload.status,
        steps_completed: payload.steps_completed
      }
    )

    for (const article of payload.articles ?? []) {
      await tx.query(
        new QueryBuilder().from('articles_published'),
        'POST',
        { ...article, run_id: run.run_id }
      )
    }

    for (const check of payload.health_checks ?? []) {
      await tx.query(
        new QueryBuilder()
          .from('health_checks')
          .onConflict(['site_id', 'check_date', 'check_key'])
          .doUpdate(),
        'POST',
        { ...check, run_id: run.run_id, site_id: payload.site_id }
      )
    }

    return run.run_id
  })

  return NextResponse.json({ ok: true, data: { run_id: runId } }, { status: 201 })
}

Key points:

  • adapter.transaction(fn) wraps the callback in BEGIN/COMMIT. If fn throws, ROLLBACK runs and no rows reach disk.
  • The SQLite adapter persists the in-memory DB image to disk exactly once per transaction (not once per statement) — large multi-write payloads are reasonably fast.
  • QueryBuilder.onConflict(cols).doUpdate() defaults to SET col = excluded.col for every inserted column except the conflict target. Pass an object — doUpdate({ status: 'done' }) — to update only specific columns. Pass doNothing() to keep the existing row.

6. Backups

sql.js works on an in-memory image, so a "hot backup" while a transaction is mid-flight isn't meaningful. The simplest backup pattern is a cron job:

cp data/site-ops.db data/backups/$(date +%F).db

Run it outside an in-flight transaction window. (A typed store.backup() helper is on the roadmap once we move to better-sqlite3.)

Environment Variables

| Variable | Description | Default | |----------|-------------|---------| | HAZO_CONNECT_TYPE | Database type (sqlite, postgrest, supabase, pg) | sqlite | | DATABASE_URL | SQLite-only URI form, e.g. sqlite:./data/site-ops.db, sqlite:/var/lib/x.db, sqlite:///var/lib/x.db, sqlite::memory:. Read by createHazoConnectFromEnv when HAZO_CONNECT_SQLITE_PATH is not set. | - | | HAZO_CONNECT_SQLITE_PATH | Path to SQLite database file. Ignored when the config object supplies sqlite.initial_sql (so test seeds aren't overridden by ambient env). Takes precedence over DATABASE_URL when both are set. | ./database.sqlite | | HAZO_CONNECT_SQLITE_READONLY | Enable read-only mode | false | | HAZO_CONNECT_SQLITE_WASM_DIR | Path to sql-wasm.wasm directory | Auto-detected | | HAZO_CONNECT_ENABLE_ADMIN_UI | Enable SQLite admin UI | false | | POSTGREST_URL | PostgREST base URL | - | | POSTGREST_API_KEY | PostgREST API key | - | | SUPABASE_URL | Supabase project URL | - | | SUPABASE_ANON_KEY | Supabase anonymous key (respects RLS) | - | | SUPABASE_SERVICE_ROLE_KEY | Supabase service role key (bypasses RLS) | - | | NEXT_PUBLIC_SUPABASE_URL | Alternative Supabase URL (for Next.js) | - | | NEXT_PUBLIC_SUPABASE_ANON_KEY | Alternative Supabase anon key (for Next.js) | - | | HAZO_CONNECT_LOG_LEVEL | Log level (debug, info, warn, error, none) | warn (prod), info (dev) |

Logging Configuration

Control the verbosity of hazo_connect logs. Useful for reducing noise from frequent operations (e.g., polling) or debugging issues.

Log Levels:

  • debug - Log all operations including query details (verbose)
  • info - Log general operations and successful requests (default in development)
  • warn - Log warnings and errors only (default in production)
  • error - Log errors only
  • none - Disable all logging

Configuration via environment variable:

HAZO_CONNECT_LOG_LEVEL=warn

Configuration via config object:

const hazo = createHazoConnect({
  type: 'postgrest',
  log_level: 'warn', // Only log warnings and errors
  postgrest: {
    base_url: process.env.POSTGREST_URL,
    api_key: process.env.POSTGREST_API_KEY
  }
})

Completely silent:

import { noOpLogger } from 'hazo_connect/server'

const hazo = createHazoConnect({
  type: 'postgrest',
  logger: noOpLogger, // No logs at all
  postgrest: { base_url: '...', api_key: '...' }
})

See docs/logging.md for detailed configuration options.

Debug Integration (hazo_debug)

hazo_connect emits structured query_executed and query_failed log entries at DEBUG level with timing data. You can forward these to hazo_debug's SQL tab by passing a custom logger:

import { createCrudService } from 'hazo_connect/server';
import { use_debug_query } from 'hazo_debug/client';

// In your React component or provider:
const { log_query } = use_debug_query();

const debug_logger = {
  debug: (msg: string, data?: Record<string, unknown>) => {
    if (msg === 'query_executed') log_query(data);
  },
  info: () => {},
  warn: () => {},
  error: (msg: string, data?: Record<string, unknown>) => {
    if (msg === 'query_failed') log_query(data);
  },
};

const service = createCrudService(adapter, 'hazo_users', {
  logger: debug_logger,
});

The structured log entries include:

| Field | Type | Description | |-------|------|-------------| | operation | string | SELECT, INSERT, UPDATE, or DELETE | | table | string | Table name being queried | | duration_ms | number | Query execution time in milliseconds | | rows_returned | number? | Number of rows returned (SELECT) | | rows_affected | number? | Number of rows affected (INSERT/UPDATE/DELETE) | | adapter | string? | Adapter class name (e.g., SqliteAdapter) | | error | string? | Error message (only on query_failed) |

Note: hazo_debug is an optional peer dependency. hazo_connect does NOT import it — the integration works entirely through the Logger interface.


Key Interfaces

HazoConnectConfig

Main configuration interface:

interface HazoConnectConfig {
  type: 'postgrest' | 'supabase' | 'sqlite' | 'file' | 'pg'
  logger?: Logger
  log_level?: 'debug' | 'info' | 'warn' | 'error' | 'none'
  enable_admin_ui?: boolean  // SQLite only
  postgrest?: { base_url: string; api_key: string }
  supabase?: { url: string; anon_key: string; service_role_key?: string }
  sqlite?: { database_path?: string; read_only?: boolean; initial_sql?: string[]; wasm_directory?: string }
  file?: { base_path: string; file_format: string }
}

HazoConnectAdapter

Database adapter interface:

interface HazoConnectAdapter {
  query(builder: QueryBuilder, method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', body?: any): Promise<any>
  rawQuery(endpoint: string, options?: RequestInit): Promise<any>
  getConfig(): Promise<any>
}

Logger

Optional logger interface:

interface Logger {
  debug(message: string, data?: Record<string, unknown>): void
  info(message: string, data?: Record<string, unknown>): void
  warn(message: string, data?: Record<string, unknown>): void
  error(message: string, data?: Record<string, unknown>): void
}

CrudService

CRUD service interface:

interface CrudService<T> {
  list(configure?: (qb: QueryBuilder) => QueryBuilder): Promise<T[]>
  findBy(criteria: Record<string, unknown>): Promise<T[]>
  findOneBy(criteria: Record<string, unknown>): Promise<T | null>
  findById(id: unknown): Promise<T | null>
  insert(data: Partial<T> | Partial<T>[]): Promise<T[]>
  updateById(id: unknown, patch: Partial<T>): Promise<T[]>
  deleteById(id: unknown): Promise<void>
  query(): ExecutableQueryBuilder
}

interface CrudServiceOptions {
  primaryKeys?: string[]  // defaults to ['id']
  logger?: Logger
  autoId?: AutoIdConfig | false  // Auto-generate IDs on insert (enabled by default)
  scope_id?: string  // Auto-filter all queries and inject into inserts (multi-tenant)
}

interface AutoIdConfig {
  enabled: boolean
  column?: string  // Default: 'id'
  type?: 'uuid'    // Currently only UUID supported
}

Services

SQLite Admin Service

Provides admin functionality for SQLite databases:

import { getSqliteAdminService } from 'hazo_connect/server'

const adminService = getSqliteAdminService()

// List all tables
const tables = await adminService.listTables()

// Get table schema
const schema = await adminService.getTableSchema('users')

// Get table data with pagination and filtering
const data = await adminService.getTableData('users', {
  limit: 20,
  offset: 0,
  order_by: 'created_at',
  order_direction: 'desc',
  filters: [{ column: 'status', operator: 'eq', value: 'active' }]
})

// Insert row
await adminService.insertRow('users', { name: 'John', email: '[email protected]' })

// Update rows
await adminService.updateRows('users', { id: 1 }, { name: 'Jane' })

// Delete rows
await adminService.deleteRows('users', { id: 1 })

SQLite Admin UI

A built-in web interface for browsing and managing SQLite databases.

Enable Admin UI

const hazo = createHazoConnect({
  type: 'sqlite',
  enable_admin_ui: true,
  sqlite: { database_path: './database.sqlite' }
})

Or via environment variable:

HAZO_CONNECT_ENABLE_ADMIN_UI=true

Access Routes

  • UI: /hazo_connect/sqlite_admin
  • API: /hazo_connect/api/sqlite/*

Tailwind v4 Setup (Required)

If you're using Tailwind v4, the Admin UI component requires an additional setup step to ensure Tailwind compiles the component's utility classes.

Add the following to your globals.css or main CSS file after the @import "tailwindcss" statement:

@import "tailwindcss";

/* Required: Enable Tailwind to scan hazo_connect's classes */
@source "../node_modules/hazo_connect/dist";

Why is this needed? Tailwind v4 uses JIT compilation and only generates CSS for classes it finds in scanned files. By default, it only scans your project files, not node_modules/. The @source directive tells Tailwind to also scan the hazo_connect package for utility classes.

Without this directive: The Admin UI will have missing styles (transparent backgrounds, incorrect spacing, missing hover states, etc.).

Note: This is only required if you're using the SQLite Admin UI component. The core database functionality does not require this setup.

API Endpoints

| Endpoint | Method | Description | |----------|--------|-------------| | /hazo_connect/api/sqlite/tables | GET | List all tables | | /hazo_connect/api/sqlite/schema?table=users | GET | Get table schema | | /hazo_connect/api/sqlite/data?table=users&limit=20 | GET | Get table data | | /hazo_connect/api/sqlite/data | POST | Insert row | | /hazo_connect/api/sqlite/data | PATCH | Update rows | | /hazo_connect/api/sqlite/data | DELETE | Delete rows |


Supported Operators

| Operator | Description | Example | |----------|-------------|---------| | eq | Equals | .where('status', 'eq', 'active') | | neq | Not equals | .where('status', 'neq', 'deleted') | | gt | Greater than | .where('age', 'gt', 18) | | gte | Greater than or equal | .where('age', 'gte', 18) | | lt | Less than | .where('age', 'lt', 65) | | lte | Less than or equal | .where('age', 'lte', 65) | | like | Like (case-sensitive) | .where('name', 'like', '%John%') | | ilike | Like (case-insensitive) | .where('name', 'ilike', '%john%') | | in | In array | .whereIn('status', ['active', 'pending']) | | is | Is null/not null | .where('deleted_at', 'is', null) | | or | Or condition | .whereOr([...conditions]) |


Server-Side Enforcement

hazo_connect is designed for server-side use only. The library uses:

  • Next.js 'use server' directives
  • Runtime guards that check for browser environment
  • Separate entry points to prevent accidental client-side usage

If you try to use server-side code in a client component, you'll get a clear error message:

Error: hazo_connect/server can only be used on the server. 
For client-side usage, import types from "hazo_connect" or "hazo_connect/ui".

Documentation


License

MIT © Pubs Abayasiri