@promakeai/dbreact 1.0.3 → 1.0.5

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 CHANGED
@@ -4,12 +4,12 @@ React hooks and providers for schema-driven, multi-language databases. Type-safe
4
4
 
5
5
  ## Features
6
6
 
7
- - **Type-Safe Hooks** - Generated TypeScript types and React hooks
7
+ - **Type-Safe Hooks** - Generic React hooks with generated TypeScript types
8
8
  - **Multi-Language Support** - Automatic translation queries with fallback
9
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.)
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`, `$contains`, etc.)
13
13
 
14
14
  ## Installation
15
15
 
@@ -26,11 +26,16 @@ npm install @promakeai/dbreact @tanstack/react-query
26
26
  ### 1. Setup Adapter
27
27
 
28
28
  ```tsx
29
- import { SqliteAdapter } from '@promakeai/dbreact';
30
-
31
- const adapter = new SqliteAdapter({
32
- storageKey: 'myapp-db', // localStorage key for persistence
33
- });
29
+ import { SqliteAdapter } from '@promakeai/dbreact';
30
+ import { parseJSONSchema } from '@promakeai/dbreact';
31
+ import schemaJson from './schema.json';
32
+
33
+ const schema = parseJSONSchema(schemaJson as any);
34
+
35
+ const adapter = new SqliteAdapter({
36
+ storageKey: 'myapp-db', // localStorage key for persistence
37
+ schema,
38
+ });
34
39
  ```
35
40
 
36
41
  ### 2. Wrap App with Provider
@@ -98,24 +103,27 @@ Main provider component that wraps your application.
98
103
 
99
104
  ```tsx
100
105
  <DbProvider
101
- adapter={adapter}
102
- lang="tr"
103
- fallbackLang="en"
104
- autoConnect={true}
105
- >
106
+ adapter={adapter}
107
+ schema={schema}
108
+ lang="tr"
109
+ fallbackLang="en"
110
+ autoConnect={true}
111
+ >
106
112
  <App />
107
113
  </DbProvider>
108
114
  ```
109
115
 
110
116
  **Props:**
111
117
 
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 |
118
+ | Prop | Type | Required | Default | Description |
119
+ |------|------|----------|---------|-------------|
120
+ | `adapter` | `IDataAdapter` | Yes | - | Database adapter instance |
121
+ | `schema` | `SchemaDefinition` | No | - | Enables populate + typed serialization |
122
+ | `lang` | `string` | No | `'en'` | Current language code |
123
+ | `fallbackLang` | `string` | No | `'en'` | Fallback language |
124
+ | `autoConnect` | `boolean` | No | `true` | Auto-connect on mount |
125
+ | `queryClient` | `QueryClient` | No | Internal | Provide a custom React Query client |
126
+ | `children` | `ReactNode` | Yes | - | Child components |
119
127
 
120
128
  ---
121
129
 
@@ -143,19 +151,29 @@ const { data, isLoading, error, refetch } = useDbList<Product>('products', {
143
151
  | `orderBy` | `array` | Sort order `[{ field, direction }]` |
144
152
  | `limit` | `number` | Max records to return |
145
153
  | `offset` | `number` | Skip records |
146
- | `populate` | `string[]` | Resolve foreign key references |
154
+ | `populate` | `PopulateOption` | Resolve foreign key references (string, array, or object) |
147
155
  | `enabled` | `boolean` | Enable/disable query |
148
156
 
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
- ```
157
+ #### useDbGet
158
+
159
+ Fetch single record by ID.
160
+
161
+ ```tsx
162
+ const { data: product, isLoading } = useDbGet<Product>('products', productId, {
163
+ populate: ['categoryId'],
164
+ enabled: !!productId,
165
+ });
166
+ ```
167
+
168
+ Find-one style (by where clause):
169
+
170
+ ```tsx
171
+ const { data: product } = useDbGet<Product>('products', {
172
+ where: { slug: 'my-product' },
173
+ enabled: !!slug,
174
+ populate: { categoryId: true },
175
+ });
176
+ ```
159
177
 
160
178
  ---
161
179
 
