@promakeai/dbreact 1.0.4 → 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
@@ -622,10 +622,14 @@ await adapter.create('products', newProduct);
622
622
 
623
623
  ---
624
624
 
625
- ## Troubleshooting
626
-
627
- **"useDbLang must be used within a DbProvider"**
628
- - 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
629
633
 
630
634
  **Queries return empty results**
631
635
  - Check adapter is connected: `const { isConnected } = useDb()`
@@ -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,697 +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
- tableColumnsCache = new Map;
228
- config;
229
- schema;
230
- defaultLang;
231
- constructor(config = {}) {
232
- this.config = {
233
- storageKey: config.storageKey ?? "dbreact_db",
234
- wasmPath: config.wasmPath ?? "https://sql.js.org/dist/sql-wasm.wasm",
235
- initialData: config.initialData ?? new Uint8Array,
236
- schema: config.schema,
237
- defaultLang: config.defaultLang
238
- };
239
- this.schema = config.schema;
240
- this.defaultLang = config.defaultLang;
241
- }
242
- setSchema(schema) {
243
- this.schema = schema;
244
- this.config.schema = schema;
245
- this.tableColumnsCache.clear();
246
- }
247
- async connect() {
248
- this.SQL = await initSqlJs({
249
- locateFile: (file) => file === "sql-wasm.wasm" ? this.config.wasmPath : file
250
- });
251
- const saved = localStorage.getItem(this.config.storageKey);
252
- if (saved) {
253
- const data = Uint8Array.from(atob(saved), (c) => c.charCodeAt(0));
254
- this.db = new this.SQL.Database(data);
255
- } else if (this.config.initialData.length > 0) {
256
- this.db = new this.SQL.Database(this.config.initialData);
257
- this.persist();
258
- } else {
259
- this.db = new this.SQL.Database;
260
- }
261
- }
262
- persist() {
263
- if (!this.db)
264
- return;
265
- const data = this.db.export();
266
- const base64 = btoa(String.fromCharCode(...data));
267
- localStorage.setItem(this.config.storageKey, base64);
268
- }
269
- getDb() {
270
- if (!this.db) {
271
- throw new Error("Database not connected. Call connect() first.");
272
- }
273
- return this.db;
274
- }
275
- runQuery(sql, params = []) {
276
- const db = this.getDb();
277
- const result = db.exec(sql, params);
278
- if (result.length === 0 || result[0].values.length === 0) {
279
- return [];
280
- }
281
- const columns = result[0].columns;
282
- return result[0].values.map((row) => {
283
- const obj = {};
284
- columns.forEach((col, i) => {
285
- obj[col] = row[i];
286
- });
287
- return obj;
288
- });
289
- }
290
- deserializeResults(table, rows) {
291
- const tableSchema = this.schema?.tables[table];
292
- if (!tableSchema || rows.length === 0)
293
- return rows;
294
- return rows.map((row) => deserializeRow(row, tableSchema.fields));
295
- }
296
- serializeData(table, data) {
297
- const tableSchema = this.schema?.tables[table];
298
- if (!tableSchema)
299
- return data;
300
- return serializeRow(data, tableSchema.fields);
301
- }
302
- buildSelectQuery(table, options, countOnly = false) {
303
- const select = countOnly ? "COUNT(*) as count" : "*";
304
- let sql = `SELECT ${select} FROM ${table}`;
305
- const params = [];
306
- if (options?.where) {
307
- const where = buildWhereClause(options.where);
308
- if (where.sql) {
309
- sql += ` WHERE ${where.sql}`;
310
- params.push(...where.params);
311
- }
312
- }
313
- if (!countOnly) {
314
- if (options?.orderBy && options.orderBy.length > 0) {
315
- const orderParts = options.orderBy.map((o) => `${o.field} ${o.direction}`);
316
- sql += ` ORDER BY ${orderParts.join(", ")}`;
317
- }
318
- if (options?.limit !== undefined) {
319
- sql += ` LIMIT ?`;
320
- params.push(options.limit);
321
- }
322
- if (options?.offset !== undefined) {
323
- sql += ` OFFSET ?`;
324
- params.push(options.offset);
325
- }
326
- }
327
- return { sql, params };
328
- }
329
- async list(table, options) {
330
- if (options?.lang && this.schema?.tables[table]) {
331
- return this.listWithLang(table, options);
332
- }
333
- const { sql, params } = this.buildSelectQuery(table, options);
334
- const rows = this.runQuery(sql, params);
335
- return this.deserializeResults(table, rows);
336
- }
337
- async listWithLang(table, options) {
338
- const tableSchema = this.schema?.tables[table];
339
- if (!tableSchema) {
340
- const { sql: sql2, params: params2 } = this.buildSelectQuery(table, options);
341
- return this.deserializeResults(table, this.runQuery(sql2, params2));
342
- }
343
- const translatableFields = getTranslatableFields(tableSchema);
344
- if (translatableFields.length === 0) {
345
- const { sql: sql2, params: params2 } = this.buildSelectQuery(table, options);
346
- return this.deserializeResults(table, this.runQuery(sql2, params2));
347
- }
348
- const { sql, params } = buildTranslationQuery({
349
- table,
350
- schema: this.schema,
351
- lang: options.lang,
352
- fallbackLang: options.fallbackLang ?? this.defaultLang,
353
- mainFallbackFields: await this.getAvailableMainFallbackFields(table),
354
- where: options.where,
355
- orderBy: options.orderBy,
356
- limit: options.limit,
357
- offset: options.offset
358
- });
359
- return this.deserializeResults(table, this.runQuery(sql, params));
360
- }
361
- async get(table, id, options) {
362
- if (options?.lang && this.schema?.tables[table]) {
363
- return this.getWithLang(table, id, options);
364
- }
365
- const results = await this.list(table, { where: { id }, limit: 1 });
366
- return results[0] ?? null;
367
- }
368
- async findOne(table, options) {
369
- const results = await this.list(table, { ...options, limit: 1 });
370
- return results[0] ?? null;
371
- }
372
- async getWithLang(table, id, options) {
373
- const tableSchema = this.schema?.tables[table];
374
- if (!tableSchema) {
375
- const results = await this.list(table, { where: { id }, limit: 1 });
376
- return results[0] ?? null;
377
- }
378
- const translatableFields = getTranslatableFields(tableSchema);
379
- if (translatableFields.length === 0) {
380
- const results = await this.list(table, { where: { id }, limit: 1 });
381
- return results[0] ?? null;
382
- }
383
- const { sql, params } = buildTranslationQueryById(table, this.schema, id, options.lang, options.fallbackLang ?? this.defaultLang, await this.getAvailableMainFallbackFields(table));
384
- const rows = this.runQuery(sql, params);
385
- const deserialized = this.deserializeResults(table, rows);
386
- return deserialized[0] ?? null;
387
- }
388
- async count(table, options) {
389
- const { sql, params } = this.buildSelectQuery(table, options, true);
390
- const result = this.runQuery(sql, params);
391
- return result[0]?.count ?? 0;
392
- }
393
- async paginate(table, page, limit, options) {
394
- const total = await this.count(table, options);
395
- const totalPages = Math.ceil(total / limit);
396
- const offset = (page - 1) * limit;
397
- const data = await this.list(table, {
398
- ...options,
399
- limit,
400
- offset
401
- });
402
- return {
403
- data,
404
- page,
405
- limit,
406
- total,
407
- totalPages,
408
- hasMore: page < totalPages
409
- };
410
- }
411
- async create(table, data) {
412
- const db = this.getDb();
413
- const tableSchema = this.schema?.tables[table];
414
- const serializedData = this.serializeData(table, data);
415
- if (tableSchema) {
416
- const { mainData, translatableData } = extractTranslatableData(serializedData, tableSchema);
417
- const columns2 = Object.keys(mainData);
418
- const values2 = Object.values(mainData);
419
- const placeholders2 = columns2.map(() => "?").join(", ");
420
- const sql2 = `INSERT INTO ${table} (${columns2.join(", ")}) VALUES (${placeholders2})`;
421
- db.run(sql2, values2);
422
- const lastId2 = db.exec("SELECT last_insert_rowid() as id")[0].values[0][0];
423
- this.persist();
424
- if (Object.keys(translatableData).length > 0 && this.defaultLang) {
425
- await this.upsertTranslation(table, lastId2, this.defaultLang, translatableData);
426
- }
427
- const result2 = await this.get(table, lastId2);
428
- return result2;
429
- }
430
- const columns = Object.keys(serializedData);
431
- const values = Object.values(serializedData);
432
- const placeholders = columns.map(() => "?").join(", ");
433
- const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
434
- db.run(sql, values);
435
- const lastId = db.exec("SELECT last_insert_rowid() as id")[0].values[0][0];
436
- this.persist();
437
- const result = await this.get(table, lastId);
438
- return result;
439
- }
440
- async createWithTranslations(table, data, translations) {
441
- const db = this.getDb();
442
- const tableSchema = this.schema?.tables[table];
443
- const serializedData = this.serializeData(table, data);
444
- let mainData = serializedData;
445
- let translatableData = {};
446
- if (tableSchema) {
447
- const extracted = extractTranslatableData(serializedData, tableSchema);
448
- mainData = extracted.mainData;
449
- translatableData = extracted.translatableData;
450
- }
451
- const columns = Object.keys(mainData);
452
- const values = Object.values(mainData);
453
- const placeholders = columns.map(() => "?").join(", ");
454
- const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
455
- db.run(sql, values);
456
- const lastId = db.exec("SELECT last_insert_rowid() as id")[0].values[0][0];
457
- if (translations) {
458
- for (const [lang, langData] of Object.entries(translations)) {
459
- await this.upsertTranslation(table, lastId, lang, langData);
460
- }
461
- } else if (Object.keys(translatableData).length > 0 && this.defaultLang) {
462
- await this.upsertTranslation(table, lastId, this.defaultLang, translatableData);
463
- }
464
- this.persist();
465
- const result = await this.get(table, lastId);
466
- return result;
467
- }
468
- async update(table, id, data) {
469
- const db = this.getDb();
470
- const tableSchema = this.schema?.tables[table];
471
- const serializedData = this.serializeData(table, data);
472
- if (tableSchema) {
473
- const { mainData, translatableData } = extractTranslatableData(serializedData, tableSchema);
474
- if (Object.keys(mainData).length > 0) {
475
- const columns2 = Object.keys(mainData);
476
- const values2 = Object.values(mainData);
477
- const setParts2 = columns2.map((col) => `${col} = ?`).join(", ");
478
- const sql2 = `UPDATE ${table} SET ${setParts2} WHERE id = ?`;
479
- db.run(sql2, [...values2, id]);
480
- }
481
- if (Object.keys(translatableData).length > 0 && this.defaultLang) {
482
- await this.upsertTranslation(table, id, this.defaultLang, translatableData);
483
- }
484
- this.persist();
485
- const result2 = await this.get(table, id);
486
- return result2;
487
- }
488
- const columns = Object.keys(serializedData);
489
- const values = Object.values(serializedData);
490
- const setParts = columns.map((col) => `${col} = ?`).join(", ");
491
- const sql = `UPDATE ${table} SET ${setParts} WHERE id = ?`;
492
- db.run(sql, [...values, id]);
493
- this.persist();
494
- const result = await this.get(table, id);
495
- return result;
496
- }
497
- async upsertTranslation(table, id, lang, data) {
498
- const db = this.getDb();
499
- if (!this.schema) {
500
- throw new Error(`No schema found for table: ${table}`);
501
- }
502
- const { sql, params } = buildTranslationUpsert(table, this.schema, id, lang, data);
503
- db.run(sql, params);
504
- this.persist();
505
- }
506
- async getTranslations(table, id) {
507
- const tableSchema = this.schema?.tables[table];
508
- if (!tableSchema) {
509
- return [];
510
- }
511
- const translationTable = toTranslationTableName(table);
512
- const fkName = toTranslationFKName(table);
513
- const sql = `SELECT * FROM ${translationTable} WHERE ${fkName} = ?`;
514
- return this.runQuery(sql, [id]);
515
- }
516
- async delete(table, id) {
517
- const db = this.getDb();
518
- if (this.schema?.tables[table]) {
519
- const translationTable = toTranslationTableName(table);
520
- const fkName = toTranslationFKName(table);
521
- try {
522
- db.run(`DELETE FROM ${translationTable} WHERE ${fkName} = ?`, [id]);
523
- } catch {}
524
- }
525
- const sql = `DELETE FROM ${table} WHERE id = ?`;
526
- db.run(sql, [id]);
527
- this.persist();
528
- const changes = db.getRowsModified();
529
- return changes > 0;
530
- }
531
- async execute(query, params) {
532
- const db = this.getDb();
533
- db.run(query, params);
534
- this.persist();
535
- const changes = db.getRowsModified();
536
- const result = db.exec("SELECT last_insert_rowid() as id");
537
- const lastInsertRowid = result[0]?.values[0]?.[0] ?? 0;
538
- return { changes, lastInsertRowid };
539
- }
540
- async raw(query, params) {
541
- return this.runQuery(query, params ?? []);
542
- }
543
- async seed(data) {
544
- const db = this.getDb();
545
- for (const [table, records] of Object.entries(data)) {
546
- for (const record of records) {
547
- const serialized = this.serializeData(table, record);
548
- const columns = Object.keys(serialized);
549
- const values = Object.values(serialized);
550
- const placeholders = columns.map(() => "?").join(", ");
551
- const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
552
- db.run(sql, values);
553
- }
554
- }
555
- this.persist();
556
- }
557
- async getTables() {
558
- const result = this.runQuery("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'");
559
- return result.map((row) => row.name);
560
- }
561
- async getTableSchema(table) {
562
- const db = this.getDb();
563
- const result = db.exec(`PRAGMA table_info(${table})`);
564
- if (result.length === 0 || result[0].values.length === 0) {
565
- return [];
566
- }
567
- return result[0].values.map((row) => ({
568
- name: String(row[1]),
569
- type: String(row[2]),
570
- notnull: Number(row[3]),
571
- pk: Number(row[5])
572
- }));
573
- }
574
- close() {
575
- if (this.db) {
576
- this.persist();
577
- this.db.close();
578
- this.db = null;
579
- this.tableColumnsCache.clear();
580
- }
581
- }
582
- async beginTransaction() {
583
- await this.execute("BEGIN TRANSACTION");
584
- }
585
- async commit() {
586
- await this.execute("COMMIT");
587
- this.persist();
588
- }
589
- async rollback() {
590
- await this.execute("ROLLBACK");
591
- }
592
- async createMany(table, records, options) {
593
- const db = this.getDb();
594
- const ids = [];
595
- const tableSchema = this.schema?.tables[table];
596
- for (const record of records) {
597
- const serializedRecord = this.serializeData(table, record);
598
- let mainData = serializedRecord;
599
- let translatableData = {};
600
- if (tableSchema) {
601
- const extracted = extractTranslatableData(serializedRecord, tableSchema);
602
- mainData = extracted.mainData;
603
- translatableData = extracted.translatableData;
604
- }
605
- const columns = Object.keys(mainData);
606
- const values = Object.values(mainData);
607
- const placeholders = columns.map(() => "?").join(", ");
608
- const insertType = options?.ignore ? "INSERT OR IGNORE" : "INSERT";
609
- const sql = `${insertType} INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
610
- db.run(sql, values);
611
- const lastId = db.exec("SELECT last_insert_rowid() as id")[0].values[0][0];
612
- if (db.getRowsModified() > 0) {
613
- ids.push(lastId);
614
- if (Object.keys(translatableData).length > 0 && this.defaultLang) {
615
- await this.upsertTranslation(table, lastId, this.defaultLang, translatableData);
616
- }
617
- }
618
- }
619
- this.persist();
620
- return { created: ids.length, ids };
621
- }
622
- async getAvailableMainFallbackFields(table) {
623
- const tableSchema = this.schema?.tables[table];
624
- if (!tableSchema)
625
- return [];
626
- const translatableFields = getTranslatableFields(tableSchema);
627
- if (translatableFields.length === 0)
628
- return [];
629
- const tableColumns = await this.getTableColumns(table);
630
- return translatableFields.filter((field) => tableColumns.has(field));
631
- }
632
- async getTableColumns(table) {
633
- const cached = this.tableColumnsCache.get(table);
634
- if (cached)
635
- return cached;
636
- const schema = await this.getTableSchema(table);
637
- const columnSet = new Set(schema.map((col) => col.name));
638
- this.tableColumnsCache.set(table, columnSet);
639
- return columnSet;
640
- }
641
- async updateMany(table, updates) {
642
- let updated = 0;
643
- for (const { id, data } of updates) {
644
- await this.update(table, id, data);
645
- updated++;
646
- }
647
- return { updated };
648
- }
649
- async deleteMany(table, ids) {
650
- let deleted = 0;
651
- for (const id of ids) {
652
- const success = await this.delete(table, id);
653
- if (success)
654
- deleted++;
655
- }
656
- return { deleted };
657
- }
658
- export() {
659
- return this.getDb().export();
660
- }
661
- async import(data) {
662
- if (!this.SQL) {
663
- throw new Error("Database not connected. Call connect() first.");
664
- }
665
- this.db?.close();
666
- this.db = new this.SQL.Database(data);
667
- this.persist();
668
- }
669
- clear() {
670
- if (!this.SQL) {
671
- throw new Error("Database not connected. Call connect() first.");
672
- }
673
- this.db?.close();
674
- this.db = new this.SQL.Database;
675
- this.persist();
676
- }
677
- }
678
- export {
679
- validatePopulate,
680
- useDbUpdate,
681
- useDbList,
682
- useDbLang,
683
- useDbGet,
684
- useDbDelete,
685
- useDbCreate,
686
- useDb,
687
- useAdapter,
688
- resolvePopulate2 as resolvePopulate,
689
- parseJSONSchema,
690
- getPopulatableFields,
691
- f,
692
- defineSchema,
693
- buildWhereClause2 as buildWhereClause,
694
- SqliteAdapter,
695
- ORM,
696
- DbProvider
697
- };
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.4",
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.4"
45
+ "@promakeai/orm": "1.0.5"
44
46
  },
45
47
  "devDependencies": {
46
48
  "@tanstack/query-core": "5.90.20",