@promakeai/dbreact
v1.0.6
Published
React client for schema-driven multi-language database
Downloads
412
Maintainers
Readme
@promakeai/dbreact
React hooks and providers for schema-driven, multi-language databases. Type-safe, with automatic translation support.
Features
- Type-Safe Hooks - Generic React hooks with generated TypeScript types
- Multi-Language Support - Automatic translation queries with fallback
- Browser SQLite - sql.js WASM adapter for offline-first apps
- React Query Integration - Built-in caching, loading states, optimistic updates
- Zero-Config Language Switching - Change language, queries refetch automatically
- MongoDB-Style Queries - Intuitive filter syntax (
$gt,$in,$like,$contains, etc.)
Installation
npm install @promakeai/dbreact @tanstack/react-queryPeer Dependencies:
react>= 18.0.0@tanstack/react-query>= 5.0.0
Quick Start
1. Setup Adapter
import { SqliteAdapter } from '@promakeai/dbreact';
import { parseJSONSchema } from '@promakeai/dbreact';
import schemaJson from './schema.json';
const schema = parseJSONSchema(schemaJson as any);
const adapter = new SqliteAdapter({
storageKey: 'myapp-db', // localStorage key for persistence
schema,
});2. Wrap App with Provider
import { DbProvider } from '@promakeai/dbreact';
function App() {
const [lang, setLang] = useState('en');
return (
<DbProvider
adapter={adapter}
lang={lang}
fallbackLang="en"
autoConnect={true}
>
<MyApp />
</DbProvider>
);
}3. Use Hooks
import { useDbList, useDbGet, useDbCreate } from '@promakeai/dbreact';
function ProductList() {
// List with filters
const { data: products, isLoading } = useDbList('products', {
where: { stock: { $gt: 0 } },
orderBy: [{ field: 'name', direction: 'ASC' }],
limit: 20,
});
// Create mutation
const createProduct = useDbCreate('products');
const handleCreate = () => {
createProduct.mutate({
sku: 'NEW-001',
price: 99.99,
});
};
if (isLoading) return <div>Loading...</div>;
return (
<div>
{products?.map(p => <ProductCard key={p.id} product={p} />)}
<button onClick={handleCreate}>Add Product</button>
</div>
);
}API Reference
DbProvider
Main provider component that wraps your application.
<DbProvider
adapter={adapter}
schema={schema}
lang="tr"
fallbackLang="en"
autoConnect={true}
>
<App />
</DbProvider>Props:
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| adapter | IDataAdapter | Yes | - | Database adapter instance |
| schema | SchemaDefinition | No | - | Enables populate + typed serialization |
| lang | string | No | 'en' | Current language code |
| fallbackLang | string | No | 'en' | Fallback language |
| autoConnect | boolean | No | true | Auto-connect on mount |
| queryClient | QueryClient | No | Internal | Provide a custom React Query client |
| children | ReactNode | Yes | - | Child components |
Query Hooks
useDbList
Fetch multiple records with filtering, pagination, and sorting.
const { data, isLoading, error, refetch } = useDbList<Product>('products', {
where: { stock: { $gt: 0 } },
orderBy: [{ field: 'price', direction: 'DESC' }],
limit: 20,
offset: 0,
enabled: true,
});Options:
| Option | Type | Description |
|--------|------|-------------|
| where | object | MongoDB-style filter conditions |
| orderBy | array | Sort order [{ field, direction }] |
| limit | number | Max records to return |
| offset | number | Skip records |
| populate | PopulateOption | Resolve foreign key references (string, array, or object) |
| enabled | boolean | Enable/disable query |
useDbGet
Fetch single record by ID.
const { data: product, isLoading } = useDbGet<Product>('products', productId, {
populate: ['categoryId'],
enabled: !!productId,
});Find-one style (by where clause):
const { data: product } = useDbGet<Product>('products', {
where: { slug: 'my-product' },
enabled: !!slug,
populate: { categoryId: true },
});Mutation Hooks
useDbCreate
Create a new record.
const createProduct = useDbCreate<Product>('products');
createProduct.mutate(
{ sku: 'NEW-001', price: 99.99 },
{
onSuccess: (data) => console.log('Created:', data),
onError: (error) => console.error(error),
}
);useDbUpdate
Update an existing record.
const updateProduct = useDbUpdate<Product>('products');
updateProduct.mutate(
{ id: productId, data: { price: 89.99 } },
{
onSuccess: () => console.log('Updated'),
}
);useDbDelete
Delete a record.
const deleteProduct = useDbDelete('products');
deleteProduct.mutate(productId, {
onSuccess: () => console.log('Deleted'),
});Context Hooks
useDb
Access database connection status.
const { adapter, isConnected, error } = useDb();
if (!isConnected) return <div>Connecting...</div>;useAdapter
Access adapter directly for raw queries.
const adapter = useAdapter();
const handleRawQuery = async () => {
const results = await adapter.raw('SELECT * FROM products WHERE price > ?', [100]);
console.log(results);
};useDbLang
Access and control language settings.
const { lang, fallbackLang, setLang } = useDbLang();
// Language switcher
<select value={lang} onChange={e => setLang(e.target.value)}>
<option value="en">English</option>
<option value="tr">Turkce</option>
<option value="de">Deutsch</option>
</select>SqliteAdapter
Browser-based SQLite storage using sql.js (WebAssembly).
const adapter = new SqliteAdapter({
storageKey: 'myapp-db', // localStorage key for persistence
schema: mySchema, // Optional: schema definition
initialData: seedData, // Optional: initial seed data
});Features:
- Automatic persistence to localStorage
- Full SQL support
- Works offline
- WASM-based (no native dependencies)
Limitations:
- Data stored in browser only
- localStorage limit: ~5-10MB (browser dependent)
- Best for < 10,000 records
Query Options
MongoDB-Style Filters
// Comparison
{ price: { $gt: 100 } } // > 100
{ price: { $gte: 100 } } // >= 100
{ price: { $lt: 100 } } // < 100
{ price: { $lte: 100 } } // <= 100
{ price: { $ne: 100 } } // != 100
{ price: { $eq: 100 } } // = 100
// Array
{ id: { $in: [1, 2, 3] } } // IN (1, 2, 3)
{ id: { $nin: [1, 2, 3] } } // NOT IN (1, 2, 3)
// String
{ name: { $like: '%shirt%' } } // LIKE '%shirt%'
{ name: { $notLike: '%test%' } } // NOT LIKE '%test%'
// Range
{ price: { $between: [10, 100] } } // BETWEEN 10 AND 100
// Null
{ description: { $isNull: true } } // IS NULL
{ description: { $isNull: false } } // IS NOT NULL
// JSON array contains
{ tags: { $contains: "sale" } }
{ tags: { $containsAny: ["sale", "new"] } }
// Logical
{ $and: [
{ price: { $gt: 50 } },
{ stock: { $gt: 0 } }
]}
{ $or: [
{ category: 'shirts' },
{ category: 'pants' }
]}
{ $not: { price: { $lt: 100 } } } // NOT (price < 100)Query Interface
interface QueryOptions {
where?: Record<string, unknown>;
orderBy?: Array<{
field: string;
direction: 'ASC' | 'DESC';
}>;
limit?: number;
offset?: number;
populate?: PopulateOption;
lang?: string;
fallbackLang?: string;
}Multi-Language Support
Schema with Translatable Fields
import { defineSchema, f } from '@promakeai/orm';
const schema = defineSchema({
languages: ['en', 'tr', 'de'],
tables: {
products: {
id: f.id(),
price: f.decimal(),
name: f.string().translatable(), // In translation table
description: f.text().translatable(), // In translation table
},
},
});Automatic Translation
Queries automatically use the current language from DbProvider:
function ProductList() {
// Automatically fetches in current language
const { data: products } = useDbList('products');
// products[0].name is in Turkish if lang='tr'
// Falls back to English if Turkish translation missing
}Language Switching
When you change language, React Query automatically refetches all queries:
function LanguageSwitcher() {
const { lang, setLang } = useDbLang();
return (
<select value={lang} onChange={e => setLang(e.target.value)}>
<option value="en">English</option>
<option value="tr">Turkce</option>
</select>
);
}Generated Types + Generic Hooks
Generate runtime schema and TypeScript interfaces:
dbcli generate --schema ./schema.json --output ./src/dbdbcli generate writes schema.json and types.ts. React hooks are imported from @promakeai/dbreact:
import { useDbList, useDbGet, useDbCreate } from '@promakeai/dbreact';
import type { DbProduct, DbProductInput } from './db/types';
function ProductManager() {
const { data: products } = useDbList<DbProduct>('products', {
where: { stock: { $gt: 0 } },
});
const { data: product } = useDbGet<DbProduct>('products', 1);
const createProduct = useDbCreate<DbProduct, DbProductInput>('products');
return null;
}Advanced Usage
Custom React Query Options
const { data } = useDbList('products', {
where: { active: true },
limit: 50,
});Direct Adapter Access
For complex queries not covered by hooks:
function AdvancedSearch() {
const adapter = useAdapter();
const [results, setResults] = useState([]);
const handleSearch = async (query: string) => {
const products = await adapter.list('products', {
where: {
$or: [
{ name: { $like: `%${query}%` } },
{ description: { $like: `%${query}%` } },
],
},
limit: 50,
});
setResults(products);
};
return <SearchInput onSearch={handleSearch} />;
}Optimistic Updates
function ProductPrice({ product }) {
const queryClient = useQueryClient();
const updateProduct = useDbUpdate('products');
const handlePriceChange = async (newPrice: number) => {
// Optimistic update
queryClient.setQueryData(
['products', 'detail', product.id],
{ ...product, price: newPrice }
);
await updateProduct.mutateAsync({
id: product.id,
data: { price: newPrice },
});
};
return <PriceInput value={product.price} onChange={handlePriceChange} />;
}Examples
E-Commerce Product List
function Shop() {
const { lang } = useDbLang();
const [category, setCategory] = useState(null);
const { data: products, isLoading } = useDbList('products', {
where: category ? { categoryId: category } : {},
orderBy: [{ field: 'name', direction: 'ASC' }],
});
if (isLoading) return <Spinner />;
return (
<div>
<CategoryFilter onSelect={setCategory} />
<ProductGrid products={products} />
</div>
);
}Multi-Language Blog
function BlogPost({ slug }: { slug: string }) {
const { data: posts } = useDbList('posts', {
where: { slug, published: true },
limit: 1,
});
const post = posts?.[0];
if (!post) return <NotFound />;
return (
<article>
<h1>{post.title}</h1> {/* Auto-translated */}
<div>{post.content}</div> {/* Auto-translated */}
</article>
);
}Real-Time Search
function ProductSearch() {
const adapter = useAdapter();
const [query, setQuery] = useState('');
const { data: results } = useQuery({
queryKey: ['search', query],
queryFn: () => adapter.list('products', {
where: {
$or: [
{ name: { $like: `%${query}%` } },
{ sku: { $like: `%${query}%` } },
],
},
limit: 10,
}),
enabled: query.length > 2,
});
return (
<div>
<input value={query} onChange={e => setQuery(e.target.value)} />
<SearchResults results={results} />
</div>
);
}TypeScript Support
Full TypeScript support with generated types:
import type { DbProduct, DbProductInput } from './db/types';
// Type-safe queries
const products: DbProduct[] = await adapter.list('products');
// Type-safe creates
const newProduct: DbProductInput = {
sku: 'SHIRT-001',
price: 99.99,
};
await adapter.create('products', newProduct);Performance Tips
Use pagination for large datasets:
const { data } = useDbList('products', { limit: 20, offset: page * 20 });Enable query conditionally:
const { data } = useDbGet('products', id, { enabled: !!id });Prefetch next page:
queryClient.prefetchQuery({ queryKey: ['products', 'list', { offset: (page + 1) * 20 }], queryFn: () => adapter.list('products', { offset: (page + 1) * 20 }), });
Troubleshooting
Production crash: jsxDEV is not a function
- Run
bun run test:build-runtimebefore publishing. - This verifies
dist/index.jsdoes not includereact/jsx-dev-runtime.
"useDbLang must be used within a DbProvider"
- Ensure component is wrapped in DbProvider
Queries return empty results
- Check adapter is connected:
const { isConnected } = useDb() - Verify data exists in database
- Check query filters are correct
Translations not working
- Ensure schema has
.translatable()fields - Run
dbcli generateto create translation tables - Check translation records exist in
{table}_translations
Related Packages
- @promakeai/orm - Core ORM with schema DSL
- @promakeai/dbcli - CLI tool for database operations
License
MIT