@@ -182,13 +200,13 @@ createProduct.mutate(
182
200
  Update an existing record.
183
201
 
184
202
  ```tsx
185
- const updateProduct = useDbUpdate<Product>('products', productId);
186
-
187
- updateProduct.mutate(
188
- { price: 89.99 },
189
- {
190
- onSuccess: () => console.log('Updated'),
191
- }
203
+ const updateProduct = useDbUpdate<Product>('products');
204
+
205
+ updateProduct.mutate(
206
+ { id: productId, data: { price: 89.99 } },
207
+ {
208
+ onSuccess: () => console.log('Updated'),
209
+ }
192
210
  );
193
211
  ```
194
212
 
@@ -197,11 +215,11 @@ updateProduct.mutate(
197
215
  Delete a record.
198
216
 
199
217
  ```tsx
200
- const deleteProduct = useDbDelete('products', productId);
201
-
202
- deleteProduct.mutate(undefined, {
203
- onSuccess: () => console.log('Deleted'),
204
- });
218
+ const deleteProduct = useDbDelete('products');
219
+
220
+ deleteProduct.mutate(productId, {
221
+ onSuccess: () => console.log('Deleted'),
222
+ });
205
223
  ```
206
224
 
207
225
  ---
@@ -294,12 +312,16 @@ const adapter = new SqliteAdapter({
294
312
  { name: { $like: '%shirt%' } } // LIKE '%shirt%'
295
313
  { name: { $notLike: '%test%' } } // NOT LIKE '%test%'
296
314
 
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
315
+ // Range
316
+ { price: { $between: [10, 100] } } // BETWEEN 10 AND 100
317
+
318
+ // Null
319
+ { description: { $isNull: true } } // IS NULL
320
+ { description: { $isNull: false } } // IS NOT NULL
321
+
322
+ // JSON array contains
323
+ { tags: { $contains: "sale" } }
324
+ { tags: { $containsAny: ["sale", "new"] } }
303
325
 
304
326
  // Logical
305
327
  { $and: [
@@ -318,18 +340,18 @@ const adapter = new SqliteAdapter({
318
340
  ### Query Interface
319
341
 
320
342
  ```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
- }
343
+ interface QueryOptions {
344
+ where?: Record<string, unknown>;
345
+ orderBy?: Array<{
346
+ field: string;
347
+ direction: 'ASC' | 'DESC';
348
+ }>;
349
+ limit?: number;
350
+ offset?: number;
351
+ populate?: PopulateOption;
352
+ lang?: string;
353
+ fallbackLang?: string;
354
+ }
333
355
  ```
334
356
 
335
357
  ---
@@ -387,36 +409,32 @@ function LanguageSwitcher() {
387
409
 
388
410
  ---
389
411
 
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
- ```
412
+ ## Generated Types + Generic Hooks
413
+
414
+ Generate runtime schema and TypeScript interfaces:
415
+
416
+ ```bash
417
+ dbcli generate --schema ./schema.json --output ./src/db
418
+ ```
419
+
420
+ `dbcli generate` writes `schema.json` and `types.ts`. React hooks are imported from `@promakeai/dbreact`:
421
+
422
+ ```tsx
423
+ import { useDbList, useDbGet, useDbCreate } from '@promakeai/dbreact';
424
+ import type { DbProduct, DbProductInput } from './db/types';
425
+
426
+ function ProductManager() {
427
+ const { data: products } = useDbList<DbProduct>('products', {
428
+ where: { stock: { $gt: 0 } },
429
+ });
430
+
431
+ const { data: product } = useDbGet<DbProduct>('products', 1);
432
+
433
+ const createProduct = useDbCreate<DbProduct, DbProductInput>('products');
434
+
435
+ return null;
436
+ }
437
+ ```
420
438
 
421
439
  ---
422
440
 
@@ -425,15 +443,10 @@ function ProductManager() {
425
443
  ### Custom React Query Options
426
444
 
427
445
  ```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
- });
446
+ const { data } = useDbList('products', {
447
+ where: { active: true },
448
+ limit: 50,
449
+ });
437
450
  ```
438
451
 
439
452
  ### Direct Adapter Access
@@ -467,7 +480,7 @@ function AdvancedSearch() {
467
480
  ```tsx
468
481
  function ProductPrice({ product }) {
469
482
  const queryClient = useQueryClient();
470
- const updateProduct = useDbUpdate('products', product.id);
483
+ const updateProduct = useDbUpdate('products');
471
484
 
472
485
  const handlePriceChange = async (newPrice: number) => {
473
486
  // Optimistic update
@@ -476,7 +489,10 @@ function ProductPrice({ product }) {
476
489
  { ...product, price: newPrice }
477
490
  );
478
491
 
479
- await updateProduct.mutateAsync({ price: newPrice });
492
+ await updateProduct.mutateAsync({
493
+ id: product.id,
494
+ data: { price: newPrice },
495
+ });
480
496
  };
481
497
 
482
498
  return <PriceInput value={product.price} onChange={handlePriceChange} />;
@@ -568,13 +584,13 @@ function ProductSearch() {
568
584
  Full TypeScript support with generated types:
569
585
 
570
586
  ```typescript
571
- import type { Product, ProductInput } from './db/generated/types';
587
+ import type { DbProduct, DbProductInput } from './db/types';
572
588
 
573
589
  // Type-safe queries
574
- const products: Product[] = await adapter.list('products');
590
+ const products: DbProduct[] = await adapter.list('products');
575
591
 
576
592
  // Type-safe creates
577
- const newProduct: ProductInput = {
593
+ const newProduct: DbProductInput = {
578
594
  sku: 'SHIRT-001',
579
595
  price: 99.99,
580
596
  };
@@ -606,10 +622,14 @@ await adapter.create('products', newProduct);
606
622
 
607
623
  ---
608
624
 
609
- ## Troubleshooting
610
-
611
- **"useDbLang must be used within a DbProvider"**
612
- - Ensure component is wrapped in DbProvider
625
+ ## Troubleshooting
626
+
627
+ **Production crash: `jsxDEV is not a function`**
628
+ - Run `bun run test:build-runtime` before publishing.
629
+ - This verifies `dist/index.js` does not include `react/jsx-dev-runtime`.
630
+
631
+ **"useDbLang must be used within a DbProvider"**
632
+ - Ensure component is wrapped in DbProvider
613
633
 
614
634
  **Queries return empty results**
615
635
  - Check adapter is connected: `const { isConnected } = useDb()`
@@ -32,6 +32,7 @@ export interface SqliteAdapterConfig {
32
32
  export declare class SqliteAdapter implements IDataAdapter {
33
33
  private db;
34
34
  private SQL;
35
+ private tableColumnsCache;
35
36
  private config;
36
37
  schema?: SchemaDefinition;
37
38
  defaultLang?: string;
@@ -141,6 +142,8 @@ export declare class SqliteAdapter implements IDataAdapter {
141
142
  created: number;
142
143
  ids: (number | bigint)[];
143
144
  }>;
145
+ private getAvailableMainFallbackFields;
146
+ private getTableColumns;
144
147
  updateMany(table: string, updates: {
145
148
  id: number | string;
146
149
  data: Record<string, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"SqliteAdapter.d.ts","sourceRoot":"","sources":["../../adapters/SqliteAdapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAepG,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,WAAW,CAAC,EAAE,UAAU,CAAC;IACzB,gDAAgD;IAChD,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,aAAc,YAAW,YAAY;IAChD,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,GAAG,CAA4B;IACvC,OAAO,CAAC,MAAM,CAGZ;IAEF,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;gBAET,MAAM,GAAE,mBAAwB;IAc5C,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAKnC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB9B,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,KAAK;IAOb,OAAO,CAAC,QAAQ;IAkBhB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;IACH,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,gBAAgB;IAuCxB;;OAEG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,CAAC,EAAE,CAAC;YAWD,YAAY;IA2B1B;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EACnB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,GAAG,MAAM,EACnB,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GACjD,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAUd,OAAO,CAAC,CAAC,GAAG,OAAO,EACvB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAClF,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;YAKN,WAAW;IA8BzB;;OAEG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAMnE;;OAEG;IACG,QAAQ,CAAC,CAAC,GAAG,OAAO,EACxB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAqB9B;;OAEG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EACtB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC;IA4Cb;;OAEG;IACG,sBAAsB,CAAC,CAAC,GAAG,OAAO,EACtC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACrD,OAAO,CAAC,CAAC,CAAC;IAwCb;;OAEG;IACG,MAAM,CAAC,CAAC,GAAG,OAAO,EACtB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,GAAG,MAAM,EACnB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,CAAC,CAAC;IAyCb;;OAEG;IACG,iBAAiB,CACrB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,GAAG,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,IAAI,CAAC;IAYhB;;OAEG;IACG,eAAe,CAAC,CAAC,GAAG,OAAO,EAC/B,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,GAAG,MAAM,GAClB,OAAO,CAAC,CAAC,EAAE,CAAC;IAaf;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsBlE;;OAEG;IACG,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAWhH;;OAEG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,CAAC,EAAE,CAAC;IAIf;;OAEG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB1E;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAOpC;;OAEG;IACG,cAAc,CAClB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAgBzE;;OAEG;IACH,KAAK,IAAI,IAAI;IASP,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKvB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAKzB,UAAU,CAAC,CAAC,GAAG,OAAO,EAC1B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAClC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAC7B,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAA;KAAE,CAAC;IAwCnD,UAAU,CACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAAE,GAChE,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAWzB,UAAU,CACd,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GACvB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAW/B;;OAEG;IACH,MAAM,IAAI,UAAU;IAIpB;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAS7C;;OAEG;IACH,KAAK,IAAI,IAAI;CAQd"}
1
+ {"version":3,"file":"SqliteAdapter.d.ts","sourceRoot":"","sources":["../../adapters/SqliteAdapter.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,7 @@ import { buildWhereClause, buildTranslationQuery, buildTranslationQueryById, bui
21
21
  export class SqliteAdapter {
22
22
  db = null;
23
23
  SQL = null;
24
+ tableColumnsCache = new Map();
24
25
  config;
25
26
  schema;
26
27
  defaultLang;
@@ -39,6 +40,7 @@ export class SqliteAdapter {
39
40
  setSchema(schema) {
40
41
  this.schema = schema;
41
42
  this.config.schema = schema;
43
+ this.tableColumnsCache.clear();
42
44
  }
43
45
  async connect() {
44
46
  // Initialize sql.js
@@ -162,6 +164,7 @@ export class SqliteAdapter {
162
164
  schema: this.schema,
163
165
  lang: options.lang,
164
166
  fallbackLang: options.fallbackLang ?? this.defaultLang,
167
+ mainFallbackFields: await this.getAvailableMainFallbackFields(table),
165
168
  where: options.where,
166
169
  orderBy: options.orderBy,
167
170
  limit: options.limit,
@@ -195,7 +198,7 @@ export class SqliteAdapter {
195
198
  const results = await this.list(table, { where: { id }, limit: 1 });
196
199
  return results[0] ?? null;
197
200
  }
198
- const { sql, params } = buildTranslationQueryById(table, this.schema, id, options.lang, options.fallbackLang ?? this.defaultLang);
201
+ const { sql, params } = buildTranslationQueryById(table, this.schema, id, options.lang, options.fallbackLang ?? this.defaultLang, await this.getAvailableMainFallbackFields(table));
199
202
  const rows = this.runQuery(sql, params);
200
203
  const deserialized = this.deserializeResults(table, rows);
201
204
  return deserialized[0] ?? null;
@@ -449,6 +452,7 @@ export class SqliteAdapter {
449
452
  this.persist();
450
453
  this.db.close();
451
454
  this.db = null;
455
+ this.tableColumnsCache.clear();
452
456
  }
453
457
  }
454
458
  // Transaction methods
@@ -495,6 +499,25 @@ export class SqliteAdapter {
495
499
  this.persist();
496
500
  return { created: ids.length, ids };
497
501
  }
502
+ async getAvailableMainFallbackFields(table) {
503
+ const tableSchema = this.schema?.tables[table];
504
+ if (!tableSchema)
505
+ return [];
506
+ const translatableFields = getTranslatableFields(tableSchema);
507
+ if (translatableFields.length === 0)
508
+ return [];
509
+ const tableColumns = await this.getTableColumns(table);
510
+ return translatableFields.filter((field) => tableColumns.has(field));
511
+ }
512
+ async getTableColumns(table) {
513
+ const cached = this.tableColumnsCache.get(table);
514
+ if (cached)
515
+ return cached;
516
+ const schema = await this.getTableSchema(table);
517
+ const columnSet = new Set(schema.map((col) => col.name));
518
+ this.tableColumnsCache.set(table, columnSet);
519
+ return columnSet;
520
+ }
498
521
  async updateMany(table, updates) {
499
522
  let updated = 0;
500
523
  for (const { id, data } of updates) {
@@ -21,7 +21,8 @@
21
21
  * ```
22
22
  */
23
23
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
24
- import { useAdapter, useDbLang } from "../providers/DbProvider";
24
+ import { useAdapter, useDb, useDbLang } from "../providers/DbProvider";
25
+ import { resolvePopulate } from "@promakeai/orm";
25
26
  /**
26
27
  * Hook to list all records from a table
27
28
  *
@@ -40,14 +41,25 @@ import { useAdapter, useDbLang } from "../providers/DbProvider";
40
41
  */
41
42
  export function useDbList(table, options) {
42
43
  const adapter = useAdapter();
44
+ const { schema } = useDb();
43
45
  const { lang, fallbackLang } = useDbLang();
46
+ const { populate, ...queryOpts } = options || {};
44
47
  return useQuery({
45
- queryKey: [table, "list", options, lang],
46
- queryFn: () => adapter.list(table, {
47
- ...options,
48
- lang,
49
- fallbackLang,
50
- }),
48
+ queryKey: [table, "list", queryOpts, lang, populate],
49
+ queryFn: async () => {
50
+ const records = await adapter.list(table, {
51
+ ...queryOpts,
52
+ lang,
53
+ fallbackLang,
54
+ });
55
+ if (populate && schema) {
56
+ const adapterWrapper = {
57
+ findMany: (t, opts) => adapter.list(t, { ...opts, lang, fallbackLang }),
58
+ };
59
+ return resolvePopulate(records, table, populate, schema, adapterWrapper);
60
+ }
61
+ return records;
62
+ },
51
63
  enabled: options?.enabled ?? true,
52
64
  });
53
65
  }
@@ -73,15 +85,27 @@ export function useDbList(table, options) {
73
85
  */
74
86
  export function useDbGet(table, idOrOptions, maybeOptions) {
75
87
  const adapter = useAdapter();
88
+ const { schema } = useDb();
76
89
  const { lang, fallbackLang } = useDbLang();
77
90
  const isWhereMode = typeof idOrOptions === "object" && idOrOptions !== null && "where" in idOrOptions;
78
91
  const where = isWhereMode ? idOrOptions.where : { id: idOrOptions };
79
92
  const enabled = isWhereMode
80
93
  ? (idOrOptions.enabled ?? true)
81
94
  : ((maybeOptions?.enabled ?? true) && idOrOptions !== undefined);
95
+ const populate = isWhereMode ? idOrOptions.populate : maybeOptions?.populate;
82
96
  return useQuery({
83
- queryKey: [table, "single", where, lang],
84
- queryFn: () => adapter.findOne(table, { where, lang, fallbackLang }),
97
+ queryKey: [table, "single", where, lang, populate],
98
+ queryFn: async () => {
99
+ const record = await adapter.findOne(table, { where, lang, fallbackLang });
100
+ if (record && populate && schema) {
101
+ const adapterWrapper = {
102
+ findMany: (t, opts) => adapter.list(t, { ...opts, lang, fallbackLang }),
103
+ };
104
+ const [populated] = await resolvePopulate([record], table, populate, schema, adapterWrapper);
105
+ return populated;
106
+ }
107
+ return record;
108
+ },
85
109
  enabled,
86
110
  });
87
111
  }
package/dist/index.js CHANGED
@@ -1,674 +1,14 @@
1
- // index.ts
2
- import {
3
- ORM,
4
- defineSchema,
5
- f,
6
- buildWhereClause as buildWhereClause2,
7
- resolvePopulate as resolvePopulate2,
8
- getPopulatableFields,
9
- validatePopulate,
10
- parseJSONSchema
11
- } from "@promakeai/orm";
12
-
13
- // providers/DbProvider.tsx
14
- import {
15
- createContext,
16
- useContext,
17
- useState,
18
- useEffect,
19
- useMemo,
20
- useCallback,
21
- useRef
22
- } from "react";
23
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
24
- import { jsxDEV } from "react/jsx-dev-runtime";
25
- var DbContext = createContext(null);
26
- var DbLangContext = createContext(null);
27
- var defaultQueryClient = new QueryClient({
28
- defaultOptions: {
29
- queries: {
30
- staleTime: 1000 * 60 * 5,
31
- gcTime: 1000 * 60 * 30,
32
- refetchOnWindowFocus: false
33
- }
34
- }
35
- });
36
- function DbProvider({
37
- adapter,
38
- schema,
39
- lang: langProp = "en",
40
- fallbackLang = "en",
41
- autoConnect = true,
42
- queryClient = defaultQueryClient,
43
- children
44
- }) {
45
- const [isConnected, setIsConnected] = useState(false);
46
- const [error, setError] = useState(null);
47
- const [lang, setLangState] = useState(langProp);
48
- const isFirstRender = useRef(true);
49
- useEffect(() => {
50
- if (langProp !== lang) {
51
- setLangState(langProp);
52
- }
53
- }, [langProp]);
54
- useEffect(() => {
55
- if (isFirstRender.current) {
56
- isFirstRender.current = false;
57
- return;
58
- }
59
- queryClient.invalidateQueries();
60
- }, [lang]);
61
- useEffect(() => {
62
- if (!autoConnect)
63
- return;
64
- let mounted = true;
65
- async function connect() {
66
- try {
67
- await adapter.connect?.();
68
- if (mounted) {
69
- setIsConnected(true);
70
- setError(null);
71
- }
72
- } catch (err) {
73
- if (mounted) {
74
- setError(err instanceof Error ? err : new Error(String(err)));
75
- setIsConnected(false);
76
- }
77
- }
78
- }
79
- connect();
80
- return () => {
81
- mounted = false;
82
- };
83
- }, [adapter, autoConnect]);
84
- const setLang = useCallback((newLang) => {
85
- setLangState(newLang);
86
- }, []);
87
- const dbContextValue = useMemo(() => ({
88
- adapter,
89
- schema,
90
- isConnected,
91
- error
92
- }), [adapter, schema, isConnected, error]);
93
- const langContextValue = useMemo(() => ({
94
- lang,
95
- fallbackLang,
96
- setLang
97
- }), [lang, fallbackLang, setLang]);
98
- return /* @__PURE__ */ jsxDEV(QueryClientProvider, {
99
- client: queryClient,
100
- children: /* @__PURE__ */ jsxDEV(DbContext.Provider, {
101
- value: dbContextValue,
102
- children: /* @__PURE__ */ jsxDEV(DbLangContext.Provider, {
103
- value: langContextValue,
104
- children
105
- }, undefined, false, undefined, this)
106
- }, undefined, false, undefined, this)
107
- }, undefined, false, undefined, this);
108
- }
109
- function useDb() {
110
- const context = useContext(DbContext);
111
- if (!context) {
112
- throw new Error("useDb must be used within a DbProvider");
113
- }
114
- return context;
115
- }
116
- function useAdapter() {
117
- const { adapter } = useDb();
118
- return adapter;
119
- }
120
- function useDbLang() {
121
- const context = useContext(DbLangContext);
122
- if (!context) {
123
- throw new Error("useDbLang must be used within a DbProvider");
124
- }
125
- return context;
126
- }
127
- // hooks/useDbHooks.ts
128
- import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
129
- import { resolvePopulate } from "@promakeai/orm";
130
- function useDbList(table, options) {
131
- const adapter = useAdapter();
132
- const { schema } = useDb();
133
- const { lang, fallbackLang } = useDbLang();
134
- const { populate, ...queryOpts } = options || {};
135
- return useQuery({
136
- queryKey: [table, "list", queryOpts, lang, populate],
137
- queryFn: async () => {
138
- const records = await adapter.list(table, {
139
- ...queryOpts,
140
- lang,
141
- fallbackLang
142
- });
143
- if (populate && schema) {
144
- const adapterWrapper = {
145
- findMany: (t, opts) => adapter.list(t, { ...opts, lang, fallbackLang })
146
- };
147
- return resolvePopulate(records, table, populate, schema, adapterWrapper);
148
- }
149
- return records;
150
- },
151
- enabled: options?.enabled ?? true
152
- });
153
- }
154
- function useDbGet(table, idOrOptions, maybeOptions) {
155
- const adapter = useAdapter();
156
- const { schema } = useDb();
157
- const { lang, fallbackLang } = useDbLang();
158
- const isWhereMode = typeof idOrOptions === "object" && idOrOptions !== null && "where" in idOrOptions;
159
- const where = isWhereMode ? idOrOptions.where : { id: idOrOptions };
160
- const enabled = isWhereMode ? idOrOptions.enabled ?? true : (maybeOptions?.enabled ?? true) && idOrOptions !== undefined;
161
- const populate = isWhereMode ? idOrOptions.populate : maybeOptions?.populate;
162
- return useQuery({
163
- queryKey: [table, "single", where, lang, populate],
164
- queryFn: async () => {
165
- const record = await adapter.findOne(table, { where, lang, fallbackLang });
166
- if (record && populate && schema) {
167
- const adapterWrapper = {
168
- findMany: (t, opts) => adapter.list(t, { ...opts, lang, fallbackLang })
169
- };
170
- const [populated] = await resolvePopulate([record], table, populate, schema, adapterWrapper);
171
- return populated;
172
- }
173
- return record;
174
- },
175
- enabled
176
- });
177
- }
178
- function useDbCreate(table) {
179
- const adapter = useAdapter();
180
- const queryClient = useQueryClient();
181
- return useMutation({
182
- mutationFn: (data) => adapter.create(table, data),
183
- onSuccess: () => {
184
- queryClient.invalidateQueries({ queryKey: [table] });
185
- }
186
- });
187
- }
188
- function useDbUpdate(table) {
189
- const adapter = useAdapter();
190
- const queryClient = useQueryClient();
191
- return useMutation({
192
- mutationFn: ({ id, data }) => adapter.update(table, id, data),
193
- onSuccess: (_, { id }) => {
194
- queryClient.invalidateQueries({ queryKey: [table] });
195
- queryClient.invalidateQueries({ queryKey: [table, "single", id] });
196
- }
197
- });
198
- }
199
- function useDbDelete(table) {
200
- const adapter = useAdapter();
201
- const queryClient = useQueryClient();
202
- return useMutation({
203
- mutationFn: (id) => adapter.delete(table, id),
204
- onSuccess: () => {
205
- queryClient.invalidateQueries({ queryKey: [table] });
206
- }
207
- });
208
- }
209
- // adapters/SqliteAdapter.ts
210
- import initSqlJs from "sql.js";
211
- import {
212
- buildWhereClause,
213
- buildTranslationQuery,
214
- buildTranslationQueryById,
215
- buildTranslationUpsert,
216
- extractTranslatableData,
217
- getTranslatableFields,
218
- toTranslationTableName,
219
- toTranslationFKName,
220
- deserializeRow,
221
- serializeRow
222
- } from "@promakeai/orm";
223
-
224
- class SqliteAdapter {
225
- db = null;
226
- SQL = null;
227
- config;
228
- schema;
229
- defaultLang;
230
- constructor(config = {}) {
231
- this.config = {
232
- storageKey: config.storageKey ?? "dbreact_db",
233
- wasmPath: config.wasmPath ?? "https://sql.js.org/dist/sql-wasm.wasm",
234
- initialData: config.initialData ?? new Uint8Array,
235
- schema: config.schema,
236
- defaultLang: config.defaultLang
237
- };
238
- this.schema = config.schema;
239
- this.defaultLang = config.defaultLang;
240
- }
241
- setSchema(schema) {
242
- this.schema = schema;
243
- this.config.schema = schema;
244
- }
245
- async connect() {
246
- this.SQL = await initSqlJs({
247
- locateFile: (file) => file === "sql-wasm.wasm" ? this.config.wasmPath : file
248
- });
249
- const saved = localStorage.getItem(this.config.storageKey);
250
- if (saved) {
251
- const data = Uint8Array.from(atob(saved), (c) => c.charCodeAt(0));
252
- this.db = new this.SQL.Database(data);
253
- } else if (this.config.initialData.length > 0) {
254
- this.db = new this.SQL.Database(this.config.initialData);
255
- this.persist();
256
- } else {
257
- this.db = new this.SQL.Database;
258
- }
259
- }
260
- persist() {
261
- if (!this.db)
262
- return;
263
- const data = this.db.export();
264
- const base64 = btoa(String.fromCharCode(...data));
265
- localStorage.setItem(this.config.storageKey, base64);
266
- }
267
- getDb() {
268
- if (!this.db) {
269
- throw new Error("Database not connected. Call connect() first.");
270
- }
271
- return this.db;
272
- }
273
- runQuery(sql, params = []) {
274
- const db = this.getDb();
275
- const result = db.exec(sql, params);
276
- if (result.length === 0 || result[0].values.length === 0) {
277
- return [];
278
- }
279
- const columns = result[0].columns;
280
- return result[0].values.map((row) => {
281
- const obj = {};
282
- columns.forEach((col, i) => {
283
- obj[col] = row[i];
284
- });
285
- return obj;
286
- });
287
- }
288
- deserializeResults(table, rows) {
289
- const tableSchema = this.schema?.tables[table];
290
- if (!tableSchema || rows.length === 0)
291
- return rows;
292
- return rows.map((row) => deserializeRow(row, tableSchema.fields));
293
- }
294
- serializeData(table, data) {
295
- const tableSchema = this.schema?.tables[table];
296
- if (!tableSchema)
297
- return data;
298
- return serializeRow(data, tableSchema.fields);
299
- }
300
- buildSelectQuery(table, options, countOnly = false) {
301
- const select = countOnly ? "COUNT(*) as count" : "*";
302
- let sql = `SELECT ${select} FROM ${table}`;
303
- const params = [];
304
- if (options?.where) {
305
- const where = buildWhereClause(options.where);
306
- if (where.sql) {
307
- sql += ` WHERE ${where.sql}`;
308
- params.push(...where.params);
309
- }
310
- }
311
- if (!countOnly) {
312
- if (options?.orderBy && options.orderBy.length > 0) {
313
- const orderParts = options.orderBy.map((o) => `${o.field} ${o.direction}`);
314
- sql += ` ORDER BY ${orderParts.join(", ")}`;
315
- }
316
- if (options?.limit !== undefined) {
317
- sql += ` LIMIT ?`;
318
- params.push(options.limit);
319
- }
320
- if (options?.offset !== undefined) {
321
- sql += ` OFFSET ?`;
322
- params.push(options.offset);
323
- }
324
- }
325
- return { sql, params };
326
- }
327
- async list(table, options) {
328
- if (options?.lang && this.schema?.tables[table]) {
329
- return this.listWithLang(table, options);
330
- }
331
- const { sql, params } = this.buildSelectQuery(table, options);
332
- const rows = this.runQuery(sql, params);
333
- return this.deserializeResults(table, rows);
334
- }
335
- async listWithLang(table, options) {
336
- const tableSchema = this.schema?.tables[table];
337
- if (!tableSchema) {
338
- const { sql: sql2, params: params2 } = this.buildSelectQuery(table, options);
339
- return this.deserializeResults(table, this.runQuery(sql2, params2));
340
- }
341
- const translatableFields = getTranslatableFields(tableSchema);
342
- if (translatableFields.length === 0) {
343
- const { sql: sql2, params: params2 } = this.buildSelectQuery(table, options);
344
- return this.deserializeResults(table, this.runQuery(sql2, params2));
345
- }
346
- const { sql, params } = buildTranslationQuery({
347
- table,
348
- schema: this.schema,
349
- lang: options.lang,
350
- fallbackLang: options.fallbackLang ?? this.defaultLang,
351
- where: options.where,
352
- orderBy: options.orderBy,
353
- limit: options.limit,
354
- offset: options.offset
355
- });
356
- return this.deserializeResults(table, this.runQuery(sql, params));
357
- }
358
- async get(table, id, options) {
359
- if (options?.lang && this.schema?.tables[table]) {
360
- return this.getWithLang(table, id, options);
361
- }
362
- const results = await this.list(table, { where: { id }, limit: 1 });
363
- return results[0] ?? null;
364
- }
365
- async findOne(table, options) {
366
- const results = await this.list(table, { ...options, limit: 1 });
367
- return results[0] ?? null;
368
- }
369
- async getWithLang(table, id, options) {
370
- const tableSchema = this.schema?.tables[table];
371
- if (!tableSchema) {
372
- const results = await this.list(table, { where: { id }, limit: 1 });
373
- return results[0] ?? null;
374
- }
375
- const translatableFields = getTranslatableFields(tableSchema);
376
- if (translatableFields.length === 0) {
377
- const results = await this.list(table, { where: { id }, limit: 1 });
378
- return results[0] ?? null;
379
- }
380
- const { sql, params } = buildTranslationQueryById(table, this.schema, id, options.lang, options.fallbackLang ?? this.defaultLang);
381
- const rows = this.runQuery(sql, params);
382
- const deserialized = this.deserializeResults(table, rows);
383
- return deserialized[0] ?? null;
384
- }
385
- async count(table, options) {
386
- const { sql, params } = this.buildSelectQuery(table, options, true);
387
- const result = this.runQuery(sql, params);
388
- return result[0]?.count ?? 0;
389
- }
390
- async paginate(table, page, limit, options) {
391
- const total = await this.count(table, options);
392
- const totalPages = Math.ceil(total / limit);
393
- const offset = (page - 1) * limit;
394
- const data = await this.list(table, {
395
- ...options,
396
- limit,
397
- offset
398
- });
399
- return {
400
- data,
401
- page,
402
- limit,
403
- total,
404
- totalPages,
405
- hasMore: page < totalPages
406
- };
407
- }
408
- async create(table, data) {
409
- const db = this.getDb();
410
- const tableSchema = this.schema?.tables[table];
411
- const serializedData = this.serializeData(table, data);
412
- if (tableSchema) {
413
- const { mainData, translatableData } = extractTranslatableData(serializedData, tableSchema);
414
- const columns2 = Object.keys(mainData);
415
- const values2 = Object.values(mainData);
416
- const placeholders2 = columns2.map(() => "?").join(", ");
417
- const sql2 = `INSERT INTO ${table} (${columns2.join(", ")}) VALUES (${placeholders2})`;
418
- db.run(sql2, values2);
419
- const lastId2 = db.exec("SELECT last_insert_rowid() as id")[0].values[0][0];
420
- this.persist();
421
- if (Object.keys(translatableData).length > 0 && this.defaultLang) {
422
- await this.upsertTranslation(table, lastId2, this.defaultLang, translatableData);
423
- }
424
- const result2 = await this.get(table, lastId2);
425
- return result2;
426
- }
427
- const columns = Object.keys(serializedData);
428
- const values = Object.values(serializedData);
429
- const placeholders = columns.map(() => "?").join(", ");
430
- const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
431
- db.run(sql, values);
432
- const lastId = db.exec("SELECT last_insert_rowid() as id")[0].values[0][0];
433
- this.persist();
434
- const result = await this.get(table, lastId);
435
- return result;
436
- }
437
- async createWithTranslations(table, data, translations) {
438
- const db = this.getDb();
439
- const tableSchema = this.schema?.tables[table];
440
- const serializedData = this.serializeData(table, data);
441
- let mainData = serializedData;
442
- let translatableData = {};
443
- if (tableSchema) {
444
- const extracted = extractTranslatableData(serializedData, tableSchema);
445
- mainData = extracted.mainData;
446
- translatableData = extracted.translatableData;
447
- }
448
- const columns = Object.keys(mainData);
449
- const values = Object.values(mainData);
450
- const placeholders = columns.map(() => "?").join(", ");
451
- const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
452
- db.run(sql, values);
453
- const lastId = db.exec("SELECT last_insert_rowid() as id")[0].values[0][0];
454
- if (translations) {
455
- for (const [lang, langData] of Object.entries(translations)) {
456
- await this.upsertTranslation(table, lastId, lang, langData);
457
- }
458
- } else if (Object.keys(translatableData).length > 0 && this.defaultLang) {
459
- await this.upsertTranslation(table, lastId, this.defaultLang, translatableData);
460
- }
461
- this.persist();
462
- const result = await this.get(table, lastId);
463
- return result;
464
- }
465
- async update(table, id, data) {
466
- const db = this.getDb();
467
- const tableSchema = this.schema?.tables[table];
468
- const serializedData = this.serializeData(table, data);
469
- if (tableSchema) {
470
- const { mainData, translatableData } = extractTranslatableData(serializedData, tableSchema);
471
- if (Object.keys(mainData).length > 0) {
472
- const columns2 = Object.keys(mainData);
473
- const values2 = Object.values(mainData);
474
- const setParts2 = columns2.map((col) => `${col} = ?`).join(", ");
475
- const sql2 = `UPDATE ${table} SET ${setParts2} WHERE id = ?`;
476
- db.run(sql2, [...values2, id]);
477
- }
478
- if (Object.keys(translatableData).length > 0 && this.defaultLang) {
479
- await this.upsertTranslation(table, id, this.defaultLang, translatableData);
480
- }
481
- this.persist();
482
- const result2 = await this.get(table, id);
483
- return result2;
484
- }
485
- const columns = Object.keys(serializedData);
486
- const values = Object.values(serializedData);
487
- const setParts = columns.map((col) => `${col} = ?`).join(", ");
488
- const sql = `UPDATE ${table} SET ${setParts} WHERE id = ?`;
489
- db.run(sql, [...values, id]);
490
- this.persist();
491
- const result = await this.get(table, id);
492
- return result;
493
- }
494
- async upsertTranslation(table, id, lang, data) {
495
- const db = this.getDb();
496
- if (!this.schema) {
497
- throw new Error(`No schema found for table: ${table}`);
498
- }
499
- const { sql, params } = buildTranslationUpsert(table, this.schema, id, lang, data);
500
- db.run(sql, params);
501
- this.persist();
502
- }
503
- async getTranslations(table, id) {
504
- const tableSchema = this.schema?.tables[table];
505
- if (!tableSchema) {
506
- return [];
507
- }
508
- const translationTable = toTranslationTableName(table);
509
- const fkName = toTranslationFKName(table);
510
- const sql = `SELECT * FROM ${translationTable} WHERE ${fkName} = ?`;
511
- return this.runQuery(sql, [id]);
512
- }
513
- async delete(table, id) {
514
- const db = this.getDb();
515
- if (this.schema?.tables[table]) {
516
- const translationTable = toTranslationTableName(table);
517
- const fkName = toTranslationFKName(table);
518
- try {
519
- db.run(`DELETE FROM ${translationTable} WHERE ${fkName} = ?`, [id]);
520
- } catch {}
521
- }
522
- const sql = `DELETE FROM ${table} WHERE id = ?`;
523
- db.run(sql, [id]);
524
- this.persist();
525
- const changes = db.getRowsModified();
526
- return changes > 0;
527
- }
528
- async execute(query, params) {
529
- const db = this.getDb();
530
- db.run(query, params);
531
- this.persist();
532
- const changes = db.getRowsModified();
533
- const result = db.exec("SELECT last_insert_rowid() as id");
534
- const lastInsertRowid = result[0]?.values[0]?.[0] ?? 0;
535
- return { changes, lastInsertRowid };
536
- }
537
- async raw(query, params) {
538
- return this.runQuery(query, params ?? []);
539
- }
540
- async seed(data) {
541
- const db = this.getDb();
542
- for (const [table, records] of Object.entries(data)) {
543
- for (const record of records) {
544
- const serialized = this.serializeData(table, record);
545
- const columns = Object.keys(serialized);
546
- const values = Object.values(serialized);
547
- const placeholders = columns.map(() => "?").join(", ");
548
- const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
549
- db.run(sql, values);
550
- }
551
- }
552
- this.persist();
553
- }
554
- async getTables() {
555
- const result = this.runQuery("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'");
556
- return result.map((row) => row.name);
557
- }
558
- async getTableSchema(table) {
559
- const db = this.getDb();
560
- const result = db.exec(`PRAGMA table_info(${table})`);
561
- if (result.length === 0 || result[0].values.length === 0) {
562
- return [];
563
- }
564
- return result[0].values.map((row) => ({
565
- name: String(row[1]),
566
- type: String(row[2]),
567
- notnull: Number(row[3]),
568
- pk: Number(row[5])
569
- }));
570
- }
571
- close() {
572
- if (this.db) {
573
- this.persist();
574
- this.db.close();
575
- this.db = null;
576
- }
577
- }
578
- async beginTransaction() {
579
- await this.execute("BEGIN TRANSACTION");
580
- }
581
- async commit() {
582
- await this.execute("COMMIT");
583
- this.persist();
584
- }
585
- async rollback() {
586
- await this.execute("ROLLBACK");
587
- }
588
- async createMany(table, records, options) {
589
- const db = this.getDb();
590
- const ids = [];
591
- const tableSchema = this.schema?.tables[table];
592
- for (const record of records) {
593
- const serializedRecord = this.serializeData(table, record);
594
- let mainData = serializedRecord;
595
- let translatableData = {};
596
- if (tableSchema) {
597
- const extracted = extractTranslatableData(serializedRecord, tableSchema);
598
- mainData = extracted.mainData;
599
- translatableData = extracted.translatableData;
600
- }
601
- const columns = Object.keys(mainData);
602
- const values = Object.values(mainData);
603
- const placeholders = columns.map(() => "?").join(", ");
604
- const insertType = options?.ignore ? "INSERT OR IGNORE" : "INSERT";
605
- const sql = `${insertType} INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
606
- db.run(sql, values);
607
- const lastId = db.exec("SELECT last_insert_rowid() as id")[0].values[0][0];
608
- if (db.getRowsModified() > 0) {
609
- ids.push(lastId);
610
- if (Object.keys(translatableData).length > 0 && this.defaultLang) {
611
- await this.upsertTranslation(table, lastId, this.defaultLang, translatableData);
612
- }
613
- }
614
- }
615
- this.persist();
616
- return { created: ids.length, ids };
617
- }
618
- async updateMany(table, updates) {
619
- let updated = 0;
620
- for (const { id, data } of updates) {
621
- await this.update(table, id, data);
622
- updated++;
623
- }
624
- return { updated };
625
- }
626
- async deleteMany(table, ids) {
627
- let deleted = 0;
628
- for (const id of ids) {
629
- const success = await this.delete(table, id);
630
- if (success)
631
- deleted++;
632
- }
633
- return { deleted };
634
- }
635
- export() {
636
- return this.getDb().export();
637
- }
638
- async import(data) {
639
- if (!this.SQL) {
640
- throw new Error("Database not connected. Call connect() first.");
641
- }
642
- this.db?.close();
643
- this.db = new this.SQL.Database(data);
644
- this.persist();
645
- }
646
- clear() {
647
- if (!this.SQL) {
648
- throw new Error("Database not connected. Call connect() first.");
649
- }
650
- this.db?.close();
651
- this.db = new this.SQL.Database;
652
- this.persist();
653
- }
654
- }
655
- export {
656
- validatePopulate,
657
- useDbUpdate,
658
- useDbList,
659
- useDbLang,
660
- useDbGet,
661
- useDbDelete,
662
- useDbCreate,
663
- useDb,
664
- useAdapter,
665
- resolvePopulate2 as resolvePopulate,
666
- parseJSONSchema,
667
- getPopulatableFields,
668
- f,
669
- defineSchema,
670
- buildWhereClause2 as buildWhereClause,
671
- SqliteAdapter,
672
- ORM,
673
- DbProvider
674
- };
1
+ /**
2
+ * @promakeai/dbreact
3
+ *
4
+ * React client for schema-driven multi-language database.
5
+ * Works with SQL.js (browser SQLite) or REST API backends.
6
+ */
7
+ // Re-export ORM core for browser usage
8
+ export { ORM, defineSchema, f, buildWhereClause, resolvePopulate, getPopulatableFields, validatePopulate, parseJSONSchema, } from "@promakeai/orm";
9
+ // Provider
10
+ export { DbProvider, useDb, useAdapter, useDbLang, } from "./providers/DbProvider";
11
+ // Generic Hooks
12
+ export { useDbList, useDbGet, useDbCreate, useDbUpdate, useDbDelete, } from "./hooks/useDbHooks";
13
+ // Adapters
14
+ export { SqliteAdapter } from "./adapters/SqliteAdapter";
@@ -42,7 +42,7 @@ const defaultQueryClient = new QueryClient({
42
42
  * }
43
43
  * ```
44
44
  */
45
- export function DbProvider({ adapter, lang: langProp = "en", fallbackLang = "en", autoConnect = true, queryClient = defaultQueryClient, children, }) {
45
+ export function DbProvider({ adapter, schema, lang: langProp = "en", fallbackLang = "en", autoConnect = true, queryClient = defaultQueryClient, children, }) {
46
46
  const [isConnected, setIsConnected] = useState(false);
47
47
  const [error, setError] = useState(null);
48
48
  const [lang, setLangState] = useState(langProp);
@@ -94,9 +94,10 @@ export function DbProvider({ adapter, lang: langProp = "en", fallbackLang = "en"
94
94
  // Database context value
95
95
  const dbContextValue = useMemo(() => ({
96
96
  adapter,
97
+ schema,
97
98
  isConnected,
98
99
  error,
99
- }), [adapter, isConnected, error]);
100
+ }), [adapter, schema, isConnected, error]);
100
101
  // Language context value
101
102
  const langContextValue = useMemo(() => ({
102
103
  lang,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promakeai/dbreact",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "React client for schema-driven multi-language database",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -14,10 +14,12 @@
14
14
  }
15
15
  },
16
16
  "scripts": {
17
- "build": "bun build index.ts --outdir dist --target browser --packages=external && bun run build:types",
17
+ "build:js": "tsc --project tsconfig.json --declaration false --declarationMap false --emitDeclarationOnly false --outDir dist",
18
+ "build": "bun run build:js && bun run build:types",
18
19
  "build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
19
20
  "dev": "bun build index.ts --outdir dist --target browser --packages=external --watch",
20
21
  "test": "bun test",
22
+ "test:build-runtime": "bun run build && bun test ./tests/build-runtime.test.ts",
21
23
  "typecheck": "tsc --noEmit",
22
24
  "release": "bun run build && npm publish --access public"
23
25
  },
@@ -40,7 +42,7 @@
40
42
  "sql.js": ">=1.11.0"
41
43
  },
42
44
  "dependencies": {
43
- "@promakeai/orm": "1.0.3"
45
+ "@promakeai/orm": "1.0.5"
44
46
  },
45
47
  "devDependencies": {
46
48
  "@tanstack/query-core": "5.90.20",