@kubun/db-expo 0.10.0 → 0.12.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/lib/driver.js +139 -1
- package/lib/index.js +29 -1
- package/lib/serialize.js +27 -1
- package/package.json +12 -12
package/lib/driver.js
CHANGED
|
@@ -1 +1,139 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import * as SQLite from 'expo-sqlite';
|
|
2
|
+
// Adapted from https://github.com/mphill/kysely-expo/blob/main/src/driver.ts
|
|
3
|
+
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from 'kysely';
|
|
4
|
+
import { serialize } from './serialize.js';
|
|
5
|
+
/**
|
|
6
|
+
* Expo dialect for Kysely.
|
|
7
|
+
*/ export class ExpoDialect {
|
|
8
|
+
config;
|
|
9
|
+
constructor(config){
|
|
10
|
+
this.config = {
|
|
11
|
+
...config,
|
|
12
|
+
disableStrictModeCreateTable: true
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
createDriver() {
|
|
16
|
+
return new ExpoDriver(this.config);
|
|
17
|
+
}
|
|
18
|
+
createQueryCompiler() {
|
|
19
|
+
return new SqliteQueryCompiler();
|
|
20
|
+
}
|
|
21
|
+
createAdapter() {
|
|
22
|
+
return new SqliteAdapter();
|
|
23
|
+
}
|
|
24
|
+
createIntrospector(db) {
|
|
25
|
+
return new SqliteIntrospector(db);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Expo driver for Kysely.
|
|
30
|
+
*/ export class ExpoDriver {
|
|
31
|
+
#connectionMutex = new ConnectionMutex();
|
|
32
|
+
#connection;
|
|
33
|
+
constructor(config){
|
|
34
|
+
this.#connection = new ExpoConnection(config);
|
|
35
|
+
}
|
|
36
|
+
async releaseConnection() {
|
|
37
|
+
this.#connectionMutex.unlock();
|
|
38
|
+
}
|
|
39
|
+
async init() {}
|
|
40
|
+
async acquireConnection() {
|
|
41
|
+
await this.#connectionMutex.lock();
|
|
42
|
+
return this.#connection;
|
|
43
|
+
}
|
|
44
|
+
async beginTransaction(connection) {
|
|
45
|
+
await connection.directQuery('begin transaction');
|
|
46
|
+
}
|
|
47
|
+
async commitTransaction(connection) {
|
|
48
|
+
await connection.directQuery('commit');
|
|
49
|
+
}
|
|
50
|
+
async rollbackTransaction(connection) {
|
|
51
|
+
await connection.directQuery('rollback');
|
|
52
|
+
}
|
|
53
|
+
async destroy() {
|
|
54
|
+
this.#connection.closeConnection();
|
|
55
|
+
}
|
|
56
|
+
async getDatabaseRuntimeVersion() {
|
|
57
|
+
try {
|
|
58
|
+
const res = await this.#connection.directQuery('select sqlite_version() as version;');
|
|
59
|
+
//@ts-expect-error
|
|
60
|
+
return res[0].version;
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error(e);
|
|
63
|
+
return 'unknown';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Expo connection for Kysely.
|
|
69
|
+
*/ class ExpoConnection {
|
|
70
|
+
sqlite;
|
|
71
|
+
debug;
|
|
72
|
+
config;
|
|
73
|
+
constructor(config){
|
|
74
|
+
this.sqlite = SQLite.openDatabaseSync(config.database);
|
|
75
|
+
this.debug = config.debug ?? false;
|
|
76
|
+
this.config = config;
|
|
77
|
+
if (this.config.disableForeignKeys) {
|
|
78
|
+
this.sqlite.execSync('PRAGMA foreign_keys = OFF;');
|
|
79
|
+
} else {
|
|
80
|
+
this.sqlite.execSync('PRAGMA foreign_keys = ON;');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async closeConnection() {
|
|
84
|
+
return this.sqlite.closeAsync();
|
|
85
|
+
}
|
|
86
|
+
async executeQuery(compiledQuery) {
|
|
87
|
+
let { sql, parameters, query } = compiledQuery;
|
|
88
|
+
// Kysely uses varchar(255) as the default string type for migrations which is not supported by STRICT mode.
|
|
89
|
+
if (query.kind === 'CreateTableNode' && !sql.includes('kysely_migration') && !sql.includes('kysely_migration_lock') && !sql.includes('STRICT') && !this.config.disableStrictModeCreateTable) {
|
|
90
|
+
sql += ' STRICT';
|
|
91
|
+
}
|
|
92
|
+
const readonly = query.kind === 'SelectQueryNode' || query.kind === 'RawNode';
|
|
93
|
+
const hasReturning = !readonly && sql.includes(' returning ');
|
|
94
|
+
const transformedParameters = serialize([
|
|
95
|
+
...parameters
|
|
96
|
+
]);
|
|
97
|
+
if (this.debug) {
|
|
98
|
+
console.debug(`${query.kind}${readonly ? ' (readonly)' : ''}${hasReturning ? ' (returning)' : ''}: ${sql}`);
|
|
99
|
+
}
|
|
100
|
+
if (readonly || hasReturning) {
|
|
101
|
+
const res = await this.sqlite.getAllAsync(sql, transformedParameters);
|
|
102
|
+
return {
|
|
103
|
+
rows: res
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const res = await this.sqlite.runAsync(sql, transformedParameters);
|
|
107
|
+
const queryResult = {
|
|
108
|
+
numAffectedRows: BigInt(res.changes),
|
|
109
|
+
insertId: BigInt(res.lastInsertRowId),
|
|
110
|
+
rows: []
|
|
111
|
+
};
|
|
112
|
+
if (this.debug) console.log('queryResult', queryResult);
|
|
113
|
+
return queryResult;
|
|
114
|
+
}
|
|
115
|
+
async directQuery(query) {
|
|
116
|
+
return await this.sqlite.getAllAsync(query, []);
|
|
117
|
+
}
|
|
118
|
+
streamQuery(_compiledQuery, _chunkSize) {
|
|
119
|
+
throw new Error('Expo SQLite driver does not support iterate on prepared statements');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
class ConnectionMutex {
|
|
123
|
+
#promise;
|
|
124
|
+
#resolve;
|
|
125
|
+
async lock() {
|
|
126
|
+
while(this.#promise){
|
|
127
|
+
await this.#promise;
|
|
128
|
+
}
|
|
129
|
+
this.#promise = new Promise((resolve)=>{
|
|
130
|
+
this.#resolve = resolve;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
unlock() {
|
|
134
|
+
const resolve = this.#resolve;
|
|
135
|
+
this.#promise = undefined;
|
|
136
|
+
this.#resolve = undefined;
|
|
137
|
+
resolve?.();
|
|
138
|
+
}
|
|
139
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1 +1,29 @@
|
|
|
1
|
-
import{AbstractSQLiteAdapter
|
|
1
|
+
import { AbstractSQLiteAdapter } from '@kubun/db-adapter';
|
|
2
|
+
import { ExpoDialect } from './driver.js';
|
|
3
|
+
export class ExpoAdapter extends AbstractSQLiteAdapter {
|
|
4
|
+
#dialect;
|
|
5
|
+
#searchWarned = false;
|
|
6
|
+
constructor(params){
|
|
7
|
+
super();
|
|
8
|
+
this.#dialect = new ExpoDialect(params);
|
|
9
|
+
}
|
|
10
|
+
get dialect() {
|
|
11
|
+
return this.#dialect;
|
|
12
|
+
}
|
|
13
|
+
#warnOnce() {
|
|
14
|
+
if (!this.#searchWarned) {
|
|
15
|
+
console.warn('ExpoAdapter: full-text search is not supported in Expo environments');
|
|
16
|
+
this.#searchWarned = true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async createSearchIndex(_db, _config) {
|
|
20
|
+
this.#warnOnce();
|
|
21
|
+
}
|
|
22
|
+
async dropSearchIndex(_db, _modelID) {}
|
|
23
|
+
async updateSearchEntry(_db, _modelID, _documentID, _fields, _fieldNames) {}
|
|
24
|
+
async removeSearchEntry(_db, _modelID, _documentID) {}
|
|
25
|
+
async searchIndex(_db, _params) {
|
|
26
|
+
this.#warnOnce();
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
package/lib/serialize.js
CHANGED
|
@@ -1 +1,27 @@
|
|
|
1
|
-
export function serialize(
|
|
1
|
+
export function serialize(parameters) {
|
|
2
|
+
return parameters.map((parameter)=>{
|
|
3
|
+
if (typeof parameter === 'string') {
|
|
4
|
+
return parameter.toString();
|
|
5
|
+
}
|
|
6
|
+
if (parameter instanceof Date) {
|
|
7
|
+
return parameter.toISOString();
|
|
8
|
+
}
|
|
9
|
+
if (typeof parameter === 'number') {
|
|
10
|
+
return parameter;
|
|
11
|
+
}
|
|
12
|
+
if (parameter instanceof Uint8Array) {
|
|
13
|
+
return parameter;
|
|
14
|
+
}
|
|
15
|
+
if (parameter === null || parameter === undefined) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
if (typeof parameter === 'object') {
|
|
19
|
+
return JSON.stringify(parameter);
|
|
20
|
+
}
|
|
21
|
+
if (typeof parameter === 'boolean') {
|
|
22
|
+
return parameter ? 'true' : 'false' // SQLite booleans must be stored a strings.
|
|
23
|
+
;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`Unknown type: ${typeof parameter}`);
|
|
26
|
+
});
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/db-expo",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"license": "see LICENSE.md",
|
|
3
|
+
"version": "0.12.0",
|
|
5
4
|
"keywords": [],
|
|
5
|
+
"license": "see LICENSE.md",
|
|
6
|
+
"sideEffects": false,
|
|
6
7
|
"type": "module",
|
|
7
|
-
"main": "lib/index.js",
|
|
8
|
-
"types": "lib/index.d.ts",
|
|
9
8
|
"exports": {
|
|
10
9
|
".": "./lib/index.js"
|
|
11
10
|
},
|
|
11
|
+
"main": "lib/index.js",
|
|
12
|
+
"types": "lib/index.d.ts",
|
|
12
13
|
"files": [
|
|
13
14
|
"lib/*",
|
|
14
15
|
"LICENSE.md"
|
|
15
16
|
],
|
|
16
|
-
"sideEffects": false,
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"expo-sqlite": "~
|
|
19
|
-
"kysely": "^0.29.
|
|
20
|
-
"@kubun/db-adapter": "^0.
|
|
18
|
+
"expo-sqlite": "~57.0.1",
|
|
19
|
+
"kysely": "^0.29.4",
|
|
20
|
+
"@kubun/db-adapter": "^0.12.0"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
|
+
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
23
24
|
"build:clean": "del lib",
|
|
24
|
-
"build:js": "swc src -d ./lib --config-file ../../swc.json --strip-leading-paths",
|
|
25
|
+
"build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
25
26
|
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
26
27
|
"build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
|
|
27
|
-
"
|
|
28
|
+
"test": "pnpm run test:types && pnpm run test:unit",
|
|
28
29
|
"test:types": "tsc --noEmit",
|
|
29
|
-
"test:unit": "vitest run"
|
|
30
|
-
"test": "pnpm run test:types && pnpm run test:unit"
|
|
30
|
+
"test:unit": "vitest run"
|
|
31
31
|
}
|
|
32
32
|
}
|