Skip to content

Commit 417504c

Browse files
committed
feat(dialect-bun-worker): function to create custom OnMessageCallback in worker, add stream support
stream support requires `Bun@^1.1.31`
1 parent 611ad1c commit 417504c

File tree

7 files changed

+163
-55
lines changed

7 files changed

+163
-55
lines changed

packages/dialect-bun-worker/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,30 @@ From `v0.7.0`, this dialect requires `Bun@^1.1.14`
1010
bun install kysely kysely-bun-worker
1111
```
1212

13+
## Usage
14+
15+
```ts
16+
import { BunWorkerDialect } from 'kysely-bun-worker'
17+
18+
const dialect = new SqliteWorkerDialect({
19+
url: ':memory:',
20+
})
21+
```
22+
23+
### Custom Worker
24+
25+
in `worker.ts`
26+
27+
```ts
28+
import { createOnMessageCallback } from 'kysely-bun-worker'
29+
30+
onmessage = createOnMessageCallback(
31+
async (db) => {
32+
db.loadExtension(/* ... */)
33+
}
34+
)
35+
```
36+
1337
## Config
1438

1539
```ts

packages/dialect-bun-worker/src/driver.ts

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { DatabaseConnection, Driver, QueryResult } from 'kysely'
2-
import type { BunWorkerDialectConfig, EventWithError, MainMsg, WorkerMsg } from './type'
2+
import type { BunWorkerDialectConfig, EventWithError, MainToWorkerMsg, WorkerToMainMsg } from './type'
33
import { EventEmitter } from 'node:events'
44
import { CompiledQuery, SelectQueryNode } from 'kysely'
55

