@syncular/dialect-expo-sqlite 0.0.1-60
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/dist/index.d.ts +73 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +129 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
- package/src/index.ts +219 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/dialect-expo-sqlite - Expo SQLite dialect for sync
|
|
3
|
+
*
|
|
4
|
+
* Provides a Kysely dialect for Expo's SQLite module (expo-sqlite)
|
|
5
|
+
* with SerializePlugin for automatic JSON serialization/deserialization.
|
|
6
|
+
* SQLite-compatible — use with @syncular/server-dialect-sqlite.
|
|
7
|
+
*
|
|
8
|
+
* Implements a custom Kysely Driver that wraps expo-sqlite's sync API
|
|
9
|
+
* into the promise-based interface Kysely expects.
|
|
10
|
+
*/
|
|
11
|
+
import { SerializePlugin } from '@syncular/core';
|
|
12
|
+
import type { DatabaseIntrospector, Dialect, DialectAdapter, Driver, QueryCompiler } from 'kysely';
|
|
13
|
+
import { Kysely } from 'kysely';
|
|
14
|
+
export type ExpoSqliteBindValue = null | string | number | Uint8Array;
|
|
15
|
+
export type ExpoSqliteBindParams = ExpoSqliteBindValue[];
|
|
16
|
+
export interface ExpoSqliteRunResult {
|
|
17
|
+
changes: number;
|
|
18
|
+
lastInsertRowId: number;
|
|
19
|
+
}
|
|
20
|
+
export interface ExpoSqliteDatabaseLike {
|
|
21
|
+
getAllSync<R>(sql: string, params: ExpoSqliteBindParams): R[];
|
|
22
|
+
getAllSync<R>(sql: string, ...params: readonly ExpoSqliteBindValue[]): R[];
|
|
23
|
+
runSync(sql: string, params: ExpoSqliteBindParams): ExpoSqliteRunResult;
|
|
24
|
+
runSync(sql: string, ...params: readonly ExpoSqliteBindValue[]): ExpoSqliteRunResult;
|
|
25
|
+
closeSync(): void;
|
|
26
|
+
}
|
|
27
|
+
/** Function type for openDatabaseSync from expo-sqlite */
|
|
28
|
+
type OpenDatabaseSync = (name: string) => ExpoSqliteDatabaseLike;
|
|
29
|
+
interface ExpoSqliteNameOptions {
|
|
30
|
+
/** Database name (will be stored in app's document directory) */
|
|
31
|
+
name: string;
|
|
32
|
+
/** The openDatabaseSync function from expo-sqlite */
|
|
33
|
+
openDatabaseSync: OpenDatabaseSync;
|
|
34
|
+
}
|
|
35
|
+
interface ExpoSqliteInstanceOptions {
|
|
36
|
+
/** An existing expo-sqlite database instance */
|
|
37
|
+
database: ExpoSqliteDatabaseLike;
|
|
38
|
+
}
|
|
39
|
+
export type ExpoSqliteOptions = ExpoSqliteNameOptions | ExpoSqliteInstanceOptions;
|
|
40
|
+
/**
|
|
41
|
+
* Create a Kysely instance with Expo SQLite dialect and SerializePlugin.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* import { openDatabaseSync } from 'expo-sqlite';
|
|
45
|
+
*
|
|
46
|
+
* const db = createExpoSqliteDb<MyDb>({
|
|
47
|
+
* name: 'myapp.db',
|
|
48
|
+
* openDatabaseSync,
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* // Or with an existing database instance:
|
|
52
|
+
* const database = openDatabaseSync('myapp.db');
|
|
53
|
+
* const db = createExpoSqliteDb<MyDb>({ database });
|
|
54
|
+
*/
|
|
55
|
+
export declare function createExpoSqliteDb<T>(options: ExpoSqliteOptions): Kysely<T>;
|
|
56
|
+
/**
|
|
57
|
+
* Create the Expo SQLite dialect directly (without SerializePlugin).
|
|
58
|
+
*/
|
|
59
|
+
export declare function createExpoSqliteDialect(options: ExpoSqliteOptions): ExpoSqliteDialect;
|
|
60
|
+
/**
|
|
61
|
+
* Create a SerializePlugin instance.
|
|
62
|
+
*/
|
|
63
|
+
export declare function createSerializePlugin(): SerializePlugin;
|
|
64
|
+
declare class ExpoSqliteDialect implements Dialect {
|
|
65
|
+
#private;
|
|
66
|
+
constructor(options: ExpoSqliteOptions);
|
|
67
|
+
createAdapter(): DialectAdapter;
|
|
68
|
+
createDriver(): Driver;
|
|
69
|
+
createQueryCompiler(): QueryCompiler;
|
|
70
|
+
createIntrospector(db: Kysely<unknown>): DatabaseIntrospector;
|
|
71
|
+
}
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAEV,oBAAoB,EACpB,OAAO,EACP,cAAc,EACd,MAAM,EACN,aAAa,EAGd,MAAM,QAAQ,CAAC;AAChB,OAAO,EAEL,MAAM,EAIP,MAAM,QAAQ,CAAC;AAEhB,MAAM,MAAM,mBAAmB,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,EAAE,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,CAAC,EAAE,CAAC;IAC9D,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,SAAS,mBAAmB,EAAE,GAAG,CAAC,EAAE,CAAC;IAC3E,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,mBAAmB,CAAC;IACxE,OAAO,CACL,GAAG,EAAE,MAAM,EACX,GAAG,MAAM,EAAE,SAAS,mBAAmB,EAAE,GACxC,mBAAmB,CAAC;IACvB,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,0DAA0D;AAC1D,KAAK,gBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,sBAAsB,CAAC;AAEjE,UAAU,qBAAqB;IAC7B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,gBAAgB,EAAE,gBAAgB,CAAC;CACpC;AAED,UAAU,yBAAyB;IACjC,gDAAgD;IAChD,QAAQ,EAAE,sBAAsB,CAAC;CAClC;AAED,MAAM,MAAM,iBAAiB,GACzB,qBAAqB,GACrB,yBAAyB,CAAC;AAE9B;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAAC,CAAC,CAAC,CAK3E;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,iBAAiB,GACzB,iBAAiB,CAEnB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD;AAMD,cAAM,iBAAkB,YAAW,OAAO;;IAGxC,YAAY,OAAO,EAAE,iBAAiB,EAErC;IAED,aAAa,IAAI,cAAc,CAE9B;IAED,YAAY,IAAI,MAAM,CAErB;IAED,mBAAmB,IAAI,aAAa,CAEnC;IAED,kBAAkB,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,oBAAoB,CAE5D;CACF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/dialect-expo-sqlite - Expo SQLite dialect for sync
|
|
3
|
+
*
|
|
4
|
+
* Provides a Kysely dialect for Expo's SQLite module (expo-sqlite)
|
|
5
|
+
* with SerializePlugin for automatic JSON serialization/deserialization.
|
|
6
|
+
* SQLite-compatible — use with @syncular/server-dialect-sqlite.
|
|
7
|
+
*
|
|
8
|
+
* Implements a custom Kysely Driver that wraps expo-sqlite's sync API
|
|
9
|
+
* into the promise-based interface Kysely expects.
|
|
10
|
+
*/
|
|
11
|
+
import { SerializePlugin } from '@syncular/core';
|
|
12
|
+
import { CompiledQuery, Kysely, SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler, } from 'kysely';
|
|
13
|
+
/**
|
|
14
|
+
* Create a Kysely instance with Expo SQLite dialect and SerializePlugin.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* import { openDatabaseSync } from 'expo-sqlite';
|
|
18
|
+
*
|
|
19
|
+
* const db = createExpoSqliteDb<MyDb>({
|
|
20
|
+
* name: 'myapp.db',
|
|
21
|
+
* openDatabaseSync,
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Or with an existing database instance:
|
|
25
|
+
* const database = openDatabaseSync('myapp.db');
|
|
26
|
+
* const db = createExpoSqliteDb<MyDb>({ database });
|
|
27
|
+
*/
|
|
28
|
+
export function createExpoSqliteDb(options) {
|
|
29
|
+
return new Kysely({
|
|
30
|
+
dialect: createExpoSqliteDialect(options),
|
|
31
|
+
plugins: [new SerializePlugin()],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create the Expo SQLite dialect directly (without SerializePlugin).
|
|
36
|
+
*/
|
|
37
|
+
export function createExpoSqliteDialect(options) {
|
|
38
|
+
return new ExpoSqliteDialect(options);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a SerializePlugin instance.
|
|
42
|
+
*/
|
|
43
|
+
export function createSerializePlugin() {
|
|
44
|
+
return new SerializePlugin();
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Kysely Dialect implementation for expo-sqlite
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
class ExpoSqliteDialect {
|
|
50
|
+
#options;
|
|
51
|
+
constructor(options) {
|
|
52
|
+
this.#options = options;
|
|
53
|
+
}
|
|
54
|
+
createAdapter() {
|
|
55
|
+
return new SqliteAdapter();
|
|
56
|
+
}
|
|
57
|
+
createDriver() {
|
|
58
|
+
return new ExpoSqliteDriver(this.#options);
|
|
59
|
+
}
|
|
60
|
+
createQueryCompiler() {
|
|
61
|
+
return new SqliteQueryCompiler();
|
|
62
|
+
}
|
|
63
|
+
createIntrospector(db) {
|
|
64
|
+
return new SqliteIntrospector(db);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
class ExpoSqliteDriver {
|
|
68
|
+
#options;
|
|
69
|
+
#db;
|
|
70
|
+
constructor(options) {
|
|
71
|
+
this.#options = options;
|
|
72
|
+
}
|
|
73
|
+
async init() {
|
|
74
|
+
this.#db = this.#resolveDatabase();
|
|
75
|
+
}
|
|
76
|
+
async acquireConnection() {
|
|
77
|
+
return new ExpoSqliteConnection(this.#db);
|
|
78
|
+
}
|
|
79
|
+
async beginTransaction(connection, _settings) {
|
|
80
|
+
await connection.executeQuery(CompiledQuery.raw('begin'));
|
|
81
|
+
}
|
|
82
|
+
async commitTransaction(connection) {
|
|
83
|
+
await connection.executeQuery(CompiledQuery.raw('commit'));
|
|
84
|
+
}
|
|
85
|
+
async rollbackTransaction(connection) {
|
|
86
|
+
await connection.executeQuery(CompiledQuery.raw('rollback'));
|
|
87
|
+
}
|
|
88
|
+
async releaseConnection(_connection) {
|
|
89
|
+
// Single-connection model — nothing to release.
|
|
90
|
+
}
|
|
91
|
+
async destroy() {
|
|
92
|
+
if (this.#db) {
|
|
93
|
+
this.#db.closeSync();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
#resolveDatabase() {
|
|
97
|
+
if ('database' in this.#options) {
|
|
98
|
+
return this.#options.database;
|
|
99
|
+
}
|
|
100
|
+
return this.#options.openDatabaseSync(this.#options.name);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
class ExpoSqliteConnection {
|
|
104
|
+
#db;
|
|
105
|
+
constructor(db) {
|
|
106
|
+
this.#db = db;
|
|
107
|
+
}
|
|
108
|
+
async executeQuery(compiledQuery) {
|
|
109
|
+
const { sql, parameters } = compiledQuery;
|
|
110
|
+
const params = [...parameters];
|
|
111
|
+
// Determine if this is a SELECT / RETURNING query
|
|
112
|
+
const isSelect = /^\s*(select|pragma|explain|with)\b/i.test(sql);
|
|
113
|
+
if (isSelect) {
|
|
114
|
+
const rows = this.#db.getAllSync(sql, params);
|
|
115
|
+
return { rows: rows ?? [] };
|
|
116
|
+
}
|
|
117
|
+
// For INSERT, UPDATE, DELETE — use runSync to get lastInsertRowId and changes
|
|
118
|
+
const result = this.#db.runSync(sql, params);
|
|
119
|
+
return {
|
|
120
|
+
rows: [],
|
|
121
|
+
numAffectedRows: BigInt(result.changes),
|
|
122
|
+
insertId: BigInt(result.lastInsertRowId),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
streamQuery(_compiledQuery, _chunkSize) {
|
|
126
|
+
throw new Error('expo-sqlite driver does not support streaming');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAWjD,OAAO,EACL,aAAa,EACb,MAAM,EACN,aAAa,EACb,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,QAAQ,CAAC;AAyChB;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,kBAAkB,CAAI,OAA0B,EAAa;IAC3E,OAAO,IAAI,MAAM,CAAI;QACnB,OAAO,EAAE,uBAAuB,CAAC,OAAO,CAAC;QACzC,OAAO,EAAE,CAAC,IAAI,eAAe,EAAE,CAAC;KACjC,CAAC,CAAC;AAAA,CACJ;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAA0B,EACP;IACnB,OAAO,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAAA,CACvC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,GAAoB;IACvD,OAAO,IAAI,eAAe,EAAE,CAAC;AAAA,CAC9B;AAED,8EAA8E;AAC9E,gDAAgD;AAChD,8EAA8E;AAE9E,MAAM,iBAAiB;IACZ,QAAQ,CAAoB;IAErC,YAAY,OAA0B,EAAE;QACtC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAAA,CACzB;IAED,aAAa,GAAmB;QAC9B,OAAO,IAAI,aAAa,EAAE,CAAC;IAAA,CAC5B;IAED,YAAY,GAAW;QACrB,OAAO,IAAI,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAAA,CAC5C;IAED,mBAAmB,GAAkB;QACnC,OAAO,IAAI,mBAAmB,EAAE,CAAC;IAAA,CAClC;IAED,kBAAkB,CAAC,EAAmB,EAAwB;QAC5D,OAAO,IAAI,kBAAkB,CAAC,EAAE,CAAC,CAAC;IAAA,CACnC;CACF;AAED,MAAM,gBAAgB;IACX,QAAQ,CAAoB;IACrC,GAAG,CAAqC;IAExC,YAAY,OAA0B,EAAE;QACtC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAAA,CACzB;IAED,KAAK,CAAC,IAAI,GAAkB;QAC1B,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;IAAA,CACpC;IAED,KAAK,CAAC,iBAAiB,GAAgC;QACrD,OAAO,IAAI,oBAAoB,CAAC,IAAI,CAAC,GAAI,CAAC,CAAC;IAAA,CAC5C;IAED,KAAK,CAAC,gBAAgB,CACpB,UAA8B,EAC9B,SAA8B,EACf;QACf,MAAM,UAAU,CAAC,YAAY,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;IAAA,CAC3D;IAED,KAAK,CAAC,iBAAiB,CAAC,UAA8B,EAAiB;QACrE,MAAM,UAAU,CAAC,YAAY,CAAC,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IAAA,CAC5D;IAED,KAAK,CAAC,mBAAmB,CAAC,UAA8B,EAAiB;QACvE,MAAM,UAAU,CAAC,YAAY,CAAC,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IAAA,CAC9D;IAED,KAAK,CAAC,iBAAiB,CAAC,WAA+B,EAAiB;QACtE,kDAAgD;IADuB,CAExE;IAED,KAAK,CAAC,OAAO,GAAkB;QAC7B,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QACvB,CAAC;IAAA,CACF;IAED,gBAAgB,GAA2B;QACzC,IAAI,UAAU,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAChC,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAAA,CAC3D;CACF;AAED,MAAM,oBAAoB;IACf,GAAG,CAAyB;IAErC,YAAY,EAA0B,EAAE;QACtC,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;IAAA,CACf;IAED,KAAK,CAAC,YAAY,CAAI,aAA4B,EAA2B;QAC3E,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,aAAa,CAAC;QAC1C,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAyB,CAAC;QAEvD,kDAAkD;QAClD,MAAM,QAAQ,GAAG,qCAAqC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEjE,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAI,GAAG,EAAE,MAAM,CAAC,CAAC;YACjD,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC;QAC9B,CAAC;QAED,gFAA8E;QAC9E,MAAM,MAAM,GAAwB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAClE,OAAO;YACL,IAAI,EAAE,EAAE;YACR,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;YACvC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC;SACzC,CAAC;IAAA,CACH;IAED,WAAW,CACT,cAA6B,EAC7B,UAAmB,EACoB;QACvC,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAAA,CAClE;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@syncular/dialect-expo-sqlite",
|
|
3
|
+
"version": "0.0.1-60",
|
|
4
|
+
"description": "Expo SQLite dialect for the Syncular client",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Benjamin Kniffler",
|
|
7
|
+
"homepage": "https://syncular.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/syncular/syncular.git",
|
|
11
|
+
"directory": "packages/dialect-expo-sqlite"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/syncular/syncular/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"sync",
|
|
18
|
+
"offline-first",
|
|
19
|
+
"realtime",
|
|
20
|
+
"database",
|
|
21
|
+
"typescript",
|
|
22
|
+
"expo",
|
|
23
|
+
"sqlite",
|
|
24
|
+
"react-native"
|
|
25
|
+
],
|
|
26
|
+
"private": false,
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"bun": "./src/index.ts",
|
|
34
|
+
"import": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"tsgo": "tsgo --noEmit",
|
|
42
|
+
"build": "rm -rf dist && tsgo",
|
|
43
|
+
"release": "bun pm pack --destination . && npm publish ./*.tgz --tag latest && rm -f ./*.tgz"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@syncular/core": "0.0.1"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"expo-sqlite": "^16.0.10",
|
|
50
|
+
"kysely": "^0.28.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@syncular/config": "0.0.0",
|
|
54
|
+
"kysely": "*"
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"dist",
|
|
58
|
+
"src"
|
|
59
|
+
]
|
|
60
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/dialect-expo-sqlite - Expo SQLite dialect for sync
|
|
3
|
+
*
|
|
4
|
+
* Provides a Kysely dialect for Expo's SQLite module (expo-sqlite)
|
|
5
|
+
* with SerializePlugin for automatic JSON serialization/deserialization.
|
|
6
|
+
* SQLite-compatible — use with @syncular/server-dialect-sqlite.
|
|
7
|
+
*
|
|
8
|
+
* Implements a custom Kysely Driver that wraps expo-sqlite's sync API
|
|
9
|
+
* into the promise-based interface Kysely expects.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { SerializePlugin } from '@syncular/core';
|
|
13
|
+
import type {
|
|
14
|
+
DatabaseConnection,
|
|
15
|
+
DatabaseIntrospector,
|
|
16
|
+
Dialect,
|
|
17
|
+
DialectAdapter,
|
|
18
|
+
Driver,
|
|
19
|
+
QueryCompiler,
|
|
20
|
+
QueryResult,
|
|
21
|
+
TransactionSettings,
|
|
22
|
+
} from 'kysely';
|
|
23
|
+
import {
|
|
24
|
+
CompiledQuery,
|
|
25
|
+
Kysely,
|
|
26
|
+
SqliteAdapter,
|
|
27
|
+
SqliteIntrospector,
|
|
28
|
+
SqliteQueryCompiler,
|
|
29
|
+
} from 'kysely';
|
|
30
|
+
|
|
31
|
+
export type ExpoSqliteBindValue = null | string | number | Uint8Array;
|
|
32
|
+
|
|
33
|
+
export type ExpoSqliteBindParams = ExpoSqliteBindValue[];
|
|
34
|
+
|
|
35
|
+
export interface ExpoSqliteRunResult {
|
|
36
|
+
changes: number;
|
|
37
|
+
lastInsertRowId: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ExpoSqliteDatabaseLike {
|
|
41
|
+
getAllSync<R>(sql: string, params: ExpoSqliteBindParams): R[];
|
|
42
|
+
getAllSync<R>(sql: string, ...params: readonly ExpoSqliteBindValue[]): R[];
|
|
43
|
+
runSync(sql: string, params: ExpoSqliteBindParams): ExpoSqliteRunResult;
|
|
44
|
+
runSync(
|
|
45
|
+
sql: string,
|
|
46
|
+
...params: readonly ExpoSqliteBindValue[]
|
|
47
|
+
): ExpoSqliteRunResult;
|
|
48
|
+
closeSync(): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Function type for openDatabaseSync from expo-sqlite */
|
|
52
|
+
type OpenDatabaseSync = (name: string) => ExpoSqliteDatabaseLike;
|
|
53
|
+
|
|
54
|
+
interface ExpoSqliteNameOptions {
|
|
55
|
+
/** Database name (will be stored in app's document directory) */
|
|
56
|
+
name: string;
|
|
57
|
+
/** The openDatabaseSync function from expo-sqlite */
|
|
58
|
+
openDatabaseSync: OpenDatabaseSync;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface ExpoSqliteInstanceOptions {
|
|
62
|
+
/** An existing expo-sqlite database instance */
|
|
63
|
+
database: ExpoSqliteDatabaseLike;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type ExpoSqliteOptions =
|
|
67
|
+
| ExpoSqliteNameOptions
|
|
68
|
+
| ExpoSqliteInstanceOptions;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a Kysely instance with Expo SQLite dialect and SerializePlugin.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* import { openDatabaseSync } from 'expo-sqlite';
|
|
75
|
+
*
|
|
76
|
+
* const db = createExpoSqliteDb<MyDb>({
|
|
77
|
+
* name: 'myapp.db',
|
|
78
|
+
* openDatabaseSync,
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* // Or with an existing database instance:
|
|
82
|
+
* const database = openDatabaseSync('myapp.db');
|
|
83
|
+
* const db = createExpoSqliteDb<MyDb>({ database });
|
|
84
|
+
*/
|
|
85
|
+
export function createExpoSqliteDb<T>(options: ExpoSqliteOptions): Kysely<T> {
|
|
86
|
+
return new Kysely<T>({
|
|
87
|
+
dialect: createExpoSqliteDialect(options),
|
|
88
|
+
plugins: [new SerializePlugin()],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Create the Expo SQLite dialect directly (without SerializePlugin).
|
|
94
|
+
*/
|
|
95
|
+
export function createExpoSqliteDialect(
|
|
96
|
+
options: ExpoSqliteOptions
|
|
97
|
+
): ExpoSqliteDialect {
|
|
98
|
+
return new ExpoSqliteDialect(options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create a SerializePlugin instance.
|
|
103
|
+
*/
|
|
104
|
+
export function createSerializePlugin(): SerializePlugin {
|
|
105
|
+
return new SerializePlugin();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Kysely Dialect implementation for expo-sqlite
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
class ExpoSqliteDialect implements Dialect {
|
|
113
|
+
readonly #options: ExpoSqliteOptions;
|
|
114
|
+
|
|
115
|
+
constructor(options: ExpoSqliteOptions) {
|
|
116
|
+
this.#options = options;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
createAdapter(): DialectAdapter {
|
|
120
|
+
return new SqliteAdapter();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
createDriver(): Driver {
|
|
124
|
+
return new ExpoSqliteDriver(this.#options);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
createQueryCompiler(): QueryCompiler {
|
|
128
|
+
return new SqliteQueryCompiler();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
createIntrospector(db: Kysely<unknown>): DatabaseIntrospector {
|
|
132
|
+
return new SqliteIntrospector(db);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
class ExpoSqliteDriver implements Driver {
|
|
137
|
+
readonly #options: ExpoSqliteOptions;
|
|
138
|
+
#db: ExpoSqliteDatabaseLike | undefined;
|
|
139
|
+
|
|
140
|
+
constructor(options: ExpoSqliteOptions) {
|
|
141
|
+
this.#options = options;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async init(): Promise<void> {
|
|
145
|
+
this.#db = this.#resolveDatabase();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async acquireConnection(): Promise<DatabaseConnection> {
|
|
149
|
+
return new ExpoSqliteConnection(this.#db!);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async beginTransaction(
|
|
153
|
+
connection: DatabaseConnection,
|
|
154
|
+
_settings: TransactionSettings
|
|
155
|
+
): Promise<void> {
|
|
156
|
+
await connection.executeQuery(CompiledQuery.raw('begin'));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async commitTransaction(connection: DatabaseConnection): Promise<void> {
|
|
160
|
+
await connection.executeQuery(CompiledQuery.raw('commit'));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
|
|
164
|
+
await connection.executeQuery(CompiledQuery.raw('rollback'));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async releaseConnection(_connection: DatabaseConnection): Promise<void> {
|
|
168
|
+
// Single-connection model — nothing to release.
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async destroy(): Promise<void> {
|
|
172
|
+
if (this.#db) {
|
|
173
|
+
this.#db.closeSync();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#resolveDatabase(): ExpoSqliteDatabaseLike {
|
|
178
|
+
if ('database' in this.#options) {
|
|
179
|
+
return this.#options.database;
|
|
180
|
+
}
|
|
181
|
+
return this.#options.openDatabaseSync(this.#options.name);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
class ExpoSqliteConnection implements DatabaseConnection {
|
|
186
|
+
readonly #db: ExpoSqliteDatabaseLike;
|
|
187
|
+
|
|
188
|
+
constructor(db: ExpoSqliteDatabaseLike) {
|
|
189
|
+
this.#db = db;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async executeQuery<R>(compiledQuery: CompiledQuery): Promise<QueryResult<R>> {
|
|
193
|
+
const { sql, parameters } = compiledQuery;
|
|
194
|
+
const params = [...parameters] as ExpoSqliteBindParams;
|
|
195
|
+
|
|
196
|
+
// Determine if this is a SELECT / RETURNING query
|
|
197
|
+
const isSelect = /^\s*(select|pragma|explain|with)\b/i.test(sql);
|
|
198
|
+
|
|
199
|
+
if (isSelect) {
|
|
200
|
+
const rows = this.#db.getAllSync<R>(sql, params);
|
|
201
|
+
return { rows: rows ?? [] };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// For INSERT, UPDATE, DELETE — use runSync to get lastInsertRowId and changes
|
|
205
|
+
const result: ExpoSqliteRunResult = this.#db.runSync(sql, params);
|
|
206
|
+
return {
|
|
207
|
+
rows: [],
|
|
208
|
+
numAffectedRows: BigInt(result.changes),
|
|
209
|
+
insertId: BigInt(result.lastInsertRowId),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
streamQuery<R>(
|
|
214
|
+
_compiledQuery: CompiledQuery,
|
|
215
|
+
_chunkSize?: number
|
|
216
|
+
): AsyncIterableIterator<QueryResult<R>> {
|
|
217
|
+
throw new Error('expo-sqlite driver does not support streaming');
|
|
218
|
+
}
|
|
219
|
+
}
|