@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 +130 -110
- package/dist/adapters/SqliteAdapter.d.ts +3 -0
- package/dist/adapters/SqliteAdapter.d.ts.map +1 -1
- package/dist/adapters/SqliteAdapter.js +24 -1
- package/dist/hooks/useDbHooks.js +33 -9
- package/dist/index.js +14 -674
- package/dist/providers/DbProvider.js +3 -2
- package/package.json +5 -3
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** -
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
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` | `
|
|
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'
|
|
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'
|
|
201
|
-
|
|
202
|
-
deleteProduct.mutate(
|
|
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?:
|
|
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
|
|
393
|
-
|
|
394
|
-
```bash
|
|
395
|
-
dbcli generate --schema ./schema.
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
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'
|
|
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({
|
|
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 {
|
|
587
|
+
import type { DbProduct, DbProductInput } from './db/types';
|
|
572
588
|
|
|
573
589
|
// Type-safe queries
|
|
574
|
-
const products:
|
|
590
|
+
const products: DbProduct[] = await adapter.list('products');
|
|
575
591
|
|
|
576
592
|
// Type-safe creates
|
|
577
|
-
const newProduct:
|
|
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
|
-
**
|
|
612
|
-
-
|
|
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;
|
|
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) {
|
package/dist/hooks/useDbHooks.js
CHANGED
|
@@ -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",
|
|
46
|
-
queryFn: () =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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: () =>
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
|
|
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
|
+
"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": "
|
|
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.
|
|
45
|
+
"@promakeai/orm": "1.0.5"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
48
|
"@tanstack/query-core": "5.90.20",
|