expo-sqlite 12.0.0 → 12.2.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 (43) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/android/build.gradle +4 -4
  3. package/android/src/main/cpp/NativeStatementBinding.cpp +19 -8
  4. package/android/src/main/cpp/NativeStatementBinding.h +2 -14
  5. package/android/src/main/java/expo/modules/sqlite/NativeStatementBinding.kt +4 -3
  6. package/android/src/main/java/expo/modules/sqlite/SQLiteModuleNext.kt +19 -12
  7. package/android/src/main/jniLibs/arm64-v8a/libcrsqlite.so +0 -0
  8. package/android/src/main/jniLibs/armeabi-v7a/libcrsqlite.so +0 -0
  9. package/android/src/main/jniLibs/x86/libcrsqlite.so +0 -0
  10. package/android/src/main/jniLibs/x86_64/libcrsqlite.so +0 -0
  11. package/build/SQLite.d.ts +2 -2
  12. package/build/SQLite.d.ts.map +1 -1
  13. package/build/SQLite.js.map +1 -1
  14. package/build/SQLite.types.d.ts +3 -2
  15. package/build/SQLite.types.d.ts.map +1 -1
  16. package/build/SQLite.types.js.map +1 -1
  17. package/build/next/Database.d.ts +91 -73
  18. package/build/next/Database.d.ts.map +1 -1
  19. package/build/next/Database.js +24 -25
  20. package/build/next/Database.js.map +1 -1
  21. package/build/next/NativeDatabase.d.ts +1 -1
  22. package/build/next/NativeDatabase.js.map +1 -1
  23. package/build/next/NativeStatement.d.ts +17 -14
  24. package/build/next/NativeStatement.d.ts.map +1 -1
  25. package/build/next/NativeStatement.js.map +1 -1
  26. package/build/next/Statement.d.ts +65 -43
  27. package/build/next/Statement.d.ts.map +1 -1
  28. package/build/next/Statement.js +76 -27
  29. package/build/next/Statement.js.map +1 -1
  30. package/build/next/hooks.d.ts +25 -0
  31. package/build/next/hooks.d.ts.map +1 -1
  32. package/build/next/hooks.js +39 -11
  33. package/build/next/hooks.js.map +1 -1
  34. package/ios/SQLiteModuleNext.swift +52 -35
  35. package/package.json +2 -2
  36. package/src/SQLite.ts +2 -1
  37. package/src/SQLite.types.ts +5 -2
  38. package/src/next/Database.ts +111 -92
  39. package/src/next/NativeDatabase.ts +1 -1
  40. package/src/next/NativeStatement.ts +25 -14
  41. package/src/next/Statement.ts +133 -66
  42. package/src/next/hooks.tsx +40 -12
  43. package/android/src/main/jniLibs/arm64/libcrsqlite.so +0 -0
@@ -5,12 +5,14 @@ import {
5
5
  NativeStatement,
6
6
  RunResult,
7
7
  VariadicBindParams,
8
+ type ColumnNames,
9
+ type ColumnValues,
8
10
  } from './NativeStatement';
9
11
 
10
12
  export { BindParams, BindValue, RunResult, VariadicBindParams };
11
13
 
12
14
  /**
13
- * A prepared statement returned by `Database.prepareAsync()` that can be binded with parameters and executed.
15
+ * A prepared statement returned by [`Database.prepareAsync()`](#prepareasyncsource) or [`Database.prepareSync()`](#preparesyncsource) that can be binded with parameters and executed.
14
16
  */
