pomegranate-db 0.1.0 → 1.0.0
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 +50 -4
- package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.d.ts +23 -5
- package/dist/adapters/expo-sqlite/ExpoSQLiteDriver.js +325 -28
- package/dist/adapters/loki/worker/LokiExecutor.d.ts +4 -0
- package/dist/adapters/loki/worker/LokiExecutor.js +142 -2
- package/dist/adapters/native-sqlite/NativeSQLiteDriver.js +17 -0
- package/dist/adapters/op-sqlite/OpSQLiteDriver.d.ts +11 -0
- package/dist/adapters/op-sqlite/OpSQLiteDriver.js +48 -6
- package/dist/adapters/sqlite/SQLiteAdapter.d.ts +33 -0
- package/dist/adapters/sqlite/SQLiteAdapter.js +95 -22
- package/dist/adapters/types.d.ts +7 -0
- package/dist/database/Database.d.ts +12 -21
- package/dist/database/Database.js +53 -2
- package/dist/encryption/index.d.ts +20 -8
- package/dist/encryption/index.js +60 -68
- package/dist/encryption/node.d.ts +8 -0
- package/dist/encryption/node.js +33 -0
- package/dist/encryption/react-native.d.ts +8 -0
- package/dist/encryption/react-native.js +14 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -5
- package/dist/observable/Subject.js +4 -0
- package/dist/sync/sync.d.ts +7 -2
- package/dist/sync/sync.js +101 -62
- package/package.json +20 -2
- package/dist/encryption/nodeCrypto.d.ts +0 -18
- package/dist/encryption/nodeCrypto.js +0 -25
- package/dist/encryption/nodeCrypto.native.d.ts +0 -13
- package/dist/encryption/nodeCrypto.native.js +0 -26
- package/native/shared/sqlite3/sqlite3.c +0 -260493
- package/native/shared/sqlite3/sqlite3.h +0 -13583
package/README.md
CHANGED
|
@@ -110,12 +110,58 @@ function PostList() {
|
|
|
110
110
|
}
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
## Installation
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
npm install pomegranate-db
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Install adapter-specific peers only when you use those entry points:
|
|
120
|
+
|
|
121
|
+
- `pomegranate-db` -> `react`
|
|
122
|
+
- `pomegranate-db/expo` -> `react`, `expo-sqlite`
|
|
123
|
+
- `pomegranate-db/encryption` -> no extra peers, uses Web Crypto
|
|
124
|
+
- `pomegranate-db/encryption/node` -> Node.js only
|
|
125
|
+
- `pomegranate-db/encryption/react-native` -> React Native / Expo Web Crypto runtime
|
|
126
|
+
- `pomegranate-db/op-sqlite` -> `@op-engineering/op-sqlite`
|
|
127
|
+
- `pomegranate-db/native-sqlite` -> React Native app with the bundled native module
|
|
128
|
+
|
|
129
|
+
## Entry Points
|
|
130
|
+
|
|
131
|
+
PomegranateDB ships a small set of explicit subpath exports for common setups:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { Database, LokiAdapter } from 'pomegranate-db'
|
|
135
|
+
import { createExpoSQLiteDriver } from 'pomegranate-db/expo'
|
|
136
|
+
import { EncryptingAdapter } from 'pomegranate-db/encryption'
|
|
137
|
+
import { nodeCryptoProvider } from 'pomegranate-db/encryption/node'
|
|
138
|
+
import { createOpSQLiteDriver } from 'pomegranate-db/op-sqlite'
|
|
139
|
+
import { createNativeSQLiteDriver } from 'pomegranate-db/native-sqlite'
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The root package intentionally excludes encryption exports so Expo Snack can install
|
|
143
|
+
`pomegranate-db` without resolving Node's `crypto` module. Import encryption through
|
|
144
|
+
the explicit `./encryption`, `./encryption/node`, or `./encryption/react-native`
|
|
145
|
+
subpaths instead.
|
|
146
|
+
|
|
147
|
+
## Migrations
|
|
148
|
+
|
|
149
|
+
Manual migrations are supported across Loki and SQLite adapters with these step types:
|
|
150
|
+
|
|
151
|
+
- `createTable`
|
|
152
|
+
- `addColumn`
|
|
153
|
+
- `destroyTable`
|
|
154
|
+
- `sql`
|
|
155
|
+
|
|
156
|
+
Use `sql` for targeted backfills such as setting a new column value on existing rows.
|
|
157
|
+
Schema diff generation is not automated yet, so migration steps are still authored manually.
|
|
158
|
+
|
|
113
159
|
## Next Steps
|
|
114
160
|
|
|
115
|
-
- [Installation](https://bobbyquantum.github.io/pomegranate/
|
|
116
|
-
- [Schema & Models](https://bobbyquantum.github.io/pomegranate/
|
|
117
|
-
- [CRUD Operations](https://bobbyquantum.github.io/pomegranate/
|
|
118
|
-
- [React Hooks](https://bobbyquantum.github.io/pomegranate/
|
|
161
|
+
- [Installation](https://bobbyquantum.github.io/pomegranate/installation) — add PomegranateDB to your project
|
|
162
|
+
- [Schema & Models](https://bobbyquantum.github.io/pomegranate/schema) — define your data model
|
|
163
|
+
- [CRUD Operations](https://bobbyquantum.github.io/pomegranate/crud) — create, read, update, delete
|
|
164
|
+
- [React Hooks](https://bobbyquantum.github.io/pomegranate/react-hooks) — reactive UI integration
|
|
119
165
|
|
|
120
166
|
## License
|
|
121
167
|
|
|
@@ -6,24 +6,42 @@
|
|
|
6
6
|
* using the Expo managed SQLite library instead of requiring
|
|
7
7
|
* react-native-quick-sqlite or op-sqlite.
|
|
8
8
|
*
|
|
9
|
+
* Supports both **async** and **sync** modes:
|
|
10
|
+
* - `preferSync: false` (default) — uses async APIs (runAsync, getAllAsync).
|
|
11
|
+
* Works on all platforms including web (wa-sqlite / OPFS).
|
|
12
|
+
* - `preferSync: true` — uses synchronous JSI APIs (runSync, getAllSync).
|
|
13
|
+
* Faster on native (no Promise overhead) but NOT available on web.
|
|
14
|
+
* Falls back to async automatically on web.
|
|
15
|
+
*
|
|
9
16
|
* Usage:
|
|
10
17
|
* import { createExpoSQLiteDriver } from 'pomegranate-db/expo';
|
|
11
18
|
* import { SQLiteAdapter } from 'pomegranate-db';
|
|
12
19
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
20
|
+
* // Async (default — works everywhere)
|
|
21
|
+
* const driver = createExpoSQLiteDriver();
|
|
22
|
+
*
|
|
23
|
+
* // Sync (native-only, falls back to async on web)
|
|
24
|
+
* const driverSync = createExpoSQLiteDriver({ preferSync: true });
|
|
17
25
|
*/
|
|
18
26
|
import type { SQLiteDriver } from '../sqlite/SQLiteAdapter';
|
|
19
27
|
export interface ExpoSQLiteDriverConfig {
|
|
20
28
|
/**
|
|
21
|
-
* Options passed to expo-sqlite's openDatabaseAsync.
|
|
29
|
+
* Options passed to expo-sqlite's openDatabaseAsync/openDatabaseSync.
|
|
22
30
|
* @default {}
|
|
23
31
|
*/
|
|
24
32
|
openOptions?: {
|
|
25
33
|
enableChangeListener?: boolean;
|
|
26
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* When true, use synchronous JSI calls (runSync, getAllSync, etc.)
|
|
37
|
+
* for better performance on native platforms.
|
|
38
|
+
*
|
|
39
|
+
* On web (wa-sqlite), sync methods are not available — the driver
|
|
40
|
+
* will automatically fall back to async mode.
|
|
41
|
+
*
|
|
42
|
+
* @default false
|
|
43
|
+
*/
|
|
44
|
+
preferSync?: boolean;
|
|
27
45
|
}
|
|
28
46
|
/**
|
|
29
47
|
* Create a SQLiteDriver backed by expo-sqlite.
|
|
@@ -7,14 +7,22 @@
|
|
|
7
7
|
* using the Expo managed SQLite library instead of requiring
|
|
8
8
|
* react-native-quick-sqlite or op-sqlite.
|
|
9
9
|
*
|
|
10
|
+
* Supports both **async** and **sync** modes:
|
|
11
|
+
* - `preferSync: false` (default) — uses async APIs (runAsync, getAllAsync).
|
|
12
|
+
* Works on all platforms including web (wa-sqlite / OPFS).
|
|
13
|
+
* - `preferSync: true` — uses synchronous JSI APIs (runSync, getAllSync).
|
|
14
|
+
* Faster on native (no Promise overhead) but NOT available on web.
|
|
15
|
+
* Falls back to async automatically on web.
|
|
16
|
+
*
|
|
10
17
|
* Usage:
|
|
11
18
|
* import { createExpoSQLiteDriver } from 'pomegranate-db/expo';
|
|
12
19
|
* import { SQLiteAdapter } from 'pomegranate-db';
|
|
13
20
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
21
|
+
* // Async (default — works everywhere)
|
|
22
|
+
* const driver = createExpoSQLiteDriver();
|
|
23
|
+
*
|
|
24
|
+
* // Sync (native-only, falls back to async on web)
|
|
25
|
+
* const driverSync = createExpoSQLiteDriver({ preferSync: true });
|
|
18
26
|
*/
|
|
19
27
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
20
28
|
if (k2 === undefined) k2 = k;
|
|
@@ -51,6 +59,27 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
51
59
|
})();
|
|
52
60
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53
61
|
exports.createExpoSQLiteDriver = createExpoSQLiteDriver;
|
|
62
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
63
|
+
/**
|
|
64
|
+
* Replace `?` placeholders in SQL with literal values.
|
|
65
|
+
*
|
|
66
|
+
* This is used for `execAsync(multiStatement)` which doesn't accept bindings.
|
|
67
|
+
* Safe because all values come from our own SQL generators with known types.
|
|
68
|
+
*/
|
|
69
|
+
function inlineBindings(sql, bindings) {
|
|
70
|
+
let index = 0;
|
|
71
|
+
return sql.replaceAll('?', () => {
|
|
72
|
+
const val = bindings[index++];
|
|
73
|
+
if (val === null || val === undefined)
|
|
74
|
+
return 'NULL';
|
|
75
|
+
if (typeof val === 'number')
|
|
76
|
+
return String(val);
|
|
77
|
+
if (typeof val === 'boolean')
|
|
78
|
+
return val ? '1' : '0';
|
|
79
|
+
return `'${String(val).replaceAll("'", "''")}'`;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// ─── Driver Factory ───────────────────────────────────────────────────────
|
|
54
83
|
/**
|
|
55
84
|
* Create a SQLiteDriver backed by expo-sqlite.
|
|
56
85
|
*
|
|
@@ -60,6 +89,48 @@ exports.createExpoSQLiteDriver = createExpoSQLiteDriver;
|
|
|
60
89
|
function createExpoSQLiteDriver(config) {
|
|
61
90
|
let db = null;
|
|
62
91
|
let expoSQLite = null;
|
|
92
|
+
// Whether we're actually using sync mode (resolved after open)
|
|
93
|
+
let useSync = false;
|
|
94
|
+
// ── Statement cache (sync mode only) ──────────────────────────────────
|
|
95
|
+
// Caches compiled sqlite3_stmt handles keyed by SQL string.
|
|
96
|
+
// Avoids re-calling sqlite3_prepare_v2 for repeated SQL (e.g. 1000 INSERTs
|
|
97
|
+
// with the same template). This mirrors NativeSQLite's cachedPrepare().
|
|
98
|
+
const stmtCache = new Map();
|
|
99
|
+
const MAX_CACHE_SIZE = 50;
|
|
100
|
+
function getCachedStmt(database, sql) {
|
|
101
|
+
if (!database.prepareSync)
|
|
102
|
+
return null;
|
|
103
|
+
let stmt = stmtCache.get(sql);
|
|
104
|
+
if (stmt)
|
|
105
|
+
return stmt;
|
|
106
|
+
// Evict oldest entry if cache is full
|
|
107
|
+
if (stmtCache.size >= MAX_CACHE_SIZE) {
|
|
108
|
+
const firstKey = stmtCache.keys().next().value;
|
|
109
|
+
const evicted = stmtCache.get(firstKey);
|
|
110
|
+
stmtCache.delete(firstKey);
|
|
111
|
+
try {
|
|
112
|
+
evicted?.finalizeSync();
|
|
113
|
+
}
|
|
114
|
+
catch { /* already finalized */ }
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
stmt = database.prepareSync(sql);
|
|
118
|
+
stmtCache.set(sql, stmt);
|
|
119
|
+
return stmt;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null; // Fall back to runSync/execSync
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function clearStmtCache() {
|
|
126
|
+
for (const stmt of stmtCache.values()) {
|
|
127
|
+
try {
|
|
128
|
+
stmt.finalizeSync();
|
|
129
|
+
}
|
|
130
|
+
catch { /* ignore */ }
|
|
131
|
+
}
|
|
132
|
+
stmtCache.clear();
|
|
133
|
+
}
|
|
63
134
|
// Lazily import expo-sqlite so this module can be imported
|
|
64
135
|
// without expo-sqlite being installed (e.g. in tests).
|
|
65
136
|
async function getExpoSQLite() {
|
|
@@ -83,26 +154,86 @@ function createExpoSQLiteDriver(config) {
|
|
|
83
154
|
return {
|
|
84
155
|
async open(name) {
|
|
85
156
|
const sqlite = await getExpoSQLite();
|
|
86
|
-
|
|
157
|
+
const dbName = name.endsWith('.db') ? name : `${name}.db`;
|
|
158
|
+
// Try sync open if preferred and available
|
|
159
|
+
if (config?.preferSync && typeof sqlite.openDatabaseSync === 'function') {
|
|
160
|
+
try {
|
|
161
|
+
db = sqlite.openDatabaseSync(dbName, config?.openOptions);
|
|
162
|
+
useSync = true;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// openDatabaseSync not supported (e.g. web) — fall through
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Async fallback (or default path)
|
|
169
|
+
if (!db) {
|
|
170
|
+
db = await sqlite.openDatabaseAsync(dbName, config?.openOptions);
|
|
171
|
+
useSync = false;
|
|
172
|
+
}
|
|
87
173
|
// Enable WAL mode for better performance (may not be supported on web/wa-sqlite)
|
|
88
174
|
try {
|
|
89
|
-
|
|
175
|
+
if (useSync && db.execSync) {
|
|
176
|
+
db.execSync('PRAGMA journal_mode = WAL');
|
|
177
|
+
db.execSync('PRAGMA synchronous = NORMAL');
|
|
178
|
+
db.execSync('PRAGMA cache_size = -8000');
|
|
179
|
+
db.execSync('PRAGMA temp_store = MEMORY');
|
|
180
|
+
db.execSync('PRAGMA busy_timeout = 5000');
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
await db.execAsync('PRAGMA journal_mode = WAL');
|
|
184
|
+
await db.execAsync('PRAGMA synchronous = NORMAL');
|
|
185
|
+
await db.execAsync('PRAGMA cache_size = -8000');
|
|
186
|
+
await db.execAsync('PRAGMA temp_store = MEMORY');
|
|
187
|
+
await db.execAsync('PRAGMA busy_timeout = 5000');
|
|
188
|
+
}
|
|
90
189
|
}
|
|
91
190
|
catch {
|
|
92
|
-
//
|
|
191
|
+
// PRAGMAs not supported on this platform (e.g. web wa-sqlite), continue without them
|
|
93
192
|
}
|
|
94
193
|
},
|
|
95
194
|
async execute(sql, bindings) {
|
|
96
195
|
const database = requireDb();
|
|
97
|
-
|
|
98
|
-
|
|
196
|
+
// DDL statements invalidate cached prepared statements
|
|
197
|
+
if (stmtCache.size > 0 && /^\s*(DROP|CREATE|ALTER)\s/i.test(sql)) {
|
|
198
|
+
clearStmtCache();
|
|
199
|
+
}
|
|
200
|
+
if (useSync && database.runSync) {
|
|
201
|
+
if (bindings && bindings.length > 0) {
|
|
202
|
+
const stmt = getCachedStmt(database, sql);
|
|
203
|
+
if (stmt) {
|
|
204
|
+
stmt.executeSync(bindings);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
database.runSync(sql, ...bindings);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else if (database.execSync) {
|
|
211
|
+
database.execSync(sql);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
database.runSync(sql);
|
|
215
|
+
}
|
|
99
216
|
}
|
|
100
217
|
else {
|
|
101
|
-
|
|
218
|
+
if (bindings && bindings.length > 0) {
|
|
219
|
+
await database.runAsync(sql, ...bindings);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
await database.execAsync(sql);
|
|
223
|
+
}
|
|
102
224
|
}
|
|
103
225
|
},
|
|
104
226
|
async query(sql, bindings) {
|
|
105
227
|
const database = requireDb();
|
|
228
|
+
if (useSync && database.getAllSync) {
|
|
229
|
+
// Use database.getAllSync directly (no statement cache for queries).
|
|
230
|
+
// The bulk row transfer in getAllSync is faster than iterating via
|
|
231
|
+
// a prepared statement's getAllSync for large result sets.
|
|
232
|
+
if (bindings && bindings.length > 0) {
|
|
233
|
+
return database.getAllSync(sql, ...bindings);
|
|
234
|
+
}
|
|
235
|
+
return database.getAllSync(sql);
|
|
236
|
+
}
|
|
106
237
|
if (bindings && bindings.length > 0) {
|
|
107
238
|
return database.getAllAsync(sql, ...bindings);
|
|
108
239
|
}
|
|
@@ -110,46 +241,212 @@ function createExpoSQLiteDriver(config) {
|
|
|
110
241
|
},
|
|
111
242
|
async executeInTransaction(fn) {
|
|
112
243
|
const database = requireDb();
|
|
113
|
-
//
|
|
114
|
-
|
|
244
|
+
// Sync transaction path (native only)
|
|
245
|
+
if (useSync && database.withTransactionSync) {
|
|
246
|
+
try {
|
|
247
|
+
database.withTransactionSync(() => {
|
|
248
|
+
// NOTE: fn() returns a Promise but our sync transaction
|
|
249
|
+
// callback is synchronous. The inner operations will also
|
|
250
|
+
// be sync (since useSync=true), so the await is a no-op.
|
|
251
|
+
// We run the promise synchronously via the micro-task trick.
|
|
252
|
+
let error;
|
|
253
|
+
let done = false;
|
|
254
|
+
fn().then(() => { done = true; }, (error_) => { error = error_; done = true; });
|
|
255
|
+
// In sync mode all awaits inside fn() resolve immediately
|
|
256
|
+
// (they're wrapping synchronous calls), so done should be true.
|
|
257
|
+
if (!done) {
|
|
258
|
+
throw new Error('ExpoSQLiteDriver: async operations inside sync transaction are not supported. ' +
|
|
259
|
+
'Use preferSync: false for mixed async/sync workloads.');
|
|
260
|
+
}
|
|
261
|
+
if (error)
|
|
262
|
+
throw error;
|
|
263
|
+
});
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
if (error instanceof Error && error.message.includes('not supported on web')) {
|
|
268
|
+
// Fall through to async
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Async transaction path
|
|
115
276
|
if (typeof database.withExclusiveTransactionAsync === 'function') {
|
|
116
277
|
try {
|
|
117
278
|
await database.withExclusiveTransactionAsync(async (_txn) => {
|
|
118
|
-
// expo-sqlite's exclusive transaction scopes all queries
|
|
119
|
-
// on this database connection to the transaction, so we
|
|
120
|
-
// can just call fn() which uses the same `db` reference.
|
|
121
279
|
await fn();
|
|
122
280
|
});
|
|
123
281
|
return;
|
|
124
282
|
}
|
|
125
|
-
catch (
|
|
126
|
-
|
|
127
|
-
// Detect this and fall through to manual transaction handling.
|
|
128
|
-
if (e instanceof Error && e.message.includes('not supported on web')) {
|
|
283
|
+
catch (error) {
|
|
284
|
+
if (error instanceof Error && error.message.includes('not supported on web')) {
|
|
129
285
|
// Fall through to manual transaction below
|
|
130
286
|
}
|
|
131
287
|
else {
|
|
132
|
-
throw
|
|
288
|
+
throw error;
|
|
133
289
|
}
|
|
134
290
|
}
|
|
135
291
|
}
|
|
136
292
|
// Manual transaction fallback (web, or platforms without exclusive transactions)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
293
|
+
if (useSync && database.execSync) {
|
|
294
|
+
database.execSync('BEGIN TRANSACTION');
|
|
295
|
+
try {
|
|
296
|
+
await fn();
|
|
297
|
+
database.execSync('COMMIT');
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
database.execSync('ROLLBACK');
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
141
303
|
}
|
|
142
|
-
|
|
143
|
-
await database.execAsync('
|
|
144
|
-
|
|
304
|
+
else {
|
|
305
|
+
await database.execAsync('BEGIN TRANSACTION');
|
|
306
|
+
try {
|
|
307
|
+
await fn();
|
|
308
|
+
await database.execAsync('COMMIT');
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
await database.execAsync('ROLLBACK');
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
145
314
|
}
|
|
146
315
|
},
|
|
147
316
|
async close() {
|
|
148
317
|
if (db) {
|
|
149
|
-
|
|
318
|
+
clearStmtCache();
|
|
319
|
+
if (useSync && db.closeSync) {
|
|
320
|
+
db.closeSync();
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
await db.closeAsync();
|
|
324
|
+
}
|
|
150
325
|
db = null;
|
|
151
326
|
}
|
|
152
327
|
},
|
|
328
|
+
// ── Raw sync/async for benchmarking ──────────────────────────────────
|
|
329
|
+
executeSync(sql, bindings) {
|
|
330
|
+
const database = requireDb();
|
|
331
|
+
if (!database.runSync) {
|
|
332
|
+
throw new Error('ExpoSQLiteDriver: sync API not available (web platform). ' +
|
|
333
|
+
'Set preferSync: true and run on native.');
|
|
334
|
+
}
|
|
335
|
+
if (bindings && bindings.length > 0) {
|
|
336
|
+
const stmt = getCachedStmt(database, sql);
|
|
337
|
+
if (stmt) {
|
|
338
|
+
stmt.executeSync(bindings);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
database.runSync(sql, ...bindings);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
else if (database.execSync) {
|
|
345
|
+
database.execSync(sql);
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
database.runSync(sql);
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
async executeAsync(sql, bindings) {
|
|
352
|
+
const database = requireDb();
|
|
353
|
+
if (bindings && bindings.length > 0) {
|
|
354
|
+
await database.runAsync(sql, ...bindings);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
await database.execAsync(sql);
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
// ── Batch without transaction wrapping ──────────────────────────────
|
|
361
|
+
// Uses execAsync with concatenated SQL to send all commands in a
|
|
362
|
+
// single native call. This avoids per-statement async bridge overhead
|
|
363
|
+
// which is the main bottleneck for expo-sqlite async mode.
|
|
364
|
+
//
|
|
365
|
+
// Values are inlined using SQLite literal escaping. This is safe
|
|
366
|
+
// because all values come from our own SQL generators (insertSQL,
|
|
367
|
+
// updateSQL, deleteSQL) with known types.
|
|
368
|
+
async executeBatchNoTx(commands) {
|
|
369
|
+
const database = requireDb();
|
|
370
|
+
if (commands.length === 0)
|
|
371
|
+
return;
|
|
372
|
+
// For sync mode, use cached prepared statements when possible
|
|
373
|
+
if (useSync && database.runSync) {
|
|
374
|
+
for (const [sql, bindings] of commands) {
|
|
375
|
+
if (bindings && bindings.length > 0) {
|
|
376
|
+
const stmt = getCachedStmt(database, sql);
|
|
377
|
+
if (stmt) {
|
|
378
|
+
stmt.executeSync(bindings);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
database.runSync(sql, ...bindings);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
else if (database.execSync) {
|
|
385
|
+
database.execSync(sql);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
database.runSync(sql);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
// Async mode: build a single SQL string with all statements.
|
|
394
|
+
// This sends one message across the async bridge instead of N.
|
|
395
|
+
const parts = [];
|
|
396
|
+
for (const [sql, bindings] of commands) {
|
|
397
|
+
if (!bindings || bindings.length === 0) {
|
|
398
|
+
parts.push(sql);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
parts.push(inlineBindings(sql, bindings));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
await database.execAsync(parts.join(';\n'));
|
|
405
|
+
},
|
|
406
|
+
// ── Batch with transaction wrapping (for use outside writeTransaction) ──
|
|
407
|
+
async executeBatch(commands) {
|
|
408
|
+
const database = requireDb();
|
|
409
|
+
if (commands.length === 0)
|
|
410
|
+
return;
|
|
411
|
+
// For sync mode, use sync transaction with cached statements
|
|
412
|
+
if (useSync && database.runSync && database.execSync) {
|
|
413
|
+
database.execSync('BEGIN TRANSACTION');
|
|
414
|
+
try {
|
|
415
|
+
for (const [sql, bindings] of commands) {
|
|
416
|
+
if (bindings && bindings.length > 0) {
|
|
417
|
+
const stmt = getCachedStmt(database, sql);
|
|
418
|
+
if (stmt) {
|
|
419
|
+
stmt.executeSync(bindings);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
database.runSync(sql, ...bindings);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
database.execSync(sql);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
database.execSync('COMMIT');
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
database.execSync('ROLLBACK');
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
// Async mode: concatenate with BEGIN/COMMIT wrapping
|
|
438
|
+
const parts = ['BEGIN TRANSACTION'];
|
|
439
|
+
for (const [sql, bindings] of commands) {
|
|
440
|
+
if (!bindings || bindings.length === 0) {
|
|
441
|
+
parts.push(sql);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
parts.push(inlineBindings(sql, bindings));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
parts.push('COMMIT');
|
|
448
|
+
await database.execAsync(parts.join(';\n'));
|
|
449
|
+
},
|
|
153
450
|
};
|
|
154
451
|
}
|
|
155
452
|
//# sourceMappingURL=ExpoSQLiteDriver.js.map
|
|
@@ -56,6 +56,10 @@ export declare class LokiExecutor {
|
|
|
56
56
|
initialize(schema: DatabaseSchema): Promise<void>;
|
|
57
57
|
private _createLokiDb;
|
|
58
58
|
private _getCollection;
|
|
59
|
+
private _getMetadataCollection;
|
|
60
|
+
private _setSchemaVersion;
|
|
61
|
+
private _addColumn;
|
|
62
|
+
private _executeSqlMigration;
|
|
59
63
|
/**
|
|
60
64
|
* Persist to storage adapter.
|
|
61
65
|
* No-op when: no persistence configured, or saveStrategy is 'auto' (timer handles it).
|