@op-engineering/op-sqlite 11.2.11 → 11.2.13

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/src/index.ts CHANGED
@@ -1,51 +1,12 @@
1
1
  import { NativeModules, Platform } from 'react-native';
2
2
 
3
- declare global {
4
- function nativeCallSyncHook(): unknown;
5
- var __OPSQLiteProxy: object | undefined;
6
- }
7
-
8
- if (global.__OPSQLiteProxy == null) {
9
- if (NativeModules.OPSQLite == null) {
10
- throw new Error('Base module not found. Maybe try rebuilding the app.');
11
- }
12
-
13
- if (NativeModules.OPSQLite.install == null) {
14
- throw new Error(
15
- 'Failed to install op-sqlite: React Native is not running on-device. OPSQLite can only be used when synchronous method invocations (JSI) are possible. If you are using a remote debugger (e.g. Chrome), switch to an on-device debugger (e.g. Flipper) instead.'
16
- );
17
- }
18
-
19
- // Call the synchronous blocking install() function
20
- const result = NativeModules.OPSQLite.install();
21
- if (result !== true) {
22
- throw new Error(
23
- `Failed to install op-sqlite: The native OPSQLite Module could not be installed! Looks like something went wrong when installing JSI bindings, check the native logs for more info`
24
- );
25
- }
26
-
27
- // Check again if the constructor now exists. If not, throw an error.
28
- if (global.__OPSQLiteProxy == null) {
29
- throw new Error(
30
- 'Failed to install op-sqlite, the native initializer function does not exist. Are you trying to use OPSQLite from different JS Runtimes?'
31
- );
32
- }
33
- }
34
-
35
- const proxy = global.__OPSQLiteProxy;
36
- export const OPSQLite = proxy as OPSQLiteProxy;
37
-
38
- export const {
39
- IOS_DOCUMENT_PATH,
40
- IOS_LIBRARY_PATH,
41
- ANDROID_DATABASE_PATH,
42
- ANDROID_FILES_PATH,
43
- ANDROID_EXTERNAL_FILES_PATH,
44
- } = !!NativeModules.OPSQLite.getConstants
45
- ? NativeModules.OPSQLite.getConstants()
46
- : NativeModules.OPSQLite;
47
-
48
- type Scalar = string | number | boolean | null | ArrayBuffer | ArrayBufferView;
3
+ export type Scalar =
4
+ | string
5
+ | number
6
+ | boolean
7
+ | null
8
+ | ArrayBuffer
9
+ | ArrayBufferView;
49
10
 
50
11
  /**
51
12
  * Object returned by SQL Query executions {
@@ -108,17 +69,17 @@ export type BatchQueryResult = {
108
69
  * Result of loading a file and executing every line as a SQL command
109
70
  * Similar to BatchQueryResult
110
71
  */
111
- export interface FileLoadResult extends BatchQueryResult {
72
+ export type FileLoadResult = BatchQueryResult & {
112
73
  commands?: number;
113
- }
74
+ };
114
75
 
115
- export interface Transaction {
76
+ export type Transaction = {
116
77
  commit: () => Promise<QueryResult>;
117
78
  execute: (query: string, params?: Scalar[]) => Promise<QueryResult>;
118
79
  rollback: () => Promise<QueryResult>;
119
- }
80
+ };
120
81
 
121
- export interface PendingTransaction {
82
+ type PendingTransaction = {
122
83
  /*
123
84
  * The start function should not throw or return a promise because the
124
85
  * queue just calls it and does not monitor for failures or completions.
@@ -129,13 +90,61 @@ export interface PendingTransaction {
129
90
  * It should also automatically commit or rollback the transaction if needed
130
91
  */
131
92
  start: () => void;
132
- }
93
+ };
133
94
 
