@syncular/dialect-wa-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 +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/worker-module.d.ts +2 -0
- package/dist/worker-module.d.ts.map +1 -0
- package/dist/worker-module.js +60 -0
- package/dist/worker-module.js.map +1 -0
- package/package.json +62 -0
- package/src/index.ts +81 -0
- package/src/worker-module.ts +90 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/dialect-wa-sqlite - wa-sqlite dialect for sync
|
|
3
|
+
*
|
|
4
|
+
* Provides a Kysely dialect for wa-sqlite (browser SQLite via WebAssembly).
|
|
5
|
+
* Uses kysely-wasqlite-worker for running SQL in a web worker with OPFS or IndexedDB storage.
|
|
6
|
+
*/
|
|
7
|
+
import { SerializePlugin } from '@syncular/core';
|
|
8
|
+
import { Kysely } from 'kysely';
|
|
9
|
+
import { WaSqliteWorkerDialect, type WaSqliteWorkerDialectConfig } from 'kysely-wasqlite-worker';
|
|
10
|
+
export interface WaSqliteOptions extends Omit<WaSqliteWorkerDialectConfig, 'fileName'> {
|
|
11
|
+
/** Database filename for persistence (defaults to 'diego.sqlite') */
|
|
12
|
+
fileName?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Create a Kysely instance with wa-sqlite dialect.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // In-memory database
|
|
19
|
+
* const db = createWaSqliteDb<MyDb>();
|
|
20
|
+
*
|
|
21
|
+
* // OPFS-persisted database
|
|
22
|
+
* const db = createWaSqliteDb<MyDb>({
|
|
23
|
+
* fileName: 'mydb.sqlite',
|
|
24
|
+
* });
|
|
25
|
+
*/
|
|
26
|
+
export declare function createWaSqliteDb<T>(options?: WaSqliteOptions): Kysely<T>;
|
|
27
|
+
/**
|
|
28
|
+
* Create the wa-sqlite dialect directly.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createWaSqliteDialect(options?: WaSqliteOptions): WaSqliteWorkerDialect;
|
|
31
|
+
export declare function createSerializePlugin(): SerializePlugin;
|
|
32
|
+
export declare function getWaSqliteWorkerEntrypointPaths(): {
|
|
33
|
+
moduleWorkerPath: string;
|
|
34
|
+
};
|
|
35
|
+
export declare function getWaSqliteWasmPaths(): {
|
|
36
|
+
asyncWasmPath: string;
|
|
37
|
+
syncWasmPath: string;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EACL,qBAAqB,EACrB,KAAK,2BAA2B,EACjC,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,eACf,SAAQ,IAAI,CAAC,2BAA2B,EAAE,UAAU,CAAC;IACrD,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAWD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,MAAM,CAAC,CAAC,CAAC,CAKxE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,CAAC,EAAE,eAAe,GACxB,qBAAqB,CAEvB;AAED,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD;AAED,wBAAgB,gCAAgC,IAAI;IAClD,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAIA;AAED,wBAAgB,oBAAoB,IAAI;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB,CAQA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/dialect-wa-sqlite - wa-sqlite dialect for sync
|
|
3
|
+
*
|
|
4
|
+
* Provides a Kysely dialect for wa-sqlite (browser SQLite via WebAssembly).
|
|
5
|
+
* Uses kysely-wasqlite-worker for running SQL in a web worker with OPFS or IndexedDB storage.
|
|
6
|
+
*/
|
|
7
|
+
import { SerializePlugin } from '@syncular/core';
|
|
8
|
+
import { Kysely } from 'kysely';
|
|
9
|
+
import { WaSqliteWorkerDialect, } from 'kysely-wasqlite-worker';
|
|
10
|
+
function toDialectConfig(options) {
|
|
11
|
+
return {
|
|
12
|
+
fileName: options?.fileName ?? 'diego.sqlite',
|
|
13
|
+
...options,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Create a Kysely instance with wa-sqlite dialect.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // In-memory database
|
|
21
|
+
* const db = createWaSqliteDb<MyDb>();
|
|
22
|
+
*
|
|
23
|
+
* // OPFS-persisted database
|
|
24
|
+
* const db = createWaSqliteDb<MyDb>({
|
|
25
|
+
* fileName: 'mydb.sqlite',
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
export function createWaSqliteDb(options) {
|
|
29
|
+
return new Kysely({
|
|
30
|
+
dialect: new WaSqliteWorkerDialect(toDialectConfig(options)),
|
|
31
|
+
plugins: [new SerializePlugin()],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Create the wa-sqlite dialect directly.
|
|
36
|
+
*/
|
|
37
|
+
export function createWaSqliteDialect(options) {
|
|
38
|
+
return new WaSqliteWorkerDialect(toDialectConfig(options));
|
|
39
|
+
}
|
|
40
|
+
export function createSerializePlugin() {
|
|
41
|
+
return new SerializePlugin();
|
|
42
|
+
}
|
|
43
|
+
export function getWaSqliteWorkerEntrypointPaths() {
|
|
44
|
+
return {
|
|
45
|
+
moduleWorkerPath: new URL('./worker-module.ts', import.meta.url).pathname,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function getWaSqliteWasmPaths() {
|
|
49
|
+
return {
|
|
50
|
+
asyncWasmPath: new URL(import.meta.resolve('@subframe7536/sqlite-wasm/wasm-async')).pathname,
|
|
51
|
+
syncWasmPath: new URL(import.meta.resolve('@subframe7536/sqlite-wasm/wasm'))
|
|
52
|
+
.pathname,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EACL,qBAAqB,GAEtB,MAAM,wBAAwB,CAAC;AAQhC,SAAS,eAAe,CACtB,OAAyB,EACI;IAC7B,OAAO;QACL,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,cAAc;QAC7C,GAAG,OAAO;KACX,CAAC;AAAA,CACH;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAI,OAAyB,EAAa;IACxE,OAAO,IAAI,MAAM,CAAI;QACnB,OAAO,EAAE,IAAI,qBAAqB,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAC5D,OAAO,EAAE,CAAC,IAAI,eAAe,EAAE,CAAC;KACjC,CAAC,CAAC;AAAA,CACJ;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAyB,EACF;IACvB,OAAO,IAAI,qBAAqB,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;AAAA,CAC5D;AAED,MAAM,UAAU,qBAAqB,GAAoB;IACvD,OAAO,IAAI,eAAe,EAAE,CAAC;AAAA,CAC9B;AAED,MAAM,UAAU,gCAAgC,GAE9C;IACA,OAAO;QACL,gBAAgB,EAAE,IAAI,GAAG,CAAC,oBAAoB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ;KAC1E,CAAC;AAAA,CACH;AAED,MAAM,UAAU,oBAAoB,GAGlC;IACA,OAAO;QACL,aAAa,EAAE,IAAI,GAAG,CACpB,OAAO,IAAI,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAC5D,CAAC,QAAQ;QACV,YAAY,EAAE,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;aACzE,QAAQ;KACZ,CAAC;AAAA,CACH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-module.d.ts","sourceRoot":"","sources":["../src/worker-module.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { changes, close, lastInsertRowId } from '@subframe7536/sqlite-wasm';
|
|
2
|
+
import { SQLITE_ROW } from '@subframe7536/sqlite-wasm/constant';
|
|
3
|
+
import { parseBigInt } from 'kysely-generic-sqlite';
|
|
4
|
+
import { createWebOnMessageCallback } from 'kysely-generic-sqlite/worker-helper-web';
|
|
5
|
+
async function defaultCreateDatabaseFn({ fileName, url, useOPFS, }) {
|
|
6
|
+
const sqlite = await import('@subframe7536/sqlite-wasm');
|
|
7
|
+
const storage = useOPFS
|
|
8
|
+
? (await import('@subframe7536/sqlite-wasm/opfs')).useOpfsStorage
|
|
9
|
+
: (await import('@subframe7536/sqlite-wasm/idb')).useIdbStorage;
|
|
10
|
+
return sqlite.initSQLiteCore(storage(fileName, { url }));
|
|
11
|
+
}
|
|
12
|
+
function createRowMapper(sqlite, stmt) {
|
|
13
|
+
const cols = sqlite.column_names(stmt);
|
|
14
|
+
return (row) => Object.fromEntries(cols.map((key, i) => [key, row[i]]));
|
|
15
|
+
}
|
|
16
|
+
async function queryData(core, sql, parameters) {
|
|
17
|
+
const iterator = core.sqlite
|
|
18
|
+
.statements(core.pointer, sql)[Symbol.asyncIterator]();
|
|
19
|
+
const first = await iterator.next();
|
|
20
|
+
if (first.done || !first.value) {
|
|
21
|
+
throw new Error('Failed to prepare statement');
|
|
22
|
+
}
|
|
23
|
+
const stmt = first.value;
|
|
24
|
+
try {
|
|
25
|
+
if (parameters?.length) {
|
|
26
|
+
core.sqlite.bind_collection(stmt, Array.from(parameters));
|
|
27
|
+
}
|
|
28
|
+
const size = core.sqlite.column_count(stmt);
|
|
29
|
+
if (size === 0) {
|
|
30
|
+
await core.sqlite.step(stmt);
|
|
31
|
+
return {
|
|
32
|
+
rows: [],
|
|
33
|
+
insertId: parseBigInt(lastInsertRowId(core)),
|
|
34
|
+
numAffectedRows: parseBigInt(changes(core)),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const mapRow = createRowMapper(core.sqlite, stmt);
|
|
38
|
+
const result = [];
|
|
39
|
+
let idx = 0;
|
|
40
|
+
while ((await core.sqlite.step(stmt)) === SQLITE_ROW) {
|
|
41
|
+
result[idx++] = mapRow(core.sqlite.row(stmt));
|
|
42
|
+
}
|
|
43
|
+
return { rows: result };
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
await iterator.return?.();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
createWebOnMessageCallback(async (initData) => {
|
|
50
|
+
const core = await defaultCreateDatabaseFn(initData);
|
|
51
|
+
return {
|
|
52
|
+
db: core,
|
|
53
|
+
query: async (_isSelect, sql, parameters) => await queryData(core, sql, parameters),
|
|
54
|
+
close: async () => await close(core),
|
|
55
|
+
iterator: () => {
|
|
56
|
+
throw new Error('Streaming is not supported by wa-sqlite worker dialect');
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=worker-module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-module.js","sourceRoot":"","sources":["../src/worker-module.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@syncular/dialect-wa-sqlite",
|
|
3
|
+
"version": "0.0.1-60",
|
|
4
|
+
"description": "WA-SQLite (WASM) 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-wa-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
|
+
"wa-sqlite",
|
|
23
|
+
"wasm",
|
|
24
|
+
"sqlite"
|
|
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
|
+
"@subframe7536/sqlite-wasm": "^0.5.8",
|
|
47
|
+
"@syncular/core": "0.0.1",
|
|
48
|
+
"kysely-generic-sqlite": "^1.2.1",
|
|
49
|
+
"kysely-wasqlite-worker": "^1.2.1"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"kysely": "^0.28.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@syncular/config": "0.0.0",
|
|
56
|
+
"kysely": "*"
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"dist",
|
|
60
|
+
"src"
|
|
61
|
+
]
|
|
62
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @syncular/dialect-wa-sqlite - wa-sqlite dialect for sync
|
|
3
|
+
*
|
|
4
|
+
* Provides a Kysely dialect for wa-sqlite (browser SQLite via WebAssembly).
|
|
5
|
+
* Uses kysely-wasqlite-worker for running SQL in a web worker with OPFS or IndexedDB storage.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { SerializePlugin } from '@syncular/core';
|
|
9
|
+
import { Kysely } from 'kysely';
|
|
10
|
+
import {
|
|
11
|
+
WaSqliteWorkerDialect,
|
|
12
|
+
type WaSqliteWorkerDialectConfig,
|
|
13
|
+
} from 'kysely-wasqlite-worker';
|
|
14
|
+
|
|
15
|
+
export interface WaSqliteOptions
|
|
16
|
+
extends Omit<WaSqliteWorkerDialectConfig, 'fileName'> {
|
|
17
|
+
/** Database filename for persistence (defaults to 'diego.sqlite') */
|
|
18
|
+
fileName?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function toDialectConfig(
|
|
22
|
+
options?: WaSqliteOptions
|
|
23
|
+
): WaSqliteWorkerDialectConfig {
|
|
24
|
+
return {
|
|
25
|
+
fileName: options?.fileName ?? 'diego.sqlite',
|
|
26
|
+
...options,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a Kysely instance with wa-sqlite dialect.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // In-memory database
|
|
35
|
+
* const db = createWaSqliteDb<MyDb>();
|
|
36
|
+
*
|
|
37
|
+
* // OPFS-persisted database
|
|
38
|
+
* const db = createWaSqliteDb<MyDb>({
|
|
39
|
+
* fileName: 'mydb.sqlite',
|
|
40
|
+
* });
|
|
41
|
+
*/
|
|
42
|
+
export function createWaSqliteDb<T>(options?: WaSqliteOptions): Kysely<T> {
|
|
43
|
+
return new Kysely<T>({
|
|
44
|
+
dialect: new WaSqliteWorkerDialect(toDialectConfig(options)),
|
|
45
|
+
plugins: [new SerializePlugin()],
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create the wa-sqlite dialect directly.
|
|
51
|
+
*/
|
|
52
|
+
export function createWaSqliteDialect(
|
|
53
|
+
options?: WaSqliteOptions
|
|
54
|
+
): WaSqliteWorkerDialect {
|
|
55
|
+
return new WaSqliteWorkerDialect(toDialectConfig(options));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createSerializePlugin(): SerializePlugin {
|
|
59
|
+
return new SerializePlugin();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getWaSqliteWorkerEntrypointPaths(): {
|
|
63
|
+
moduleWorkerPath: string;
|
|
64
|
+
} {
|
|
65
|
+
return {
|
|
66
|
+
moduleWorkerPath: new URL('./worker-module.ts', import.meta.url).pathname,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getWaSqliteWasmPaths(): {
|
|
71
|
+
asyncWasmPath: string;
|
|
72
|
+
syncWasmPath: string;
|
|
73
|
+
} {
|
|
74
|
+
return {
|
|
75
|
+
asyncWasmPath: new URL(
|
|
76
|
+
import.meta.resolve('@subframe7536/sqlite-wasm/wasm-async')
|
|
77
|
+
).pathname,
|
|
78
|
+
syncWasmPath: new URL(import.meta.resolve('@subframe7536/sqlite-wasm/wasm'))
|
|
79
|
+
.pathname,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { SQLiteDBCore } from '@subframe7536/sqlite-wasm';
|
|
2
|
+
import { changes, close, lastInsertRowId } from '@subframe7536/sqlite-wasm';
|
|
3
|
+
import { SQLITE_ROW } from '@subframe7536/sqlite-wasm/constant';
|
|
4
|
+
import type { QueryResult } from 'kysely';
|
|
5
|
+
import { parseBigInt } from 'kysely-generic-sqlite';
|
|
6
|
+
import { createWebOnMessageCallback } from 'kysely-generic-sqlite/worker-helper-web';
|
|
7
|
+
|
|
8
|
+
type InitData = {
|
|
9
|
+
fileName: string;
|
|
10
|
+
url?: string;
|
|
11
|
+
useOPFS?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
async function defaultCreateDatabaseFn({
|
|
15
|
+
fileName,
|
|
16
|
+
url,
|
|
17
|
+
useOPFS,
|
|
18
|
+
}: InitData): Promise<SQLiteDBCore> {
|
|
19
|
+
const sqlite = await import('@subframe7536/sqlite-wasm');
|
|
20
|
+
const storage = useOPFS
|
|
21
|
+
? (await import('@subframe7536/sqlite-wasm/opfs')).useOpfsStorage
|
|
22
|
+
: (await import('@subframe7536/sqlite-wasm/idb')).useIdbStorage;
|
|
23
|
+
return sqlite.initSQLiteCore(storage(fileName, { url }));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createRowMapper(
|
|
27
|
+
sqlite: SQLiteDBCore['sqlite'],
|
|
28
|
+
stmt: Parameters<SQLiteDBCore['sqlite']['column_names']>[0]
|
|
29
|
+
) {
|
|
30
|
+
const cols = sqlite.column_names(stmt);
|
|
31
|
+
return (row: unknown[]) =>
|
|
32
|
+
Object.fromEntries(cols.map((key, i) => [key, row[i]]));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function queryData(
|
|
36
|
+
core: SQLiteDBCore,
|
|
37
|
+
sql: string,
|
|
38
|
+
parameters?: readonly unknown[]
|
|
39
|
+
): Promise<QueryResult<Record<string, unknown>>> {
|
|
40
|
+
const iterator = core.sqlite
|
|
41
|
+
.statements(core.pointer, sql)
|
|
42
|
+
[Symbol.asyncIterator]();
|
|
43
|
+
const first = await iterator.next();
|
|
44
|
+
if (first.done || !first.value) {
|
|
45
|
+
throw new Error('Failed to prepare statement');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const stmt = first.value;
|
|
49
|
+
try {
|
|
50
|
+
if (parameters?.length) {
|
|
51
|
+
core.sqlite.bind_collection(stmt, Array.from(parameters));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const size = core.sqlite.column_count(stmt);
|
|
55
|
+
if (size === 0) {
|
|
56
|
+
await core.sqlite.step(stmt);
|
|
57
|
+
return {
|
|
58
|
+
rows: [] as Record<string, unknown>[],
|
|
59
|
+
insertId: parseBigInt(lastInsertRowId(core)),
|
|
60
|
+
numAffectedRows: parseBigInt(changes(core)),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const mapRow = createRowMapper(core.sqlite, stmt);
|
|
65
|
+
const result: Record<string, unknown>[] = [];
|
|
66
|
+
let idx = 0;
|
|
67
|
+
while ((await core.sqlite.step(stmt)) === SQLITE_ROW) {
|
|
68
|
+
result[idx++] = mapRow(core.sqlite.row(stmt));
|
|
69
|
+
}
|
|
70
|
+
return { rows: result };
|
|
71
|
+
} finally {
|
|
72
|
+
await iterator.return?.();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
createWebOnMessageCallback(async (initData: InitData) => {
|
|
77
|
+
const core = await defaultCreateDatabaseFn(initData);
|
|
78
|
+
return {
|
|
79
|
+
db: core,
|
|
80
|
+
query: async (
|
|
81
|
+
_isSelect: boolean,
|
|
82
|
+
sql: string,
|
|
83
|
+
parameters?: readonly unknown[]
|
|
84
|
+
) => await queryData(core, sql, parameters),
|
|
85
|
+
close: async () => await close(core),
|
|
86
|
+
iterator: () => {
|
|
87
|
+
throw new Error('Streaming is not supported by wa-sqlite worker dialect');
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
});
|