@promakeai/dbreact 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,633 @@
1
+ # @promakeai/dbreact
2
+
3
+ React hooks and providers for schema-driven, multi-language databases. Type-safe, with automatic translation support.
4
+
5
+ ## Features
6
+
7
+ - **Type-Safe Hooks** - Generated TypeScript types and React hooks
8
+ - **Multi-Language Support** - Automatic translation queries with fallback
9
+ - **Browser SQLite** - sql.js WASM adapter for offline-first apps
10
+ - **React Query Integration** - Built-in caching, loading states, optimistic updates
11
+ - **Zero-Config Language Switching** - Change language, queries refetch automatically
12
+ - **MongoDB-Style Queries** - Intuitive filter syntax (`$gt`, `$in`, `$like`, etc.)
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @promakeai/dbreact @tanstack/react-query
18
+ ```
19
+
20
+ **Peer Dependencies:**
21
+ - `react` >= 18.0.0
22
+ - `@tanstack/react-query` >= 5.0.0
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Setup Adapter
27
+
28
+ ```tsx
29
+ import { SqliteAdapter } from '@promakeai/dbreact';
30
+
31
+ const adapter = new SqliteAdapter({
32
+ storageKey: 'myapp-db', // localStorage key for persistence
33
+ });
34
+ ```
35
+
36
+ ### 2. Wrap App with Provider
37
+
38
+ ```tsx
39
+ import { DbProvider } from '@promakeai/dbreact';
40
+
41
+ function App() {
42
+ const [lang, setLang] = useState('en');
43
+
44
+ return (
45
+ <DbProvider
46
+ adapter={adapter}
47
+ lang={lang}
48
+ fallbackLang="en"
49
+ autoConnect={true}
50
+ >
51
+ <MyApp />
52
+ </DbProvider>
53
+ );
54
+ }
55
+ ```
56
+
57
+ ### 3. Use Hooks
58
+
59
+ ```tsx
60
+ import { useDbList, useDbGet, useDbCreate } from '@promakeai/dbreact';
61
+
62
+ function ProductList() {
63
+ // List with filters
64
+ const { data: products, isLoading } = useDbList('products', {
65
+ where: { stock: { $gt: 0 } },
66
+ orderBy: [{ field: 'name', direction: 'ASC' }],
67
+ limit: 20,
68
+ });
69
+
70
+ // Create mutation
71
+ const createProduct = useDbCreate('products');
72
+
73
+ const handleCreate = () => {
74
+ createProduct.mutate({
75
+ sku: 'NEW-001',
76
+ price: 99.99,
77
+ });
78
+ };
79
+
80
+ if (isLoading) return <div>Loading...</div>;
81
+
82
+ return (
83
+ <div>
84
+ {products?.map(p => <ProductCard key={p.id} product={p} />)}
85
+ <button onClick={handleCreate}>Add Product</button>
86
+ </div>
87
+ );
88
+ }
89
+ ```
90
+
91
+ ---
92
+
93
+ ## API Reference
94
+
95
+ ### DbProvider
96
+
97
+ Main provider component that wraps your application.
98
+
99
+ ```tsx
100
+ <DbProvider
101
+ adapter={adapter}
102
+ lang="tr"
103
+ fallbackLang="en"
104
+ autoConnect={true}
105
+ >
106
+ <App />
107
+ </DbProvider>
108
+ ```
109
+
110
+ **Props:**
111
+
112
+ | Prop | Type | Required | Default | Description |
113
+ |------|------|----------|---------|-------------|
114
+ | `adapter` | `IDataAdapter` | Yes | - | Database adapter instance |
115
+ | `lang` | `string` | No | `'en'` | Current language code |
116
+ | `fallbackLang` | `string` | No | `'en'` | Fallback language |
117
+ | `autoConnect` | `boolean` | No | `true` | Auto-connect on mount |
118
+ | `children` | `ReactNode` | Yes | - | Child components |
119
+
120
+ ---
121
+
122
+ ### Query Hooks
123
+
124
+ #### useDbList
125
+
126
+ Fetch multiple records with filtering, pagination, and sorting.
127
+
128
+ ```tsx
129
+ const { data, isLoading, error, refetch } = useDbList<Product>('products', {
130
+ where: { stock: { $gt: 0 } },
131
+ orderBy: [{ field: 'price', direction: 'DESC' }],
132
+ limit: 20,
133
+ offset: 0,
134
+ enabled: true,
135
+ });
136
+ ```
137
+
138
+ **Options:**
139
+
140
+ | Option | Type | Description |
141
+ |--------|------|-------------|
142
+ | `where` | `object` | MongoDB-style filter conditions |
143
+ | `orderBy` | `array` | Sort order `[{ field, direction }]` |
144
+ | `limit` | `number` | Max records to return |
145
+ | `offset` | `number` | Skip records |
146
+ | `populate` | `string[]` | Resolve foreign key references |
147
+ | `enabled` | `boolean` | Enable/disable query |
148
+
149
+ #### useDbGet
150
+
151
+ Fetch single record by ID.
152
+
153
+ ```tsx
154
+ const { data: product, isLoading } = useDbGet<Product>('products', productId, {
155
+ populate: ['categoryId'],
156
+ enabled: !!productId,
157
+ });
158
+ ```
159
+
160
+ ---
161
+
162
+ ### Mutation Hooks
163
+
164
+ #### useDbCreate
165
+
166
+ Create a new record.
167
+
168
+ ```tsx
169
+ const createProduct = useDbCreate<Product>('products');
170
+
171
+ createProduct.mutate(
172
+ { sku: 'NEW-001', price: 99.99 },
173
+ {
174
+ onSuccess: (data) => console.log('Created:', data),
175
+ onError: (error) => console.error(error),
176
+ }
177
+ );
178
+ ```
179
+
180
+ #### useDbUpdate
181
+
182
+ Update an existing record.
183
+
184
+ ```tsx
185
+ const updateProduct = useDbUpdate<Product>('products', productId);
186
+
187
+ updateProduct.mutate(
188
+ { price: 89.99 },
189
+ {
190
+ onSuccess: () => console.log('Updated'),
191
+ }
192
+ );
193
+ ```
194
+
195
+ #### useDbDelete
196
+
197
+ Delete a record.
198
+
199
+ ```tsx
200
+ const deleteProduct = useDbDelete('products', productId);
201
+
202
+ deleteProduct.mutate(undefined, {
203
+ onSuccess: () => console.log('Deleted'),
204
+ });
205
+ ```
206
+
207
+ ---
208
+
209
+ ### Context Hooks
210
+
211
+ #### useDb
212
+
213
+ Access database connection status.
214
+
215
+ ```tsx
216
+ const { adapter, isConnected, error } = useDb();
217
+
218
+ if (!isConnected) return <div>Connecting...</div>;
219
+ ```
220
+
221
+ #### useAdapter
222
+
223
+ Access adapter directly for raw queries.
224
+
225
+ ```tsx
226
+ const adapter = useAdapter();
227
+
228
+ const handleRawQuery = async () => {
229
+ const results = await adapter.raw('SELECT * FROM products WHERE price > ?', [100]);
230
+ console.log(results);
231
+ };
232
+ ```
233
+
234
+ #### useDbLang
235
+
236
+ Access and control language settings.
237
+
238
+ ```tsx
239
+ const { lang, fallbackLang, setLang } = useDbLang();
240
+
241
+ // Language switcher
242
+ <select value={lang} onChange={e => setLang(e.target.value)}>
243
+ <option value="en">English</option>
244
+ <option value="tr">Turkce</option>
245
+ <option value="de">Deutsch</option>
246
+ </select>
247
+ ```
248
+
249
+ ---
250
+
251
+ ### SqliteAdapter
252
+
253
+ Browser-based SQLite storage using sql.js (WebAssembly).
254
+
255
+ ```tsx
256
+ const adapter = new SqliteAdapter({
257
+ storageKey: 'myapp-db', // localStorage key for persistence
258
+ schema: mySchema, // Optional: schema definition
259
+ initialData: seedData, // Optional: initial seed data
260
+ });
261
+ ```
262
+
263
+ **Features:**
264
+ - Automatic persistence to localStorage
265
+ - Full SQL support
266
+ - Works offline
267
+ - WASM-based (no native dependencies)
268
+
269
+ **Limitations:**
270
+ - Data stored in browser only
271
+ - localStorage limit: ~5-10MB (browser dependent)
272
+ - Best for < 10,000 records
273
+
274
+ ---
275
+
276
+ ## Query Options
277
+
278
+ ### MongoDB-Style Filters
279
+
280
+ ```typescript
281
+ // Comparison
282
+ { price: { $gt: 100 } } // > 100
283
+ { price: { $gte: 100 } } // >= 100
284
+ { price: { $lt: 100 } } // < 100
285
+ { price: { $lte: 100 } } // <= 100
286
+ { price: { $ne: 100 } } // != 100
287
+ { price: { $eq: 100 } } // = 100
288
+
289
+ // Array
290
+ { id: { $in: [1, 2, 3] } } // IN (1, 2, 3)
291
+ { id: { $nin: [1, 2, 3] } } // NOT IN (1, 2, 3)
292
+
293
+ // String
294
+ { name: { $like: '%shirt%' } } // LIKE '%shirt%'
295
+ { name: { $notLike: '%test%' } } // NOT LIKE '%test%'
296
+
297
+ // Range
298
+ { price: { $between: [10, 100] } } // BETWEEN 10 AND 100
299
+
300
+ // Null
301
+ { description: { $isNull: true } } // IS NULL
302
+ { description: { $isNull: false } } // IS NOT NULL
303
+
304
+ // Logical
305
+ { $and: [
306
+ { price: { $gt: 50 } },
307
+ { stock: { $gt: 0 } }
308
+ ]}
309
+
310
+ { $or: [
311
+ { category: 'shirts' },
312
+ { category: 'pants' }
313
+ ]}
314
+
315
+ { $not: { price: { $lt: 100 } } } // NOT (price < 100)
316
+ ```
317
+
318
+ ### Query Interface
319
+
320
+ ```typescript
321
+ interface QueryOptions {
322
+ where?: Record<string, unknown>;
323
+ orderBy?: Array<{
324
+ field: string;
325
+ direction: 'ASC' | 'DESC';
326
+ }>;
327
+ limit?: number;
328
+ offset?: number;
329
+ populate?: string[];
330
+ lang?: string;
331
+ fallbackLang?: string;
332
+ }
333
+ ```
334
+
335
+ ---
336
+
337
+ ## Multi-Language Support
338
+
339
+ ### Schema with Translatable Fields
340
+
341
+ ```typescript
342
+ import { defineSchema, f } from '@promakeai/orm';
343
+
344
+ const schema = defineSchema({
345
+ languages: ['en', 'tr', 'de'],
346
+ tables: {
347
+ products: {
348
+ id: f.id(),
349
+ price: f.decimal(),
350
+ name: f.string().translatable(), // In translation table
351
+ description: f.text().translatable(), // In translation table
352
+ },
353
+ },
354
+ });
355
+ ```
356
+
357
+ ### Automatic Translation
358
+
359
+ Queries automatically use the current language from DbProvider:
360
+
361
+ ```tsx
362
+ function ProductList() {
363
+ // Automatically fetches in current language
364
+ const { data: products } = useDbList('products');
365
+
366
+ // products[0].name is in Turkish if lang='tr'
367
+ // Falls back to English if Turkish translation missing
368
+ }
369
+ ```
370
+
371
+ ### Language Switching
372
+
373
+ When you change language, React Query automatically refetches all queries:
374
+
375
+ ```tsx
376
+ function LanguageSwitcher() {
377
+ const { lang, setLang } = useDbLang();
378
+
379
+ return (
380
+ <select value={lang} onChange={e => setLang(e.target.value)}>
381
+ <option value="en">English</option>
382
+ <option value="tr">Turkce</option>
383
+ </select>
384
+ );
385
+ }
386
+ ```
387
+
388
+ ---
389
+
390
+ ## Generated Hooks
391
+
392
+ Generate type-safe hooks from your schema:
393
+
394
+ ```bash
395
+ dbcli generate --schema ./schema.ts --output ./src/db/generated
396
+ ```
397
+
398
+ **Generated files:**
399
+ - `types.ts` - TypeScript interfaces for each table
400
+ - `hooks.ts` - Type-safe hooks for each table
401
+
402
+ ```tsx
403
+ // Generated hooks usage
404
+ import { useProducts, useProduct, useCreateProduct } from './db/generated/hooks';
405
+
406
+ function ProductManager() {
407
+ const { data: products } = useProducts({
408
+ where: { stock: { $gt: 0 } },
409
+ });
410
+
411
+ const { data: product } = useProduct(1);
412
+
413
+ const createProduct = useCreateProduct();
414
+
415
+ return (
416
+ // ...
417
+ );
418
+ }
419
+ ```
420
+
421
+ ---
422
+
423
+ ## Advanced Usage
424
+
425
+ ### Custom React Query Options
426
+
427
+ ```tsx
428
+ const { data } = useDbList('products', {
429
+ where: { active: true },
430
+ }, {
431
+ // React Query options
432
+ staleTime: 1000 * 60 * 5, // 5 minutes
433
+ gcTime: 1000 * 60 * 30, // 30 minutes
434
+ refetchOnWindowFocus: false,
435
+ retry: 3,
436
+ });
437
+ ```
438
+
439
+ ### Direct Adapter Access
440
+
441
+ For complex queries not covered by hooks:
442
+
443
+ ```tsx
444
+ function AdvancedSearch() {
445
+ const adapter = useAdapter();
446
+ const [results, setResults] = useState([]);
447
+
448
+ const handleSearch = async (query: string) => {
449
+ const products = await adapter.list('products', {
450
+ where: {
451
+ $or: [
452
+ { name: { $like: `%${query}%` } },
453
+ { description: { $like: `%${query}%` } },
454
+ ],
455
+ },
456
+ limit: 50,
457
+ });
458
+ setResults(products);
459
+ };
460
+
461
+ return <SearchInput onSearch={handleSearch} />;
462
+ }
463
+ ```
464
+
465
+ ### Optimistic Updates
466
+
467
+ ```tsx
468
+ function ProductPrice({ product }) {
469
+ const queryClient = useQueryClient();
470
+ const updateProduct = useDbUpdate('products', product.id);
471
+
472
+ const handlePriceChange = async (newPrice: number) => {
473
+ // Optimistic update
474
+ queryClient.setQueryData(
475
+ ['products', 'detail', product.id],
476
+ { ...product, price: newPrice }
477
+ );
478
+
479
+ await updateProduct.mutateAsync({ price: newPrice });
480
+ };
481
+
482
+ return <PriceInput value={product.price} onChange={handlePriceChange} />;
483
+ }
484
+ ```
485
+
486
+ ---
487
+
488
+ ## Examples
489
+
490
+ ### E-Commerce Product List
491
+
492
+ ```tsx
493
+ function Shop() {
494
+ const { lang } = useDbLang();
495
+ const [category, setCategory] = useState(null);
496
+
497
+ const { data: products, isLoading } = useDbList('products', {
498
+ where: category ? { categoryId: category } : {},
499
+ orderBy: [{ field: 'name', direction: 'ASC' }],
500
+ });
501
+
502
+ if (isLoading) return <Spinner />;
503
+
504
+ return (
505
+ <div>
506
+ <CategoryFilter onSelect={setCategory} />
507
+ <ProductGrid products={products} />
508
+ </div>
509
+ );
510
+ }
511
+ ```
512
+
513
+ ### Multi-Language Blog
514
+
515
+ ```tsx
516
+ function BlogPost({ slug }: { slug: string }) {
517
+ const { data: posts } = useDbList('posts', {
518
+ where: { slug, published: true },
519
+ limit: 1,
520
+ });
521
+
522
+ const post = posts?.[0];
523
+ if (!post) return <NotFound />;
524
+
525
+ return (
526
+ <article>
527
+ <h1>{post.title}</h1> {/* Auto-translated */}
528
+ <div>{post.content}</div> {/* Auto-translated */}
529
+ </article>
530
+ );
531
+ }
532
+ ```
533
+
534
+ ### Real-Time Search
535
+
536
+ ```tsx
537
+ function ProductSearch() {
538
+ const adapter = useAdapter();
539
+ const [query, setQuery] = useState('');
540
+
541
+ const { data: results } = useQuery({
542
+ queryKey: ['search', query],
543
+ queryFn: () => adapter.list('products', {
544
+ where: {
545
+ $or: [
546
+ { name: { $like: `%${query}%` } },
547
+ { sku: { $like: `%${query}%` } },
548
+ ],
549
+ },
550
+ limit: 10,
551
+ }),
552
+ enabled: query.length > 2,
553
+ });
554
+
555
+ return (
556
+ <div>
557
+ <input value={query} onChange={e => setQuery(e.target.value)} />
558
+ <SearchResults results={results} />
559
+ </div>
560
+ );
561
+ }
562
+ ```
563
+
564
+ ---
565
+
566
+ ## TypeScript Support
567
+
568
+ Full TypeScript support with generated types:
569
+
570
+ ```typescript
571
+ import type { Product, ProductInput } from './db/generated/types';
572
+
573
+ // Type-safe queries
574
+ const products: Product[] = await adapter.list('products');
575
+
576
+ // Type-safe creates
577
+ const newProduct: ProductInput = {
578
+ sku: 'SHIRT-001',
579
+ price: 99.99,
580
+ };
581
+
582
+ await adapter.create('products', newProduct);
583
+ ```
584
+
585
+ ---
586
+
587
+ ## Performance Tips
588
+
589
+ 1. **Use pagination** for large datasets:
590
+ ```tsx
591
+ const { data } = useDbList('products', { limit: 20, offset: page * 20 });
592
+ ```
593
+
594
+ 2. **Enable query conditionally**:
595
+ ```tsx
596
+ const { data } = useDbGet('products', id, { enabled: !!id });
597
+ ```
598
+
599
+ 3. **Prefetch next page**:
600
+ ```tsx
601
+ queryClient.prefetchQuery({
602
+ queryKey: ['products', 'list', { offset: (page + 1) * 20 }],
603
+ queryFn: () => adapter.list('products', { offset: (page + 1) * 20 }),
604
+ });
605
+ ```
606
+
607
+ ---
608
+
609
+ ## Troubleshooting
610
+
611
+ **"useDbLang must be used within a DbProvider"**
612
+ - Ensure component is wrapped in DbProvider
613
+
614
+ **Queries return empty results**
615
+ - Check adapter is connected: `const { isConnected } = useDb()`
616
+ - Verify data exists in database
617
+ - Check query filters are correct
618
+
619
+ **Translations not working**
620
+ - Ensure schema has `.translatable()` fields
621
+ - Run `dbcli generate` to create translation tables
622
+ - Check translation records exist in `{table}_translations`
623
+
624
+ ---
625
+
626
+ ## Related Packages
627
+
628
+ - [@promakeai/orm](../orm) - Core ORM with schema DSL
629
+ - [@promakeai/dbcli](../dbcli) - CLI tool for database operations
630
+
631
+ ## License
632
+
633
+ MIT