134
- export type PreparedStatementObj = {
95
+ export type PreparedStatement = {
135
96
  bind: (params: any[]) => Promise<void>;
136
97
  execute: () => Promise<QueryResult>;
137
98
  };
138
99
 
100
+ type InternalDB = {
101
+ close: () => void;
102
+ delete: (location?: string) => void;
103
+ attach: (
104
+ mainDbName: string,
105
+ dbNameToAttach: string,
106
+ alias: string,
107
+ location?: string
108
+ ) => void;
109
+ detach: (mainDbName: string, alias: string) => void;
110
+ transaction: (fn: (tx: Transaction) => Promise<void>) => Promise<void>;
111
+ executeSync: (query: string, params?: Scalar[]) => QueryResult;
112
+ execute: (query: string, params?: Scalar[]) => Promise<QueryResult>;
113
+ executeWithHostObjects: (
114
+ query: string,
115
+ params?: Scalar[]
116
+ ) => Promise<QueryResult>;
117
+ executeBatch: (commands: SQLBatchTuple[]) => Promise<BatchQueryResult>;
118
+ loadFile: (location: string) => Promise<FileLoadResult>;
119
+ updateHook: (
120
+ callback?:
121
+ | ((params: {
122
+ table: string;
123
+ operation: UpdateHookOperation;
124
+ row?: any;
125
+ rowId: number;
126
+ }) => void)
127
+ | null
128
+ ) => void;
129
+ commitHook: (callback?: (() => void) | null) => void;
130
+ rollbackHook: (callback?: (() => void) | null) => void;
131
+ prepareStatement: (query: string) => PreparedStatement;
132
+ loadExtension: (path: string, entryPoint?: string) => void;
133
+ executeRaw: (query: string, params?: Scalar[]) => Promise<any[]>;
134
+ getDbPath: (location?: string) => string;
135
+ reactiveExecute: (params: {
136
+ query: string;
137
+ arguments: any[];
138
+ fireOn: {
139
+ table: string;
140
+ ids?: number[];
141
+ }[];
142
+ callback: (response: any) => void;
143
+ }) => () => void;
144
+ sync: () => void;
145
+ flushPendingReactiveQueries: () => Promise<void>;
146
+ };
147
+
139
148
  export type DB = {
140
149
  close: () => void;
141
150
  delete: (location?: string) => void;
@@ -146,14 +155,78 @@ export type DB = {
146
155
  location?: string
147
156
  ) => void;
148
157
  detach: (mainDbName: string, alias: string) => void;
158
+ /**
159
+ * Wraps all the executions into a transaction. If an error is thrown it will rollback all of the changes
160
+ *
161
+ * You need to use this if you are using reactive queries for the queries to fire after the transaction is done
162
+ */
149
163
  transaction: (fn: (tx: Transaction) => Promise<void>) => Promise<void>;
164
+ /**
165
+ * Sync version of the execute function
166
+ * It will block the JS thread and therefore your UI and should be used with caution
167
+ *
168
+ * When writing your queries, you can use the ? character as a placeholder for parameters
169
+ * The parameters will be automatically escaped and sanitized
170
+ *
171
+ * Example:
172
+ * db.executeSync('SELECT * FROM table WHERE id = ?', [1]);
173
+ *
174
+ * If you are writing a query that doesn't require parameters, you can omit the second argument
175
+ *
176
+ * If you are writing to the database YOU SHOULD BE USING TRANSACTIONS!
177
+ * Transactions protect you from partial writes and ensure that your data is always in a consistent state
178
+ *
179
+ * @param query
180
+ * @param params
181
+ * @returns QueryResult
182
+ */
150
183
  executeSync: (query: string, params?: Scalar[]) => QueryResult;
184
+ /**
185
+ * Basic query execution function, it is async don't forget to await it
186
+ *
187
+ * When writing your queries, you can use the ? character as a placeholder for parameters
188
+ * The parameters will be automatically escaped and sanitized
189
+ *
190
+ * Example:
191
+ * await db.execute('SELECT * FROM table WHERE id = ?', [1]);
192
+ *
193
+ * If you are writing a query that doesn't require parameters, you can omit the second argument
194
+ *
195
+ * If you are writing to the database YOU SHOULD BE USING TRANSACTIONS!
196
+ * Transactions protect you from partial writes and ensure that your data is always in a consistent state
197
+ *
198
+ * If you need a large amount of queries ran as fast as possible you should be using `executeBatch`, `executeRaw`, `loadFile` or `executeWithHostObjects`
199
+ *
200
+ * @param query string of your SQL query
201
+ * @param params a list of parameters to bind to the query, if any
202
+ * @returns Promise<QueryResult> with the result of the query
203
+ */
151
204
  execute: (query: string, params?: Scalar[]) => Promise<QueryResult>;
205
+ /**
206
+ * Similar to the execute function but returns the response in HostObjects
207
+ * Read more about HostObjects in the documentation and their pitfalls
208
+ *
209
+ * Will be a lot faster than the normal execute functions when returning data but you will pay when accessing the fields
210
+ * as the conversion is done the moment you access any field
211
+ * @param query
212
+ * @param params
213
+ * @returns
214
+ */
152
215
  executeWithHostObjects: (
153
216
  query: string,
154
217
  params?: Scalar[]
155
218
  ) => Promise<QueryResult>;
219
+ /**
220
+ * Executes all the queries in the params inside a single transaction
221
+ *
222
+ * It's faster than executing single queries as data is sent to the native side only once
223
+ * @param commands
224
+ * @returns Promise<BatchQueryResult>
225
+ */
156
226
  executeBatch: (commands: SQLBatchTuple[]) => Promise<BatchQueryResult>;
227
+ /**
228
+ * Loads a SQLite Dump from disk. It will be the fastest way to execute a large set of queries as no JS is involved
229
+ */
157
230
  loadFile: (location: string) => Promise<FileLoadResult>;
158
231
  updateHook: (
159
232
  callback?:
@@ -167,10 +240,33 @@ export type DB = {
167
240
  ) => void;
168
241
  commitHook: (callback?: (() => void) | null) => void;
169
242
  rollbackHook: (callback?: (() => void) | null) => void;
170
- prepareStatement: (query: string) => PreparedStatementObj;
243
+ /**
244
+ * Constructs a prepared statement from the query string
245
+ * The statement can be re-bound with parameters and executed
246
+ * The performance gain is significant when the same query is executed multiple times, NOT when the query is executed (once)
247
+ * The cost lies in the preparation of the statement as it is compiled and optimized by the sqlite engine, the params can then rebound
248
+ * but the query itself is already optimized
249
+ *
250
+ * @param query string of your SQL query
251
+ * @returns Prepared statement object
252
+ */
253
+ prepareStatement: (query: string) => PreparedStatement;
254
+ /**
255
+ * Loads a runtime loadable sqlite extension. Libsql and iOS embedded version do not support loading extensions
256
+ */
171
257
  loadExtension: (path: string, entryPoint?: string) => void;
258
+ /**
259
+ * Same as `execute` except the results are not returned in objects but rather in arrays with just the values and not the keys
260
+ * It will be faster since a lot of repeated work is skipped and only the values you care about are returned
261
+ */
172
262
  executeRaw: (query: string, params?: Scalar[]) => Promise<any[]>;
263
+ /**
264
+ * Get's the absolute path to the db file. Useful for debugging on local builds and for attaching the DB from users devices
265
+ */
173
266
  getDbPath: (location?: string) => string;
267
+ /**
268
+ * Reactive execution of queries when data is written to the database. Check the docs for how to use them.
269
+ */
174
270
  reactiveExecute: (params: {
175
271
  query: string;
176
272
  arguments: any[];
@@ -188,34 +284,70 @@ export type DB = {
188
284
  * The database is hosted in turso
189
285
  **/
190
286
  sync: () => void;
191
- flushPendingReactiveQueries: () => Promise<void>;
192
287
  };
193
288
 
194
- type OPSQLiteProxy = {
289
+ export type DBParams = {
290
+ url?: string;
291
+ authToken?: string;
292
+ name?: string;
293
+ location?: string;
294
+ syncInterval?: number;
295
+ };
296
+
297
+ export type OPSQLiteProxy = {
195
298
  open: (options: {
196
299
  name: string;
197
300
  location?: string;
198
301
  encryptionKey?: string;
199
- }) => DB;
200
- openRemote: (options: { url: string; authToken: string }) => DB;
201
- openSync: (options: {
202
- url: string;
203
- authToken: string;
204
- name: string;
205
- location?: string;
206
- syncInterval?: number;
207
- }) => DB;
302
+ }) => InternalDB;
303
+ openRemote: (options: { url: string; authToken: string }) => InternalDB;
304
+ openSync: (options: DBParams) => InternalDB;
208
305
  isSQLCipher: () => boolean;
209
306
  isLibsql: () => boolean;
210
307
  isIOSEmbedded: () => boolean;
211
308
  };
212
309
 
213
- const locks: Record<
214
- string,
215
- { queue: PendingTransaction[]; inProgress: boolean }
216
- > = {};
310
+ declare global {
311
+ var __OPSQLiteProxy: object | undefined;
312
+ }
313
+
314
+ if (global.__OPSQLiteProxy == null) {
315
+ if (NativeModules.OPSQLite == null) {
316
+ throw new Error(
317
+ 'Base module not found. Did you do a pod install/clear the gradle cache?'
318
+ );
319
+ }
320
+
321
+ // Call the synchronous blocking install() function
322
+ const installed = NativeModules.OPSQLite.install();
323
+ if (!installed) {
324
+ throw new Error(
325
+ `Failed to install op-sqlite: The native OPSQLite Module could not be installed! Looks like something went wrong when installing JSI bindings, check the native logs for more info`
326
+ );
327
+ }
328
+
329
+ // Check again if the constructor now exists. If not, throw an error.
330
+ if (global.__OPSQLiteProxy == null) {
331
+ throw new Error(
332
+ 'OPSqlite native object is not available. Something is wrong. Check the native logs for more information.'
333
+ );
334
+ }
335
+ }
217
336
 
218
- function enhanceDB(db: DB, options: any): DB {
337
+ const proxy = global.__OPSQLiteProxy;
338
+ const OPSQLite = proxy as OPSQLiteProxy;
339
+
340
+ export const {
341
+ IOS_DOCUMENT_PATH,
342
+ IOS_LIBRARY_PATH,
343
+ ANDROID_DATABASE_PATH,
344
+ ANDROID_FILES_PATH,
345
+ ANDROID_EXTERNAL_FILES_PATH,
346
+ } = !!NativeModules.OPSQLite.getConstants
347
+ ? NativeModules.OPSQLite.getConstants()
348
+ : NativeModules.OPSQLite;
349
+
350
+ function enhanceDB(db: InternalDB, options: DBParams): DB {
219
351
  const lock = {
220
352
  queue: [] as PendingTransaction[],
221
353
  inProgress: false,
@@ -241,7 +373,8 @@ function enhanceDB(db: DB, options: any): DB {
241
373
  }
242
374
  };
243
375
 
244
- // spreading the object is not working, so we need to do it manually
376
+ // spreading the object does not work with HostObjects (db)
377
+ // We need to manually assign the fields
245
378
  let enhancedDb = {
246
379
  delete: db.delete,
247
380
  attach: db.attach,
@@ -256,11 +389,7 @@ function enhanceDB(db: DB, options: any): DB {
256
389
  getDbPath: db.getDbPath,
257
390
  reactiveExecute: db.reactiveExecute,
258
391
  sync: db.sync,
259
- flushPendingReactiveQueries: db.flushPendingReactiveQueries,
260
- close: () => {
261
- db.close();
262
- delete locks[options.url];
263
- },
392
+ close: db.close,
264
393
  executeWithHostObjects: async (
265
394
  query: string,
266
395
  params?: Scalar[]
@@ -290,7 +419,7 @@ function enhanceDB(db: DB, options: any): DB {
290
419
  ? db.executeSync(query, sanitizedParams as Scalar[])
291
420
  : db.executeSync(query);
292
421
 
293
- let rows: any[] = [];
422
+ let rows: Record<string, Scalar>[] = [];
294
423
  for (let i = 0; i < (intermediateResult.rawRows?.length ?? 0); i++) {
295
424
  let row: Record<string, Scalar> = {};
296
425
  let rawRow = intermediateResult.rawRows![i]!;
@@ -329,9 +458,9 @@ function enhanceDB(db: DB, options: any): DB {
329
458
  sanitizedParams as Scalar[]
330
459
  );
331
460
 
332
- let rows: any[] = [];
461
+ let rows: Record<string, Scalar>[] = [];
333
462
  for (let i = 0; i < (intermediateResult.rawRows?.length ?? 0); i++) {
334
- let row: any = {};
463
+ let row: Record<string, Scalar> = {};
335
464
  let rawRow = intermediateResult.rawRows![i]!;
336
465
  for (let j = 0; j < intermediateResult.columnNames!.length; j++) {
337
466
  let columnName = intermediateResult.columnNames![j]!;
@@ -355,7 +484,7 @@ function enhanceDB(db: DB, options: any): DB {
355
484
  const stmt = db.prepareStatement(query);
356
485
 
357
486
  return {
358
- bind: async (params: any[]) => {
487
+ bind: async (params: Scalar[]) => {
359
488
  const sanitizedParams = params.map((p) => {
360
489
  if (ArrayBuffer.isView(p)) {
361
490
  return p.buffer;
@@ -374,10 +503,12 @@ function enhanceDB(db: DB, options: any): DB {
374
503
  ): Promise<void> => {
375
504
  let isFinalized = false;
376
505
 
377
- const execute = async (query: string, params?: any[] | undefined) => {
506
+ const execute = async (query: string, params?: Scalar[]) => {
378
507
  if (isFinalized) {
379
508
  throw Error(
380
- `OP-Sqlite Error: Database: ${options.url}. Cannot execute query on finalized transaction`
509
+ `OP-Sqlite Error: Database: ${
510
+ options.name || options.url
511
+ }. Cannot execute query on finalized transaction`
381
512
  );
382
513
  }
383
514
  return await enhancedDb.execute(query, params);
@@ -386,12 +517,14 @@ function enhanceDB(db: DB, options: any): DB {
386
517
  const commit = async (): Promise<QueryResult> => {
387
518
  if (isFinalized) {
388
519
  throw Error(
389
- `OP-Sqlite Error: Database: ${options.url}. Cannot execute query on finalized transaction`
520
+ `OP-Sqlite Error: Database: ${
521
+ options.name || options.url
522
+ }. Cannot execute query on finalized transaction`
390
523
  );
391
524
  }
392
525
  const result = await enhancedDb.execute('COMMIT;');
393
526
 
394
- await enhancedDb.flushPendingReactiveQueries();
527
+ await db.flushPendingReactiveQueries();
395
528
 
396
529
  isFinalized = true;
397
530
  return result;
@@ -400,7 +533,9 @@ function enhanceDB(db: DB, options: any): DB {
400
533
  const rollback = async (): Promise<QueryResult> => {
401
534
  if (isFinalized) {
402
535
  throw Error(
403
- `OP-Sqlite Error: Database: ${options.url}. Cannot execute query on finalized transaction`
536
+ `OP-Sqlite Error: Database: ${
537
+ options.name || options.url
538
+ }. Cannot execute query on finalized transaction`
404
539
  );
405
540
  }
406
541
  const result = await enhancedDb.execute('ROLLBACK;');
@@ -422,7 +557,6 @@ function enhanceDB(db: DB, options: any): DB {
422
557
  await commit();
423
558
  }
424
559
  } catch (executionError) {
425
- // console.warn('transaction error', executionError);
426
560
  if (!isFinalized) {
427
561
  try {
428
562
  await rollback();
@@ -455,10 +589,11 @@ function enhanceDB(db: DB, options: any): DB {
455
589
  return enhancedDb;
456
590
  }
457
591
 
458
- /** Open a replicating connection via libsql to a turso db
592
+ /**
593
+ * Open a replicating connection via libsql to a turso db
459
594
  * libsql needs to be enabled on your package.json
460
595
  */
461
- export const openSync = (options: {
596
+ export const openSync = (params: {
462
597
  url: string;
463
598
  authToken: string;
464
599
  name: string;
@@ -469,44 +604,56 @@ export const openSync = (options: {
469
604
  throw new Error('This function is only available for libsql');
470
605
  }
471
606
 
472
- const db = OPSQLite.openSync(options);
473
- const enhancedDb = enhanceDB(db, options);
607
+ const db = OPSQLite.openSync(params);
608
+ const enhancedDb = enhanceDB(db, params);
474
609
 
475
610
  return enhancedDb;
476
611
  };
477
612
 
478
- /** Open a remote connection via libsql to a turso db
613
+ /**
614
+ * Open a remote connection via libsql to a turso db
479
615
  * libsql needs to be enabled on your package.json
480
616
  */
481
- export const openRemote = (options: { url: string; authToken: string }): DB => {
617
+ export const openRemote = (params: { url: string; authToken: string }): DB => {
482
618
  if (!isLibsql()) {
483
619
  throw new Error('This function is only available for libsql');
484
620
  }
485
621
 
486
- const db = OPSQLite.openRemote(options);
487
- const enhancedDb = enhanceDB(db, options);
622
+ const db = OPSQLite.openRemote(params);
623
+ const enhancedDb = enhanceDB(db, params);
488
624
 
489
625
  return enhancedDb;
490
626
  };
491
627
 
492
- export const open = (options: {
628
+ /**
629
+ * Open a connection to a local sqlite or sqlcipher database
630
+ * If you want libsql remote or sync connections, use openSync or openRemote
631
+ */
632
+ export const open = (params: {
493
633
  name: string;
494
634
  location?: string;
495
635
  encryptionKey?: string;
496
636
  }): DB => {
497
- if (options.location?.startsWith('file://')) {
637
+ if (params.location?.startsWith('file://')) {
498
638
  console.warn(
499
639
  "[op-sqlite] You are passing a path with 'file://' prefix, it's automatically removed"
500
640
  );
501
- options.location = options.location.substring(7);
641
+ params.location = params.location.substring(7);
502
642
  }
503
643
 
504
- const db = OPSQLite.open(options);
505
- const enhancedDb = enhanceDB(db, options);
644
+ const db = OPSQLite.open(params);
645
+ const enhancedDb = enhanceDB(db, params);
506
646
 
507
647
  return enhancedDb;
508
648
  };
509
649
 
650
+ /**
651
+ * Moves the database from the assets folder to the default path (check the docs) or to a custom path
652
+ * It DOES NOT OVERWRITE the database if it already exists in the destination path
653
+ * if you want to overwrite the database, you need to pass the overwrite flag as true
654
+ * @param args object with the parameters for the operaiton
655
+ * @returns promise, rejects if failed to move the database, resolves if the operation was successful
656
+ */
510
657
  export const moveAssetsDatabase = async (args: {
511
658
  filename: string;
512
659
  path?: string;
@@ -515,6 +662,14 @@ export const moveAssetsDatabase = async (args: {
515
662
  return NativeModules.OPSQLite.moveAssetsDatabase(args);
516
663
  };
517
664
 
665
+ /**
666
+ * Used to load a dylib file that contains a sqlite 3 extension/plugin
667
+ * It returns the raw path to the actual file which then needs to be passed to the loadExtension function
668
+ * Check the docs for more information
669
+ * @param bundle the iOS bundle identifier of the .framework
670
+ * @param name the file name of the dylib file
671
+ * @returns
672
+ */
518
673
  export const getDylibPath = (bundle: string, name: string): string => {
519
674
  return NativeModules.OPSQLite.getDylibPath(bundle, name);
520
675
  };