@op-engineering/op-sqlite 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.
Files changed (39) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +274 -0
  3. package/android/.project +17 -0
  4. package/android/.settings/org.eclipse.buildship.core.prefs +13 -0
  5. package/android/CMakeLists.txt +57 -0
  6. package/android/build.gradle +127 -0
  7. package/android/cpp-adapter.cpp +42 -0
  8. package/android/gradle.properties +4 -0
  9. package/android/src/main/AndroidManifest.xml +4 -0
  10. package/android/src/main/java/com/op/sqlite/OPSQLiteBridge.java +29 -0
  11. package/android/src/main/java/com/op/sqlite/OPSQLiteModule.java +46 -0
  12. package/android/src/main/java/com/op/sqlite/OPSQLitePackage.java +26 -0
  13. package/cpp/DynamicHostObject.cpp +30 -0
  14. package/cpp/DynamicHostObject.h +30 -0
  15. package/cpp/ThreadPool.cpp +95 -0
  16. package/cpp/ThreadPool.h +46 -0
  17. package/cpp/bindings.cpp +430 -0
  18. package/cpp/bindings.h +13 -0
  19. package/cpp/bridge.cpp +502 -0
  20. package/cpp/bridge.h +34 -0
  21. package/cpp/logs.h +38 -0
  22. package/cpp/macros.h +16 -0
  23. package/cpp/sqlbatchexecutor.cpp +94 -0
  24. package/cpp/sqlbatchexecutor.h +28 -0
  25. package/cpp/sqlite3.c +252611 -0
  26. package/cpp/sqlite3.h +13257 -0
  27. package/cpp/utils.cpp +218 -0
  28. package/cpp/utils.h +55 -0
  29. package/ios/OPSQLite.h +8 -0
  30. package/ios/OPSQLite.mm +63 -0
  31. package/ios/OPSQLite.xcodeproj/project.pbxproj +275 -0
  32. package/lib/commonjs/index.js +190 -0
  33. package/lib/commonjs/index.js.map +1 -0
  34. package/lib/module/index.js +183 -0
  35. package/lib/module/index.js.map +1 -0
  36. package/lib/typescript/index.d.ts +108 -0
  37. package/op-sqlite.podspec +39 -0
  38. package/package.json +79 -0
  39. package/src/index.ts +374 -0
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@op-engineering/op-sqlite",
3
+ "version": "1.0.0",
4
+ "description": "Next generation SQLite for React Native",
5
+ "main": "lib/commonjs/index",
6
+ "module": "lib/module/index",
7
+ "types": "lib/typescript/index.d.ts",
8
+ "react-native": "src/index",
9
+ "source": "src/index",
10
+ "files": [
11
+ "src",
12
+ "lib",
13
+ "android",
14
+ "ios",
15
+ "cpp",
16
+ "op-sqlite.podspec",
17
+ "!lib/typescript/example",
18
+ "!android/build",
19
+ "!android/.cxx",
20
+ "!ios/build",
21
+ "!**/__tests__",
22
+ "!**/__fixtures__",
23
+ "!**/__mocks__"
24
+ ],
25
+ "scripts": {
26
+ "typescript": "tsc --noEmit",
27
+ "prepare": "bob build",
28
+ "example": "yarn --cwd example",
29
+ "pods": "cd example && npx pod-install --quiet",
30
+ "bootstrap": "yarn example && yarn && yarn pods",
31
+ "bump": "./bump-version.sh"
32
+ },
33
+ "keywords": [
34
+ "react-native",
35
+ "ios",
36
+ "android"
37
+ ],
38
+ "repository": "https://github.com/OP-Engineering/op-sqlite",
39
+ "author": "Oscar Franco <ospfranco@protonmail.com> (https://github.com/ospfranco)",
40
+ "license": "MIT",
41
+ "bugs": {
42
+ "url": "https://github.com/OP-Engineering/op-sqlite/issues"
43
+ },
44
+ "homepage": "https://github.com/OP-Engineering/op-sqlite#readme",
45
+ "publishConfig": {
46
+ "registry": "https://registry.npmjs.org/"
47
+ },
48
+ "devDependencies": {
49
+ "react": "18.2.0",
50
+ "react-native": "0.72.6",
51
+ "react-native-builder-bob": "^0.18.2",
52
+ "typescript": "^4.8.4"
53
+ },
54
+ "peerDependencies": {
55
+ "react": "*",
56
+ "react-native": "*"
57
+ },
58
+ "prettier": {
59
+ "quoteProps": "consistent",
60
+ "singleQuote": true,
61
+ "tabWidth": 2,
62
+ "trailingComma": "es5",
63
+ "useTabs": false
64
+ },
65
+ "react-native-builder-bob": {
66
+ "source": "src",
67
+ "output": "lib",
68
+ "targets": [
69
+ "commonjs",
70
+ "module",
71
+ [
72
+ "typescript",
73
+ {
74
+ "project": "tsconfig.build.json"
75
+ }
76
+ ]
77
+ ]
78
+ }
79
+ }
package/src/index.ts ADDED
@@ -0,0 +1,374 @@
1
+ import { NativeModules } from 'react-native';
2
+
3
+ declare global {
4
+ function nativeCallSyncHook(): unknown;
5
+ var __OPSQLiteProxy: object | undefined;
6
+ }
7
+
8
+ if (global.__OPSQLiteProxy == null) {
9
+ const OPSQLiteModule = NativeModules.OPSQLite;
10
+
11
+ if (OPSQLiteModule == null) {
12
+ throw new Error('Base module not found. Maybe try rebuilding the app.');
13
+ }
14
+
15
+ // Check if we are running on-device (JSI)
16
+ if (global.nativeCallSyncHook == null || OPSQLiteModule.install == null) {
17
+ throw new Error(
18
+ '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.'
19
+ );
20
+ }
21
+
22
+ // Call the synchronous blocking install() function
23
+ const result = OPSQLiteModule.install();
24
+ if (result !== true) {
25
+ throw new Error(
26
+ `Failed to install op-sqlite: The native OPSQLite Module could not be installed! Looks like something went wrong when installing JSI bindings: ${result}`
27
+ );
28
+ }
29
+
30
+ // Check again if the constructor now exists. If not, throw an error.
31
+ if (global.__OPSQLiteProxy == null) {
32
+ throw new Error(
33
+ 'Failed to install op-sqlite, the native initializer function does not exist. Are you trying to use OPSQLite from different JS Runtimes?'
34
+ );
35
+ }
36
+ }
37
+
38
+ const proxy = global.__OPSQLiteProxy;
39
+ export const OPSQLite = proxy as ISQLite;
40
+
41
+ /**
42
+ * Object returned by SQL Query executions {
43
+ * insertId: Represent the auto-generated row id if applicable
44
+ * rowsAffected: Number of affected rows if result of a update query
45
+ * message: if status === 1, here you will find error description
46
+ * rows: if status is undefined or 0 this object will contain the query results
47
+ * }
48
+ *
49
+ * @interface QueryResult
50
+ */
51
+ export type QueryResult = {
52
+ insertId?: number;
53
+ rowsAffected: number;
54
+ rows?: {
55
+ /** Raw array with all dataset */
56
+ _array: any[];
57
+ /** The lengh of the dataset */
58
+ length: number;
59
+ /** A convenience function to acess the index based the row object
60
+ * @param idx the row index
61
+ * @returns the row structure identified by column names
62
+ */
63
+ item: (idx: number) => any;
64
+ };
65
+ /**
66
+ * Query metadata, avaliable only for select query results
67
+ */
68
+ metadata?: ColumnMetadata[];
69
+ };
70
+
71
+ /**
72
+ * Column metadata
73
+ * Describes some information about columns fetched by the query
74
+ */
75
+ export type ColumnMetadata = {
76
+ /** The name used for this column for this resultset */
77
+ name: string;
78
+ /** The declared column type for this column, when fetched directly from a table or a View resulting from a table column. "UNKNOWN" for dynamic values, like function returned ones. */
79
+ type: string;
80
+ /**
81
+ * The index for this column for this resultset*/
82
+ index: number;
83
+ };
84
+
85
+ /**
86
+ * Allows the execution of bulk of sql commands
87
+ * inside a transaction
88
+ * If a single query must be executed many times with different arguments, its preferred
89
+ * to declare it a single time, and use an array of array parameters.
90
+ */
91
+ export type SQLBatchTuple = [string] | [string, Array<any> | Array<Array<any>>];
92
+
93
+ /**
94
+ * status: 0 or undefined for correct execution, 1 for error
95
+ * message: if status === 1, here you will find error description
96
+ * rowsAffected: Number of affected rows if status == 0
97
+ */
98
+ export type BatchQueryResult = {
99
+ rowsAffected?: number;
100
+ };
101
+
102
+ /**
103
+ * Result of loading a file and executing every line as a SQL command
104
+ * Similar to BatchQueryResult
105
+ */
106
+ export interface FileLoadResult extends BatchQueryResult {
107
+ commands?: number;
108
+ }
109
+
110
+ export interface Transaction {
111
+ commit: () => QueryResult;
112
+ execute: (query: string, params?: any[]) => QueryResult;
113
+ executeAsync: (
114
+ query: string,
115
+ params?: any[] | undefined
116
+ ) => Promise<QueryResult>;
117
+ rollback: () => QueryResult;
118
+ }
119
+
120
+ export interface PendingTransaction {
121
+ /*
122
+ * The start function should not throw or return a promise because the
123
+ * queue just calls it and does not monitor for failures or completions.
124
+ *
125
+ * It should catch any errors and call the resolve or reject of the wrapping
126
+ * promise when complete.
127
+ *
128
+ * It should also automatically commit or rollback the transaction if needed
129
+ */
130
+ start: () => void;
131
+ }
132
+
133
+ interface ISQLite {
134
+ open: (dbName: string, location?: string) => void;
135
+ close: (dbName: string) => void;
136
+ delete: (dbName: string, location?: string) => void;
137
+ attach: (
138
+ mainDbName: string,
139
+ dbNameToAttach: string,
140
+ alias: string,
141
+ location?: string
142
+ ) => void;
143
+ detach: (mainDbName: string, alias: string) => void;
144
+ transaction: (
145
+ dbName: string,
146
+ fn: (tx: Transaction) => Promise<void> | void
147
+ ) => Promise<void>;
148
+ execute: (dbName: string, query: string, params?: any[]) => QueryResult;
149
+ executeAsync: (
150
+ dbName: string,
151
+ query: string,
152
+ params?: any[]
153
+ ) => Promise<QueryResult>;
154
+ executeBatch: (dbName: string, commands: SQLBatchTuple[]) => BatchQueryResult;
155
+ executeBatchAsync: (
156
+ dbName: string,
157
+ commands: SQLBatchTuple[]
158
+ ) => Promise<BatchQueryResult>;
159
+ loadFile: (dbName: string, location: string) => Promise<FileLoadResult>;
160
+ }
161
+
162
+ const locks: Record<
163
+ string,
164
+ { queue: PendingTransaction[]; inProgress: boolean }
165
+ > = {};
166
+
167
+ // Enhance some host functions
168
+
169
+ // Add 'item' function to result object to allow the sqlite-storage typeorm driver to work
170
+ const enhanceQueryResult = (result: QueryResult): void => {
171
+ // Add 'item' function to result object to allow the sqlite-storage typeorm driver to work
172
+ if (result.rows == null) {
173
+ result.rows = {
174
+ _array: [],
175
+ length: 0,
176
+ item: (idx: number) => result.rows._array[idx],
177
+ };
178
+ } else {
179
+ result.rows.item = (idx: number) => result.rows._array[idx];
180
+ }
181
+ };
182
+
183
+ const _open = OPSQLite.open;
184
+ OPSQLite.open = (dbName: string, location?: string) => {
185
+ _open(dbName, location);
186
+
187
+ locks[dbName] = {
188
+ queue: [],
189
+ inProgress: false,
190
+ };
191
+ };
192
+
193
+ const _close = OPSQLite.close;
194
+ OPSQLite.close = (dbName: string) => {
195
+ _close(dbName);
196
+ delete locks[dbName];
197
+ };
198
+
199
+ const _execute = OPSQLite.execute;
200
+ OPSQLite.execute = (
201
+ dbName: string,
202
+ query: string,
203
+ params?: any[] | undefined
204
+ ): QueryResult => {
205
+ const result = _execute(dbName, query, params);
206
+ enhanceQueryResult(result);
207
+ return result;
208
+ };
209
+
210
+ const _executeAsync = OPSQLite.executeAsync;
211
+ OPSQLite.executeAsync = async (
212
+ dbName: string,
213
+ query: string,
214
+ params?: any[] | undefined
215
+ ): Promise<QueryResult> => {
216
+ const res = await _executeAsync(dbName, query, params);
217
+ enhanceQueryResult(res);
218
+ return res;
219
+ };
220
+
221
+ OPSQLite.transaction = async (
222
+ dbName: string,
223
+ fn: (tx: Transaction) => Promise<void>
224
+ ): Promise<void> => {
225
+ if (!locks[dbName]) {
226
+ throw Error(`SQLite Error: No lock found on db: ${dbName}`);
227
+ }
228
+
229
+ let isFinalized = false;
230
+
231
+ // Local transaction context object implementation
232
+ const execute = (query: string, params?: any[]): QueryResult => {
233
+ if (isFinalized) {
234
+ throw Error(
235
+ `SQLite Error: Cannot execute query on finalized transaction: ${dbName}`
236
+ );
237
+ }
238
+ return OPSQLite.execute(dbName, query, params);
239
+ };
240
+
241
+ const executeAsync = (query: string, params?: any[] | undefined) => {
242
+ if (isFinalized) {
243
+ throw Error(
244
+ `SQLite Error: Cannot execute query on finalized transaction: ${dbName}`
245
+ );
246
+ }
247
+ return OPSQLite.executeAsync(dbName, query, params);
248
+ };
249
+
250
+ const commit = () => {
251
+ if (isFinalized) {
252
+ throw Error(
253
+ `SQLite Error: Cannot execute commit on finalized transaction: ${dbName}`
254
+ );
255
+ }
256
+ const result = OPSQLite.execute(dbName, 'COMMIT');
257
+ isFinalized = true;
258
+ return result;
259
+ };
260
+
261
+ const rollback = () => {
262
+ if (isFinalized) {
263
+ throw Error(
264
+ `SQLite Error: Cannot execute rollback on finalized transaction: ${dbName}`
265
+ );
266
+ }
267
+ const result = OPSQLite.execute(dbName, 'ROLLBACK');
268
+ isFinalized = true;
269
+ return result;
270
+ };
271
+
272
+ async function run() {
273
+ try {
274
+ await OPSQLite.executeAsync(dbName, 'BEGIN TRANSACTION');
275
+
276
+ await fn({
277
+ commit,
278
+ execute,
279
+ executeAsync,
280
+ rollback,
281
+ });
282
+
283
+ if (!isFinalized) {
284
+ commit();
285
+ }
286
+ } catch (executionError) {
287
+ if (!isFinalized) {
288
+ try {
289
+ rollback();
290
+ } catch (rollbackError) {
291
+ throw rollbackError;
292
+ }
293
+ }
294
+
295
+ throw executionError;
296
+ } finally {
297
+ locks[dbName].inProgress = false;
298
+ isFinalized = false;
299
+ startNextTransaction(dbName);
300
+ }
301
+ }
302
+
303
+ return await new Promise((resolve, reject) => {
304
+ const tx: PendingTransaction = {
305
+ start: () => {
306
+ run().then(resolve).catch(reject);
307
+ },
308
+ };
309
+
310
+ locks[dbName].queue.push(tx);
311
+ startNextTransaction(dbName);
312
+ });
313
+ };
314
+
315
+ const startNextTransaction = (dbName: string) => {
316
+ if (!locks[dbName]) {
317
+ throw Error(`Lock not found for db: ${dbName}`);
318
+ }
319
+
320
+ if (locks[dbName].inProgress) {
321
+ // Transaction is already in process bail out
322
+ return;
323
+ }
324
+
325
+ if (locks[dbName].queue.length) {
326
+ locks[dbName].inProgress = true;
327
+ const tx = locks[dbName].queue.shift();
328
+ setImmediate(() => {
329
+ tx.start();
330
+ });
331
+ }
332
+ };
333
+
334
+ export type OPSQLiteConnection = {
335
+ close: () => void;
336
+ delete: () => void;
337
+ attach: (dbNameToAttach: string, alias: string, location?: string) => void;
338
+ detach: (alias: string) => void;
339
+ transaction: (fn: (tx: Transaction) => Promise<void> | void) => Promise<void>;
340
+ execute: (query: string, params?: any[]) => QueryResult;
341
+ executeAsync: (query: string, params?: any[]) => Promise<QueryResult>;
342
+ executeBatch: (commands: SQLBatchTuple[]) => BatchQueryResult;
343
+ executeBatchAsync: (commands: SQLBatchTuple[]) => Promise<BatchQueryResult>;
344
+ loadFile: (location: string) => Promise<FileLoadResult>;
345
+ };
346
+
347
+ export const open = (options: {
348
+ name: string;
349
+ location?: string;
350
+ }): OPSQLiteConnection => {
351
+ OPSQLite.open(options.name, options.location);
352
+
353
+ return {
354
+ close: () => OPSQLite.close(options.name),
355
+ delete: () => OPSQLite.delete(options.name, options.location),
356
+ attach: (dbNameToAttach: string, alias: string, location?: string) =>
357
+ OPSQLite.attach(options.name, dbNameToAttach, alias, location),
358
+ detach: (alias: string) => OPSQLite.detach(options.name, alias),
359
+ transaction: (fn: (tx: Transaction) => Promise<void> | void) =>
360
+ OPSQLite.transaction(options.name, fn),
361
+ execute: (query: string, params?: any[] | undefined): QueryResult =>
362
+ OPSQLite.execute(options.name, query, params),
363
+ executeAsync: (
364
+ query: string,
365
+ params?: any[] | undefined
366
+ ): Promise<QueryResult> =>
367
+ OPSQLite.executeAsync(options.name, query, params),
368
+ executeBatch: (commands: SQLBatchTuple[]) =>
369
+ OPSQLite.executeBatch(options.name, commands),
370
+ executeBatchAsync: (commands: SQLBatchTuple[]) =>
371
+ OPSQLite.executeBatchAsync(options.name, commands),
372
+ loadFile: (location: string) => OPSQLite.loadFile(options.name, location),
373
+ };
374
+ };