@promakeai/dbreact 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +633 -0
- package/SKILL.md +311 -0
- package/dist/adapters/RestAdapter.d.ts.map +1 -0
- package/dist/adapters/SqliteAdapter.d.ts.map +1 -0
- package/dist/adapters/SqliteAdapter.js +543 -0
- package/dist/core/DataManager.d.ts.map +1 -0
- package/dist/hooks/useDataManager.d.ts.map +1 -0
- package/dist/hooks/useDbHooks.d.ts.map +1 -0
- package/dist/hooks/useDbHooks.js +157 -0
- package/dist/hooks/useDbLang.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +648 -0
- package/dist/providers/DbProvider.d.ts.map +1 -0
- package/dist/providers/DbProvider.js +134 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/utils/whereBuilder.d.ts.map +1 -0
- package/package.json +54 -0
- package/providers/DbProvider.tsx +191 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser SQLite Adapter
|
|
3
|
+
*
|
|
4
|
+
* Uses sql.js (WASM) for in-browser SQLite database.
|
|
5
|
+
* Persists to localStorage for offline support.
|
|
6
|
+
*/
|
|
7
|
+
import initSqlJs from "sql.js";
|
|
8
|
+
import { buildWhereClause, buildTranslationQuery, buildTranslationQueryById, buildTranslationUpsert, extractTranslatableData, getTranslatableFields, toTranslationTableName, toTranslationFKName, deserializeRow, serializeRow, } from "@promakeai/orm";
|
|
9
|
+
/**
|
|
10
|
+
* Browser SQLite Adapter
|
|
11
|
+
*
|
|
12
|
+
* Provides SQLite database in the browser using sql.js WASM.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const adapter = new SqliteAdapter({ storageKey: 'myapp' });
|
|
17
|
+
* await adapter.connect();
|
|
18
|
+
* const users = await adapter.list('users');
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export class SqliteAdapter {
|
|
22
|
+
db = null;
|
|
23
|
+
SQL = null;
|
|
24
|
+
config;
|
|
25
|
+
schema;
|
|
26
|
+
defaultLang;
|
|
27
|
+
constructor(config = {}) {
|
|
28
|
+
this.config = {
|
|
29
|
+
storageKey: config.storageKey ?? "dbreact_db",
|
|
30
|
+
wasmPath: config.wasmPath ??
|
|
31
|
+
"https://sql.js.org/dist/sql-wasm.wasm",
|
|
32
|
+
initialData: config.initialData ?? new Uint8Array(),
|
|
33
|
+
schema: config.schema,
|
|
34
|
+
defaultLang: config.defaultLang,
|
|
35
|
+
};
|
|
36
|
+
this.schema = config.schema;
|
|
37
|
+
this.defaultLang = config.defaultLang;
|
|
38
|
+
}
|
|
39
|
+
setSchema(schema) {
|
|
40
|
+
this.schema = schema;
|
|
41
|
+
this.config.schema = schema;
|
|
42
|
+
}
|
|
43
|
+
async connect() {
|
|
44
|
+
// Initialize sql.js
|
|
45
|
+
this.SQL = await initSqlJs({
|
|
46
|
+
locateFile: (file) => file === "sql-wasm.wasm" ? this.config.wasmPath : file,
|
|
47
|
+
});
|
|
48
|
+
// Try to load from localStorage
|
|
49
|
+
const saved = localStorage.getItem(this.config.storageKey);
|
|
50
|
+
if (saved) {
|
|
51
|
+
const data = Uint8Array.from(atob(saved), (c) => c.charCodeAt(0));
|
|
52
|
+
this.db = new this.SQL.Database(data);
|
|
53
|
+
}
|
|
54
|
+
else if (this.config.initialData.length > 0) {
|
|
55
|
+
// Use initial data
|
|
56
|
+
this.db = new this.SQL.Database(this.config.initialData);
|
|
57
|
+
this.persist();
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Create empty database
|
|
61
|
+
this.db = new this.SQL.Database();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
persist() {
|
|
65
|
+
if (!this.db)
|
|
66
|
+
return;
|
|
67
|
+
const data = this.db.export();
|
|
68
|
+
const base64 = btoa(String.fromCharCode(...data));
|
|
69
|
+
localStorage.setItem(this.config.storageKey, base64);
|
|
70
|
+
}
|
|
71
|
+
getDb() {
|
|
72
|
+
if (!this.db) {
|
|
73
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
74
|
+
}
|
|
75
|
+
return this.db;
|
|
76
|
+
}
|
|
77
|
+
runQuery(sql, params = []) {
|
|
78
|
+
const db = this.getDb();
|
|
79
|
+
const result = db.exec(sql, params);
|
|
80
|
+
if (result.length === 0 || result[0].values.length === 0) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const columns = result[0].columns;
|
|
84
|
+
return result[0].values.map((row) => {
|
|
85
|
+
const obj = {};
|
|
86
|
+
columns.forEach((col, i) => {
|
|
87
|
+
obj[col] = row[i];
|
|
88
|
+
});
|
|
89
|
+
return obj;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Deserialize rows using schema field definitions (bool 0/1 → boolean, json TEXT → parsed)
|
|
94
|
+
*/
|
|
95
|
+
deserializeResults(table, rows) {
|
|
96
|
+
const tableSchema = this.schema?.tables[table];
|
|
97
|
+
if (!tableSchema || rows.length === 0)
|
|
98
|
+
return rows;
|
|
99
|
+
return rows.map(row => deserializeRow(row, tableSchema.fields));
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Serialize data for DB storage (bool → 0/1, json → TEXT)
|
|
103
|
+
*/
|
|
104
|
+
serializeData(table, data) {
|
|
105
|
+
const tableSchema = this.schema?.tables[table];
|
|
106
|
+
if (!tableSchema)
|
|
107
|
+
return data;
|
|
108
|
+
return serializeRow(data, tableSchema.fields);
|
|
109
|
+
}
|
|
110
|
+
buildSelectQuery(table, options, countOnly = false) {
|
|
111
|
+
const select = countOnly ? "COUNT(*) as count" : "*";
|
|
112
|
+
let sql = `SELECT ${select} FROM ${table}`;
|
|
113
|
+
const params = [];
|
|
114
|
+
if (options?.where) {
|
|
115
|
+
const where = buildWhereClause(options.where);
|
|
116
|
+
if (where.sql) {
|
|
117
|
+
sql += ` WHERE ${where.sql}`;
|
|
118
|
+
params.push(...where.params);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!countOnly) {
|
|
122
|
+
if (options?.orderBy && options.orderBy.length > 0) {
|
|
123
|
+
const orderParts = options.orderBy.map((o) => `${o.field} ${o.direction}`);
|
|
124
|
+
sql += ` ORDER BY ${orderParts.join(", ")}`;
|
|
125
|
+
}
|
|
126
|
+
if (options?.limit !== undefined) {
|
|
127
|
+
sql += ` LIMIT ?`;
|
|
128
|
+
params.push(options.limit);
|
|
129
|
+
}
|
|
130
|
+
if (options?.offset !== undefined) {
|
|
131
|
+
sql += ` OFFSET ?`;
|
|
132
|
+
params.push(options.offset);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { sql, params };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* List records from a table
|
|
139
|
+
*/
|
|
140
|
+
async list(table, options) {
|
|
141
|
+
// If lang is specified and we have schema, use translation query
|
|
142
|
+
if (options?.lang && this.schema?.tables[table]) {
|
|
143
|
+
return this.listWithLang(table, options);
|
|
144
|
+
}
|
|
145
|
+
const { sql, params } = this.buildSelectQuery(table, options);
|
|
146
|
+
const rows = this.runQuery(sql, params);
|
|
147
|
+
return this.deserializeResults(table, rows);
|
|
148
|
+
}
|
|
149
|
+
async listWithLang(table, options) {
|
|
150
|
+
const tableSchema = this.schema?.tables[table];
|
|
151
|
+
if (!tableSchema) {
|
|
152
|
+
const { sql, params } = this.buildSelectQuery(table, options);
|
|
153
|
+
return this.deserializeResults(table, this.runQuery(sql, params));
|
|
154
|
+
}
|
|
155
|
+
const translatableFields = getTranslatableFields(tableSchema);
|
|
156
|
+
if (translatableFields.length === 0) {
|
|
157
|
+
const { sql, params } = this.buildSelectQuery(table, options);
|
|
158
|
+
return this.deserializeResults(table, this.runQuery(sql, params));
|
|
159
|
+
}
|
|
160
|
+
const { sql, params } = buildTranslationQuery({
|
|
161
|
+
table,
|
|
162
|
+
schema: this.schema,
|
|
163
|
+
lang: options.lang,
|
|
164
|
+
fallbackLang: options.fallbackLang ?? this.defaultLang,
|
|
165
|
+
where: options.where,
|
|
166
|
+
orderBy: options.orderBy,
|
|
167
|
+
limit: options.limit,
|
|
168
|
+
offset: options.offset,
|
|
169
|
+
});
|
|
170
|
+
return this.deserializeResults(table, this.runQuery(sql, params));
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Get a single record by ID
|
|
174
|
+
*/
|
|
175
|
+
async get(table, id, options) {
|
|
176
|
+
// If lang is specified and we have schema, use translation query
|
|
177
|
+
if (options?.lang && this.schema?.tables[table]) {
|
|
178
|
+
return this.getWithLang(table, id, options);
|
|
179
|
+
}
|
|
180
|
+
const results = await this.list(table, { where: { id }, limit: 1 });
|
|
181
|
+
return results[0] ?? null;
|
|
182
|
+
}
|
|
183
|
+
async findOne(table, options) {
|
|
184
|
+
const results = await this.list(table, { ...options, limit: 1 });
|
|
185
|
+
return results[0] ?? null;
|
|
186
|
+
}
|
|
187
|
+
async getWithLang(table, id, options) {
|
|
188
|
+
const tableSchema = this.schema?.tables[table];
|
|
189
|
+
if (!tableSchema) {
|
|
190
|
+
const results = await this.list(table, { where: { id }, limit: 1 });
|
|
191
|
+
return results[0] ?? null;
|
|
192
|
+
}
|
|
193
|
+
const translatableFields = getTranslatableFields(tableSchema);
|
|
194
|
+
if (translatableFields.length === 0) {
|
|
195
|
+
const results = await this.list(table, { where: { id }, limit: 1 });
|
|
196
|
+
return results[0] ?? null;
|
|
197
|
+
}
|
|
198
|
+
const { sql, params } = buildTranslationQueryById(table, this.schema, id, options.lang, options.fallbackLang ?? this.defaultLang);
|
|
199
|
+
const rows = this.runQuery(sql, params);
|
|
200
|
+
const deserialized = this.deserializeResults(table, rows);
|
|
201
|
+
return deserialized[0] ?? null;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Count records in a table
|
|
205
|
+
*/
|
|
206
|
+
async count(table, options) {
|
|
207
|
+
const { sql, params } = this.buildSelectQuery(table, options, true);
|
|
208
|
+
const result = this.runQuery(sql, params);
|
|
209
|
+
return result[0]?.count ?? 0;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Paginate records
|
|
213
|
+
*/
|
|
214
|
+
async paginate(table, page, limit, options) {
|
|
215
|
+
const total = await this.count(table, options);
|
|
216
|
+
const totalPages = Math.ceil(total / limit);
|
|
217
|
+
const offset = (page - 1) * limit;
|
|
218
|
+
const data = await this.list(table, {
|
|
219
|
+
...options,
|
|
220
|
+
limit,
|
|
221
|
+
offset,
|
|
222
|
+
});
|
|
223
|
+
return {
|
|
224
|
+
data,
|
|
225
|
+
page,
|
|
226
|
+
limit,
|
|
227
|
+
total,
|
|
228
|
+
totalPages,
|
|
229
|
+
hasMore: page < totalPages,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Create a new record
|
|
234
|
+
*/
|
|
235
|
+
async create(table, data) {
|
|
236
|
+
const db = this.getDb();
|
|
237
|
+
const tableSchema = this.schema?.tables[table];
|
|
238
|
+
const serializedData = this.serializeData(table, data);
|
|
239
|
+
// If we have schema and translatable fields, handle them
|
|
240
|
+
if (tableSchema) {
|
|
241
|
+
const { mainData, translatableData } = extractTranslatableData(serializedData, tableSchema);
|
|
242
|
+
// Only main data columns
|
|
243
|
+
const columns = Object.keys(mainData);
|
|
244
|
+
const values = Object.values(mainData);
|
|
245
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
246
|
+
const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
|
|
247
|
+
db.run(sql, values);
|
|
248
|
+
const lastId = db.exec("SELECT last_insert_rowid() as id")[0].values[0][0];
|
|
249
|
+
this.persist();
|
|
250
|
+
// Create default lang translation if we have translatable data
|
|
251
|
+
if (Object.keys(translatableData).length > 0 && this.defaultLang) {
|
|
252
|
+
await this.upsertTranslation(table, lastId, this.defaultLang, translatableData);
|
|
253
|
+
}
|
|
254
|
+
const result = await this.get(table, lastId);
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
// No schema - simple insert
|
|
258
|
+
const columns = Object.keys(serializedData);
|
|
259
|
+
const values = Object.values(serializedData);
|
|
260
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
261
|
+
const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
|
|
262
|
+
db.run(sql, values);
|
|
263
|
+
const lastId = db.exec("SELECT last_insert_rowid() as id")[0].values[0][0];
|
|
264
|
+
this.persist();
|
|
265
|
+
const result = await this.get(table, lastId);
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Create a record with explicit translations
|
|
270
|
+
*/
|
|
271
|
+
async createWithTranslations(table, data, translations) {
|
|
272
|
+
const db = this.getDb();
|
|
273
|
+
const tableSchema = this.schema?.tables[table];
|
|
274
|
+
const serializedData = this.serializeData(table, data);
|
|
275
|
+
let mainData = serializedData;
|
|
276
|
+
let translatableData = {};
|
|
277
|
+
if (tableSchema) {
|
|
278
|
+
const extracted = extractTranslatableData(serializedData, tableSchema);
|
|
279
|
+
mainData = extracted.mainData;
|
|
280
|
+
translatableData = extracted.translatableData;
|
|
281
|
+
}
|
|
282
|
+
// Insert main record
|
|
283
|
+
const columns = Object.keys(mainData);
|
|
284
|
+
const values = Object.values(mainData);
|
|
285
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
286
|
+
const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
|
|
287
|
+
db.run(sql, values);
|
|
288
|
+
const lastId = db.exec("SELECT last_insert_rowid() as id")[0].values[0][0];
|
|
289
|
+
// Insert translations
|
|
290
|
+
if (translations) {
|
|
291
|
+
for (const [lang, langData] of Object.entries(translations)) {
|
|
292
|
+
await this.upsertTranslation(table, lastId, lang, langData);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
else if (Object.keys(translatableData).length > 0 && this.defaultLang) {
|
|
296
|
+
// Auto-create default lang translation from translatableData
|
|
297
|
+
await this.upsertTranslation(table, lastId, this.defaultLang, translatableData);
|
|
298
|
+
}
|
|
299
|
+
this.persist();
|
|
300
|
+
const result = await this.get(table, lastId);
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Update a record by ID
|
|
305
|
+
*/
|
|
306
|
+
async update(table, id, data) {
|
|
307
|
+
const db = this.getDb();
|
|
308
|
+
const tableSchema = this.schema?.tables[table];
|
|
309
|
+
const serializedData = this.serializeData(table, data);
|
|
310
|
+
if (tableSchema) {
|
|
311
|
+
const { mainData, translatableData } = extractTranslatableData(serializedData, tableSchema);
|
|
312
|
+
// Update main table if there's data
|
|
313
|
+
if (Object.keys(mainData).length > 0) {
|
|
314
|
+
const columns = Object.keys(mainData);
|
|
315
|
+
const values = Object.values(mainData);
|
|
316
|
+
const setParts = columns.map((col) => `${col} = ?`).join(", ");
|
|
317
|
+
const sql = `UPDATE ${table} SET ${setParts} WHERE id = ?`;
|
|
318
|
+
db.run(sql, [...values, id]);
|
|
319
|
+
}
|
|
320
|
+
// Upsert translation for default lang if we have translatable data
|
|
321
|
+
if (Object.keys(translatableData).length > 0 && this.defaultLang) {
|
|
322
|
+
await this.upsertTranslation(table, id, this.defaultLang, translatableData);
|
|
323
|
+
}
|
|
324
|
+
this.persist();
|
|
325
|
+
const result = await this.get(table, id);
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
// No schema - simple update
|
|
329
|
+
const columns = Object.keys(serializedData);
|
|
330
|
+
const values = Object.values(serializedData);
|
|
331
|
+
const setParts = columns.map((col) => `${col} = ?`).join(", ");
|
|
332
|
+
const sql = `UPDATE ${table} SET ${setParts} WHERE id = ?`;
|
|
333
|
+
db.run(sql, [...values, id]);
|
|
334
|
+
this.persist();
|
|
335
|
+
const result = await this.get(table, id);
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Upsert a translation for a record
|
|
340
|
+
*/
|
|
341
|
+
async upsertTranslation(table, id, lang, data) {
|
|
342
|
+
const db = this.getDb();
|
|
343
|
+
if (!this.schema) {
|
|
344
|
+
throw new Error(`No schema found for table: ${table}`);
|
|
345
|
+
}
|
|
346
|
+
const { sql, params } = buildTranslationUpsert(table, this.schema, id, lang, data);
|
|
347
|
+
db.run(sql, params);
|
|
348
|
+
this.persist();
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Get all translations for a record
|
|
352
|
+
*/
|
|
353
|
+
async getTranslations(table, id) {
|
|
354
|
+
const tableSchema = this.schema?.tables[table];
|
|
355
|
+
if (!tableSchema) {
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
358
|
+
const translationTable = toTranslationTableName(table);
|
|
359
|
+
const fkName = toTranslationFKName(table);
|
|
360
|
+
const sql = `SELECT * FROM ${translationTable} WHERE ${fkName} = ?`;
|
|
361
|
+
return this.runQuery(sql, [id]);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Delete a record by ID
|
|
365
|
+
*/
|
|
366
|
+
async delete(table, id) {
|
|
367
|
+
const db = this.getDb();
|
|
368
|
+
// Delete translations first if schema exists
|
|
369
|
+
if (this.schema?.tables[table]) {
|
|
370
|
+
const translationTable = toTranslationTableName(table);
|
|
371
|
+
const fkName = toTranslationFKName(table);
|
|
372
|
+
try {
|
|
373
|
+
db.run(`DELETE FROM ${translationTable} WHERE ${fkName} = ?`, [id]);
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
// Translation table might not exist
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const sql = `DELETE FROM ${table} WHERE id = ?`;
|
|
380
|
+
db.run(sql, [id]);
|
|
381
|
+
this.persist();
|
|
382
|
+
const changes = db.getRowsModified();
|
|
383
|
+
return changes > 0;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Execute a raw SQL statement
|
|
387
|
+
*/
|
|
388
|
+
async execute(query, params) {
|
|
389
|
+
const db = this.getDb();
|
|
390
|
+
db.run(query, params);
|
|
391
|
+
this.persist();
|
|
392
|
+
const changes = db.getRowsModified();
|
|
393
|
+
// sql.js doesn't expose lastInsertRowid directly, use a query
|
|
394
|
+
const result = db.exec("SELECT last_insert_rowid() as id");
|
|
395
|
+
const lastInsertRowid = result[0]?.values[0]?.[0] ?? 0;
|
|
396
|
+
return { changes, lastInsertRowid };
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Execute a raw SQL query and return results
|
|
400
|
+
*/
|
|
401
|
+
async raw(query, params) {
|
|
402
|
+
return this.runQuery(query, params ?? []);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Seed database with initial data
|
|
406
|
+
*/
|
|
407
|
+
async seed(data) {
|
|
408
|
+
const db = this.getDb();
|
|
409
|
+
for (const [table, records] of Object.entries(data)) {
|
|
410
|
+
for (const record of records) {
|
|
411
|
+
const serialized = this.serializeData(table, record);
|
|
412
|
+
const columns = Object.keys(serialized);
|
|
413
|
+
const values = Object.values(serialized);
|
|
414
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
415
|
+
const sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
|
|
416
|
+
db.run(sql, values);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
this.persist();
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Get all table names
|
|
423
|
+
*/
|
|
424
|
+
async getTables() {
|
|
425
|
+
const result = this.runQuery("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'");
|
|
426
|
+
return result.map((row) => row.name);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Get table schema
|
|
430
|
+
*/
|
|
431
|
+
async getTableSchema(table) {
|
|
432
|
+
const db = this.getDb();
|
|
433
|
+
const result = db.exec(`PRAGMA table_info(${table})`);
|
|
434
|
+
if (result.length === 0 || result[0].values.length === 0) {
|
|
435
|
+
return [];
|
|
436
|
+
}
|
|
437
|
+
return result[0].values.map((row) => ({
|
|
438
|
+
name: String(row[1]),
|
|
439
|
+
type: String(row[2]),
|
|
440
|
+
notnull: Number(row[3]),
|
|
441
|
+
pk: Number(row[5]),
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Close the database connection
|
|
446
|
+
*/
|
|
447
|
+
close() {
|
|
448
|
+
if (this.db) {
|
|
449
|
+
this.persist();
|
|
450
|
+
this.db.close();
|
|
451
|
+
this.db = null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
// Transaction methods
|
|
455
|
+
async beginTransaction() {
|
|
456
|
+
await this.execute("BEGIN TRANSACTION");
|
|
457
|
+
}
|
|
458
|
+
async commit() {
|
|
459
|
+
await this.execute("COMMIT");
|
|
460
|
+
this.persist();
|
|
461
|
+
}
|
|
462
|
+
async rollback() {
|
|
463
|
+
await this.execute("ROLLBACK");
|
|
464
|
+
}
|
|
465
|
+
// Batch methods
|
|
466
|
+
async createMany(table, records, options) {
|
|
467
|
+
const db = this.getDb();
|
|
468
|
+
const ids = [];
|
|
469
|
+
const tableSchema = this.schema?.tables[table];
|
|
470
|
+
for (const record of records) {
|
|
471
|
+
const serializedRecord = this.serializeData(table, record);
|
|
472
|
+
let mainData = serializedRecord;
|
|
473
|
+
let translatableData = {};
|
|
474
|
+
if (tableSchema) {
|
|
475
|
+
const extracted = extractTranslatableData(serializedRecord, tableSchema);
|
|
476
|
+
mainData = extracted.mainData;
|
|
477
|
+
translatableData = extracted.translatableData;
|
|
478
|
+
}
|
|
479
|
+
const columns = Object.keys(mainData);
|
|
480
|
+
const values = Object.values(mainData);
|
|
481
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
482
|
+
const insertType = options?.ignore ? "INSERT OR IGNORE" : "INSERT";
|
|
483
|
+
const sql = `${insertType} INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
|
|
484
|
+
db.run(sql, values);
|
|
485
|
+
const lastId = db.exec("SELECT last_insert_rowid() as id")[0]
|
|
486
|
+
.values[0][0];
|
|
487
|
+
if (db.getRowsModified() > 0) {
|
|
488
|
+
ids.push(lastId);
|
|
489
|
+
// Create translation for default lang
|
|
490
|
+
if (Object.keys(translatableData).length > 0 && this.defaultLang) {
|
|
491
|
+
await this.upsertTranslation(table, lastId, this.defaultLang, translatableData);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
this.persist();
|
|
496
|
+
return { created: ids.length, ids };
|
|
497
|
+
}
|
|
498
|
+
async updateMany(table, updates) {
|
|
499
|
+
let updated = 0;
|
|
500
|
+
for (const { id, data } of updates) {
|
|
501
|
+
await this.update(table, id, data);
|
|
502
|
+
updated++;
|
|
503
|
+
}
|
|
504
|
+
return { updated };
|
|
505
|
+
}
|
|
506
|
+
async deleteMany(table, ids) {
|
|
507
|
+
let deleted = 0;
|
|
508
|
+
for (const id of ids) {
|
|
509
|
+
const success = await this.delete(table, id);
|
|
510
|
+
if (success)
|
|
511
|
+
deleted++;
|
|
512
|
+
}
|
|
513
|
+
return { deleted };
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Export database as Uint8Array
|
|
517
|
+
*/
|
|
518
|
+
export() {
|
|
519
|
+
return this.getDb().export();
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Import database from Uint8Array
|
|
523
|
+
*/
|
|
524
|
+
async import(data) {
|
|
525
|
+
if (!this.SQL) {
|
|
526
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
527
|
+
}
|
|
528
|
+
this.db?.close();
|
|
529
|
+
this.db = new this.SQL.Database(data);
|
|
530
|
+
this.persist();
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Clear all data and reset database
|
|
534
|
+
*/
|
|
535
|
+
clear() {
|
|
536
|
+
if (!this.SQL) {
|
|
537
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
538
|
+
}
|
|
539
|
+
this.db?.close();
|
|
540
|
+
this.db = new this.SQL.Database();
|
|
541
|
+
this.persist();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataManager.d.ts","sourceRoot":"","sources":["../../core/DataManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDataManager.d.ts","sourceRoot":"","sources":["../../hooks/useDataManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDbHooks.d.ts","sourceRoot":"","sources":["../../hooks/useDbHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,8DAchE;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EACxB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,EAChD,YAAY,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,2GAgBrC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,gFAUhE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM;QAO7B,MAAM;UAAQ,MAAM;YAKvD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,sFAUxC"}
|