15
17
  export class Statement {
16
18
  constructor(
@@ -22,11 +24,13 @@ export class Statement {
22
24
 
23
25
  /**
24
26
  * Run the prepared statement and return the result.
25
- *
26
- * @param params @see `BindParams`
27
+ * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`BindValue`](#bindvalue) for more information about binding values.
27
28
  */
28
- public runAsync(...params: VariadicBindParams): Promise<RunResult>;
29
29
  public runAsync(params: BindParams): Promise<RunResult>;
30
+ /**
31
+ * @hidden
32
+ */
33
+ public runAsync(...params: VariadicBindParams): Promise<RunResult>;
30
34
  public async runAsync(...params: unknown[]): Promise<RunResult> {
31
35
  const { params: bindParams, shouldPassAsObject } = normalizeParams(...params);
32
36
  if (shouldPassAsObject) {
@@ -38,64 +42,78 @@ export class Statement {
38
42
 
39
43
  /**
40
44
  * Iterate the prepared statement and return results as an async iterable.
41
- *
42
- * @param params @see `BindParams`
43
- *
45
+ * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`BindValue`](#bindvalue) for more information about binding values.
44
46
  * @example
45
47
  * ```ts
46
48
  * const statement = await db.prepareAsync('SELECT * FROM test');
47
49
  * for await (const row of statement.eachAsync<any>()) {
48
50
  * console.log(row);
49
51
  * }
52
+ * await statement.finalizeAsync();
50
53
  * ```
51
54
  */
52
- public eachAsync<T>(...params: VariadicBindParams): AsyncIterableIterator<T>;
53
55
  public eachAsync<T>(params: BindParams): AsyncIterableIterator<T>;
56
+ /**
57
+ * @hidden
58
+ */
59
+ public eachAsync<T>(...params: VariadicBindParams): AsyncIterableIterator<T>;
54
60
  public async *eachAsync<T>(...params: unknown[]): AsyncIterableIterator<T> {
55
61
  const { params: bindParams, shouldPassAsObject } = normalizeParams(...params);
56
62
  const func = shouldPassAsObject
57
63
  ? this.nativeStatement.objectGetAsync.bind(this.nativeStatement)
58
64
  : this.nativeStatement.arrayGetAsync.bind(this.nativeStatement);
59
65
 
60
- let result: T | null = null;
66
+ const columnNames = await this.getColumnNamesAsync();
67
+ let result = null;
61
68
  do {
62
69
  result = await func(this.nativeDatabase, bindParams);
63
70
  if (result != null) {
64
- yield result;
71
+ yield composeRow<T>(columnNames, result);
65
72
  }
66
73
  } while (result != null);
67
74
  }
68
75
 
69
76
  /**
70
77
  * Get one row from the prepared statement.
71
- *
72
- * @param params @see `BindParams`
78
+ * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`BindValue`](#bindvalue) for more information about binding values.
73
79
  */
74
- public getAsync<T>(...params: VariadicBindParams): Promise<T | null>;
75
80
  public getAsync<T>(params: BindParams): Promise<T | null>;
81
+ /**
82
+ * @hidden
83
+ */
84
+ public getAsync<T>(...params: VariadicBindParams): Promise<T | null>;
76
85
  public async getAsync<T>(...params: unknown[]): Promise<T | null> {
77
86
  const { params: bindParams, shouldPassAsObject } = normalizeParams(...params);
78
- if (shouldPassAsObject) {
79
- return (await this.nativeStatement.objectGetAsync(this.nativeDatabase, bindParams)) ?? null;
80
- } else {
81
- return (await this.nativeStatement.arrayGetAsync(this.nativeDatabase, bindParams)) ?? null;
82
- }
87
+ const columnNames = await this.getColumnNamesAsync();
88
+ const columnValues = shouldPassAsObject
89
+ ? await this.nativeStatement.objectGetAsync(this.nativeDatabase, bindParams)
90
+ : await this.nativeStatement.arrayGetAsync(this.nativeDatabase, bindParams);
91
+ return columnValues != null ? composeRow<T>(columnNames, columnValues) : null;
83
92
  }
84
93
 
85
94
  /**
86
95
  * Get all rows from the prepared statement.
87
- *
88
- * @param params @see `BindParams`
96
+ * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`BindValue`](#bindvalue) for more information about binding values.
89
97
  */
90
- public allAsync<T>(...params: VariadicBindParams): Promise<T[]>;
91
98
  public allAsync<T>(params: BindParams): Promise<T[]>;
99
+ /**
100
+ * @hidden
101
+ */
102
+ public allAsync<T>(...params: VariadicBindParams): Promise<T[]>;
92
103
  public async allAsync<T>(...params: unknown[]): Promise<T[]> {
93
104
  const { params: bindParams, shouldPassAsObject } = normalizeParams(...params);
94
- if (shouldPassAsObject) {
95
- return await this.nativeStatement.objectGetAllAsync(this.nativeDatabase, bindParams);
96
- } else {
97
- return await this.nativeStatement.arrayGetAllAsync(this.nativeDatabase, bindParams);
98
- }
105
+ const columnNames = await this.getColumnNamesAsync();
106
+ const columnValuesList = shouldPassAsObject
107
+ ? await this.nativeStatement.objectGetAllAsync(this.nativeDatabase, bindParams)
108
+ : await this.nativeStatement.arrayGetAllAsync(this.nativeDatabase, bindParams);
109
+ return composeRows<T>(columnNames, columnValuesList);
110
+ }
111
+
112
+ /**
113
+ * Get the column names of the prepared statement.
114
+ */
115
+ public getColumnNamesAsync(): Promise<string[]> {
116
+ return this.nativeStatement.getColumnNamesAsync();
99
117
  }
100
118
 
101
119
  /**
@@ -119,13 +137,14 @@ export class Statement {
119
137
 
120
138
  /**
121
139
  * Run the prepared statement and return the result.
122
- *
123
- * > **Note:** Running heavy tasks with this function can block the JavaScript thread, affecting performance.
124
- *
125
- * @param params @see `BindParams`
140
+ * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.
141
+ * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`BindValue`](#bindvalue) for more information about binding values.
126
142
  */
127
- public runSync(...params: VariadicBindParams): RunResult;
128
143
  public runSync(params: BindParams): RunResult;
144
+ /**
145
+ * @hidden
146
+ */
147
+ public runSync(...params: VariadicBindParams): RunResult;
129
148
  public runSync(...params: unknown[]): RunResult {
130
149
  const { params: bindParams, shouldPassAsObject } = normalizeParams(...params);
131
150
  if (shouldPassAsObject) {
@@ -137,70 +156,73 @@ export class Statement {
137
156
 
138
157
  /**
139
158
  * Iterate the prepared statement and return results as an iterable.
140
- *
141
- * > **Note:** Running heavy tasks with this function can block the JavaScript thread, affecting performance.
142
- *
143
- * @param params @see `BindParams`
144
- *
145
- * @example
146
- * ```ts
147
- * const statement = await db.prepareSync('SELECT * FROM test');
148
- * for (const row of statement.eachSync<any>()) {
149
- * console.log(row);
150
- * }
151
- * ```
159
+ * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.
160
+ * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`BindValue`](#bindvalue) for more information about binding values.
152
161
  */
153
- public eachSync<T>(...params: VariadicBindParams): IterableIterator<T>;
154
162
  public eachSync<T>(params: BindParams): IterableIterator<T>;
163
+ /**
164
+ * @hidden
165
+ */
166
+ public eachSync<T>(...params: VariadicBindParams): IterableIterator<T>;
155
167
  public *eachSync<T>(...params: unknown[]): IterableIterator<T> {
156
168
  const { params: bindParams, shouldPassAsObject } = normalizeParams(...params);
157
169
  const func = shouldPassAsObject
158
170
  ? this.nativeStatement.objectGetSync.bind(this.nativeStatement)
159
171
  : this.nativeStatement.arrayGetSync.bind(this.nativeStatement);
160
172
 
161
- let result: T | null = null;
173
+ const columnNames = this.getColumnNamesSync();
174
+ let result = null;
162
175
  do {
163
176
  result = func(this.nativeDatabase, bindParams);
164
177
  if (result != null) {
165
- yield result;
178
+ yield composeRow<T>(columnNames, result);
166
179
  }
167
180
  } while (result != null);
168
181
  }
169
182
 
170
183
  /**
171
184
  * Get one row from the prepared statement.
172
- *
173
- * > **Note:** Running heavy tasks with this function can block the JavaScript thread, affecting performance.
174
- *
175
- * @param params @see `BindParams`
185
+ * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.
186
+ * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`BindValue`](#bindvalue) for more information about binding values.
176
187
  */
177
- public getSync<T>(...params: VariadicBindParams): T | null;
178
188
  public getSync<T>(params: BindParams): T | null;
189
+ /**
190
+ * @hidden
191
+ */
192
+ public getSync<T>(...params: VariadicBindParams): T | null;
179
193
  public getSync<T>(...params: unknown[]): T | null {
180
194
  const { params: bindParams, shouldPassAsObject } = normalizeParams(...params);
181
- if (shouldPassAsObject) {
182
- return this.nativeStatement.objectGetSync(this.nativeDatabase, bindParams) ?? null;
183
- } else {
184
- return this.nativeStatement.arrayGetSync(this.nativeDatabase, bindParams) ?? null;
185
- }
195
+ const columnNames = this.getColumnNamesSync();
196
+ const columnValues = shouldPassAsObject
197
+ ? this.nativeStatement.objectGetSync(this.nativeDatabase, bindParams)
198
+ : this.nativeStatement.arrayGetSync(this.nativeDatabase, bindParams);
199
+ return columnValues != null ? composeRow<T>(columnNames, columnValues) : null;
186
200
  }
187
201
 
188
202
  /**
189
203
  * Get all rows from the prepared statement.
190
- *
191
- * > **Note:** Running heavy tasks with this function can block the JavaScript thread, affecting performance.
192
- *
193
- * @param params @see `BindParams`
204
+ * > **Note:** Running heavy tasks with this function can block the JavaScript thread and affect performance.
205
+ * @param params The parameters to bind to the prepared statement. You can pass values in array, object, or variadic arguments. See [`BindValue`](#bindvalue) for more information about binding values.
194
206
  */
195
- public allSync<T>(...params: VariadicBindParams): T[];
196
207
  public allSync<T>(params: BindParams): T[];
208
+ /**
209
+ * @hidden
210
+ */
211
+ public allSync<T>(...params: VariadicBindParams): T[];
197
212
  public allSync<T>(...params: unknown[]): T[] {
198
213
  const { params: bindParams, shouldPassAsObject } = normalizeParams(...params);
199
- if (shouldPassAsObject) {
200
- return this.nativeStatement.objectGetAllSync(this.nativeDatabase, bindParams);
201
- } else {
202
- return this.nativeStatement.arrayGetAllSync(this.nativeDatabase, bindParams);
203
- }
214
+ const columnNames = this.getColumnNamesSync();
215
+ const columnValuesList = shouldPassAsObject
216
+ ? this.nativeStatement.objectGetAllSync(this.nativeDatabase, bindParams)
217
+ : this.nativeStatement.arrayGetAllSync(this.nativeDatabase, bindParams);
218
+ return composeRows<T>(columnNames, columnValuesList);
219
+ }
220
+
221
+ /**
222
+ * Get the column names of the prepared statement.
223
+ */
224
+ public getColumnNamesSync(): string[] {
225
+ return this.nativeStatement.getColumnNamesSync();
204
226
  }
205
227
 
206
228
  /**
@@ -232,6 +254,9 @@ export function normalizeParams(...params: any[]): {
232
254
  shouldPassAsObject: boolean;
233
255
  } {
234
256
  let bindParams = params.length > 1 ? params : (params[0] as BindParams);
257
+ if (bindParams == null) {
258
+ bindParams = [];
259
+ }
235
260
  if (typeof bindParams !== 'object') {
236
261
  bindParams = [bindParams];
237
262
  }
@@ -241,3 +266,45 @@ export function normalizeParams(...params: any[]): {
241
266
  shouldPassAsObject,
242
267
  };
243
268
  }
269
+
270
+ /**
271
+ * Compose `columnNames` and `columnValues` to an row object.
272
+ * @hidden
273
+ */
274
+ export function composeRow<T>(columnNames: ColumnNames, columnValues: ColumnValues): T {
275
+ const row = {};
276
+ if (columnNames.length !== columnValues.length) {
277
+ throw new Error(
278
+ `Column names and values count mismatch. Names: ${columnNames.length}, Values: ${columnValues.length}`
279
+ );
280
+ }
281
+ for (let i = 0; i < columnNames.length; i++) {
282
+ row[columnNames[i]] = columnValues[i];
283
+ }
284
+ return row as T;
285
+ }
286
+
287
+ /**
288
+ * Compose `columnNames` and `columnValuesList` to an array of row objects.
289
+ * @hidden
290
+ */
291
+ export function composeRows<T>(columnNames: ColumnNames, columnValuesList: ColumnValues[]): T[] {
292
+ if (columnValuesList.length === 0) {
293
+ return [];
294
+ }
295
+ if (columnNames.length !== columnValuesList[0].length) {
296
+ // We only check the first row because SQLite returns the same column count for all rows.
297
+ throw new Error(
298
+ `Column names and values count mismatch. Names: ${columnNames.length}, Values: ${columnValuesList[0].length}`
299
+ );
300
+ }
301
+ const results: T[] = [];
302
+ for (const columnValues of columnValuesList) {
303
+ const row = {};
304
+ for (let i = 0; i < columnNames.length; i++) {
305
+ row[columnNames[i]] = columnValues[i];
306
+ }
307
+ results.push(row as T);
308
+ }
309
+ return results;
310
+ }
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useEffect, useState } from 'react';
1
+ import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
2
2
 
3
3
  import { openDatabaseAsync, type Database } from './Database';
4
4
  import type { OpenOptions } from './NativeDatabase';
@@ -38,10 +38,15 @@ export interface SQLiteProviderProps {
38
38
  errorHandler?: (error: Error) => void;
39
39
  }
40
40
 
41
- // Create a context for the SQLite database
41
+ /**
42
+ * Create a context for the SQLite database
43
+ */
42
44
  const SQLiteContext = createContext<Database | null>(null);
43
45
 
44
- // Create a provider component
46
+ /**
47
+ * Context.Provider component that provides a SQLite database to all children.
48
+ * All descendants of this component will be able to access the database using the [`useSQLiteContext`](#usesqlitecontext) hook.
49
+ */
45
50
  export function SQLiteProvider({
46
51
  dbName,
47
52
  options,
@@ -50,7 +55,7 @@ export function SQLiteProvider({
50
55
  loadingFallback,
51
56
  errorHandler,
52
57
  }: SQLiteProviderProps) {
53
- const [database, setDatabase] = useState<Database | null>(null);
58
+ const databaseRef = useRef<Database | null>(null);
54
59
  const [loading, setLoading] = useState(true);
55
60
  const [error, setError] = useState<Error | null>(null);
56
61
 
@@ -58,19 +63,19 @@ export function SQLiteProvider({
58
63
  async function setup() {
59
64
  try {
60
65
  const db = await openDatabaseAsync(dbName, options);
61
- setDatabase(db);
62
66
  if (initHandler != null) {
63
67
  await initHandler(db);
64
68
  }
69
+ databaseRef.current = db;
65
70
  setLoading(false);
66
71
  } catch (e) {
67
72
  setError(e);
68
73
  }
69
74
  }
70
75
 
71
- async function teardown() {
76
+ async function teardown(db: Database | null) {
72
77
  try {
73
- await database?.closeAsync();
78
+ await db?.closeAsync();
74
79
  } catch (e) {
75
80
  setError(e);
76
81
  }
@@ -79,7 +84,10 @@ export function SQLiteProvider({
79
84
  setup();
80
85
 
81
86
  return () => {
82
- teardown();
87
+ const db = databaseRef.current;
88
+ teardown(db);
89
+ databaseRef.current = null;
90
+ setLoading(true);
83
91
  };
84
92
  }, [dbName, options, initHandler]);
85
93
 
@@ -92,14 +100,34 @@ export function SQLiteProvider({
92
100
  handler(error);
93
101
  }
94
102
 
95
- if (loading) {
103
+ if (loading || !databaseRef.current) {
96
104
  return loadingFallback != null ? <>{loadingFallback}</> : null;
97
105
  }
98
- return <SQLiteContext.Provider value={database}>{children}</SQLiteContext.Provider>;
106
+ return <SQLiteContext.Provider value={databaseRef.current}>{children}</SQLiteContext.Provider>;
99
107
  }
100
108
 
101
- // Create a hook for accessing the SQLite database context
102
- export function useSQLiteContext() {
109
+ /**
110
+ * A global hook for accessing the SQLite database across components.
111
+ * This hook should only be used within a [`<SQLiteProvider>`](#sqliteprovider) component.
112
+ *
113
+ * @example
114
+ * ```tsx
115
+ * export default function App() {
116
+ * return (
117
+ * <SQLiteProvider dbName="test.db">
118
+ * <Main />
119
+ * </SQLiteProvider>
120
+ * );
121
+ * }
122
+ *
123
+ * export function Main() {
124
+ * const db = useSQLiteContext();
125
+ * console.log('sqlite version', db.getSync('SELECT sqlite_version()'));
126
+ * return <View />
127
+ * }
128
+ * ```
129
+ */
130
+ export function useSQLiteContext(): Database {
103
131
  const context = useContext(SQLiteContext);
104
132
  if (context == null) {
105
133
  throw new Error('useSQLiteContext must be used within a <SQLiteProvider>');