@@ -18,14 +18,14 @@ export class BunWorkerDriver implements Driver {
1818
{ type: 'module' },
1919
)
2020
this.mitt = new EventEmitter()
21-
this.worker.onmessage = ({ data: [type, data, err] }: MessageEvent<WorkerMsg>) => {
21+
this.worker.onmessage = ({ data: [type, data, err] }: MessageEvent<WorkerToMainMsg>) => {
2222
this.mitt?.emit(type, data, err)
2323
}
2424
this.worker.postMessage([
2525
0, // init
2626
this.config?.url,
2727
this.config?.cacheStatment,
28-
] satisfies MainMsg)
28+
] satisfies MainToWorkerMsg)
2929
await new Promise<void>((resolve, reject) => {
3030
this.mitt?.once(0/* init */, (_, err) => err ? reject(err) : resolve())
3131
})
@@ -61,7 +61,7 @@ export class BunWorkerDriver implements Driver {
6161
if (!this.worker) {
6262
return
6363
}
64-
this.worker.postMessage([2] satisfies MainMsg)
64+
this.worker.postMessage([2] satisfies MainToWorkerMsg)
6565
return new Promise<void>((resolve, reject) => {
6666
this.mitt?.once(2/* close */, (_, err) => {
6767
if (err) {
@@ -107,14 +107,54 @@ class BunWorkerConnection implements DatabaseConnection {
107107
private mitt?: EventEmitter<EventWithError>,
108108
) { }
109109

110-
streamQuery<R>(): AsyncIterableIterator<QueryResult<R>> {
111-
throw new Error('Bun:sqlite-worker driver doesn\'t support streaming')
110+
async *streamQuery<R>(compiledQuery: CompiledQuery): AsyncIterableIterator<QueryResult<R>> {
111+
const { parameters, sql, query } = compiledQuery
112+
if (!SelectQueryNode.is(query)) {
113+
throw new Error('WaSqlite dialect only supported SELECT queries')
114+
}
115+
this.worker.postMessage([3, sql, parameters] satisfies MainToWorkerMsg)
116+
let resolver: ((value: IteratorResult<{ rows: QueryResult<R>[] }>) => void) | null = null
117+
let rejecter: ((reason: any) => void) | null = null
118+
119+
this.mitt!.on(3, (data, err) => {
120+
if (err && rejecter) {
121+
rejecter(err)
122+
}
123+
if (resolver) {
124+
resolver({ value: { rows: data! }, done: false })
125+
resolver = null
126+
}
127+
})
128+
129+
this.mitt!.on(4, (_, err) => {
130+
if (err && rejecter) {
131+
rejecter(err)
132+
}
133+
if (resolver) {
134+
resolver({ value: undefined, done: true })
135+
}
136+
})
137+
138+
return {
139+
[Symbol.asyncIterator]() {
140+
return this
141+
},
142+
async next() {
143+
return new Promise<IteratorResult<any>>((resolve, reject) => {
144+
resolver = resolve
145+
rejecter = reject
146+
})
147+
},
148+
async return() {
149+
return { value: undefined, done: true }
150+
},
151+
}
112152
}
113153

114154
async executeQuery<R>(compiledQuery: CompiledQuery<unknown>): Promise<QueryResult<R>> {
115155
const { parameters, sql, query } = compiledQuery
116156
const isSelect = SelectQueryNode.is(query)
117-
this.worker.postMessage([1/* run */, isSelect, sql, parameters] satisfies MainMsg)
157+
this.worker.postMessage([1/* run */, isSelect, sql, parameters] satisfies MainToWorkerMsg)
118158
return new Promise((resolve, reject) => {
119159
if (!this.mitt) {
120160
reject(new Error('kysely instance has been destroyed'))

packages/dialect-bun-worker/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type { BunWorkerDialectConfig } from './type'
33
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from 'kysely'
44
import { BunWorkerDriver } from './driver'
55

6+
export { createOnMessageCallback } from './worker/utils'
7+
68
export class BunWorkerDialect implements Dialect {
79
/**
810
* dialect for `bun:sqlite`, run sql in worker

packages/dialect-bun-worker/src/type.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,24 @@ type InitMsg = [
3636
cache?: boolean,
3737
]
3838

39-
export type MainMsg = InitMsg | RunMsg | CloseMsg
39+
type StreamMsg = [
40+
type: 3,
41+
sql: string,
42+
parameters?: readonly unknown[],
43+
]
44+
45+
export type MainToWorkerMsg = InitMsg | RunMsg | CloseMsg | StreamMsg
4046

41-
export type WorkerMsg = {
47+
export type WorkerToMainMsg = {
4248
[K in keyof Events]: [ type: K, data: Events[K], err: unknown ]
4349
}[keyof Events]
4450

4551
type Events = {
4652
0: null
4753
1: QueryResult<any> | null
4854
2: null
55+
3: QueryResult<any>[] | null
56+
4: null
4957
}
5058

5159
export type EventWithError = {

packages/dialect-bun-worker/src/worker.ts

Lines changed: 0 additions & 46 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { createOnMessageCallback } from './utils'
2+
3+
onmessage = createOnMessageCallback()
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import type { QueryResult } from 'kysely'
2+
import type { MainToWorkerMsg, WorkerToMainMsg } from '../type'
3+
import Database from 'bun:sqlite'
4+
5+
let db: Database
6+
let fn: 'query' | 'prepare'
7+
function run(isSelect: boolean, sql: string, parameters: readonly unknown[]): QueryResult<any> {
8+
const stmt = db[fn](sql)
9+
const rows = stmt.all(parameters as any)
10+
11+
if (isSelect || rows.length) {
12+
return { rows }
13+
}
14+
const { changes, lastInsertRowid } = db.query('SELECT 1').run()
15+
return {
16+
rows,
17+
insertId: BigInt(changes),
18+
numAffectedRows: BigInt(lastInsertRowid),
19+
}
20+
}
21+
22+
function stream(onData: (data: any) => void, sql: string, parameters?: readonly unknown[]): void {
23+
const stmt = db[fn](sql)
24+
if (!('iterate' in stmt)) {
25+
throw new Error('Streaming not supported, please upgrade to Bun@^1.1.31')
26+
}
27+
for (const row of stmt.iterate(...parameters as any)) {
28+
onData(row)
29+
}
30+
}
31+
32+
/**
33+
* Handle worker message, support custom callback on initialization
34+
* @example
35+
* // worker.ts
36+
* import { createOnMessageCallback } from 'kysely-bun-worker'
37+
*
38+
* createOnMessageCallback(
39+
* async (db) => {
40+
* db.loadExtension(...)
41+
* }
42+
* )
43+
*/
44+
export function createOnMessageCallback(
45+
onInit?: (db: typeof Database) => void,
46+
): (event: MessageEvent<MainToWorkerMsg>) => void {
47+
return ({ data: [type, data1, data2, data3] }: MessageEvent<MainToWorkerMsg>) => {
48+
const ret: WorkerToMainMsg = [
49+
type,
50+
null,
51+
null,
52+
]
53+
54+
try {
55+
switch (type) {
56+
case 0:
57+
db = new Database(data1, { create: true })
58+
onInit?.(db as any)
59+
fn = data2 ? 'query' : 'prepare'
60+
break
61+
case 1:
62+
ret[1] = run(data1, data2, data3 || [])
63+
break
64+
case 2:
65+
db.close()
66+
break
67+
case 3:
68+
stream(val => postMessage([3, [val], null] satisfies WorkerToMainMsg), data1, data2)
69+
ret[0] = 4
70+
break
71+
}
72+
} catch (error) {
73+
ret[2] = error
74+
}
75+
postMessage(ret)
76+
}
77+
}

0 commit comments

Comments
 (0)