@pepys/kysely-turso 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,31 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
+ <h1>@pepys/kysely-turso</h1>
2
3
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
4
+ <p>
5
+ <a href="https://www.npmjs.com/package/@pepys/kysely-turso"><img src="https://img.shields.io/npm/v/@pepys/kysely-turso" alt="npm version"></a>
6
+ <a href="../../LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
7
+ </p>
8
+
9
+ <p>
10
+ <a href="../../docs/README.md">Documentation</a>
11
+ </p>
12
+ </div>
13
+
14
+ A Kysely dialect for Turso databases on Bun.
15
+
16
+ `createTursoDialect` takes a function that opens a `@tursodatabase/database` database, and gives
17
+ Kysely a dialect that queries it. The pepys test store and the pepys API use it.
18
+
19
+ ## Install
20
+
21
+ ```sh
22
+ bun add @pepys/kysely-turso
23
+ ```
24
+
25
+ ## Features
26
+
27
+ - `createTursoDialect`: the dialect to pass to `new Kysely({ dialect })`.
28
+
29
+ ## Documentation
30
+
31
+ The [pepys docs](../../docs/README.md) hold the design of the store.
package/package.json CHANGED
@@ -1,6 +1,26 @@
1
1
  {
2
2
  "name": "@pepys/kysely-turso",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "A Kysely dialect for Turso databases on Bun",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/zgeoff/pepys.git",
9
+ "directory": "packages/kysely-turso"
10
+ },
11
+ "files": [
12
+ "src",
13
+ "!src/**/*.test.ts",
14
+ "!src/**/test-utils"
15
+ ],
16
+ "type": "module",
17
+ "exports": "./src/index.ts",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "dependencies": {
22
+ "@tursodatabase/database": "0.8.3",
23
+ "kysely": "0.29.8",
24
+ "tiny-invariant": "1.3.3"
25
+ }
26
+ }
@@ -0,0 +1,59 @@
1
+ import type { Database } from '@tursodatabase/database';
2
+ import type { CompiledQuery, DatabaseConnection, QueryResult } from 'kysely';
3
+
4
+ export function createTursoConnection(database: Database): DatabaseConnection {
5
+ return {
6
+ // Kysely's DatabaseConnection fixes this name
7
+ // oxlint-disable-next-line zgeoff/function-verb
8
+ executeQuery: (compiledQuery) => runQuery(database, compiledQuery),
9
+ streamQuery: (compiledQuery) => streamRows(database, compiledQuery),
10
+ };
11
+ }
12
+
13
+ async function runQuery<Row>(
14
+ database: Database,
15
+ compiledQuery: CompiledQuery,
16
+ ): Promise<QueryResult<Row>> {
17
+ const statement = await database.prepare(compiledQuery.sql);
18
+
19
+ try {
20
+ if (statement.reader) {
21
+ const rows: unknown[] = await statement.all(...compiledQuery.parameters);
22
+
23
+ return { rows: narrowRows<Row>(rows) };
24
+ }
25
+
26
+ const result = await statement.run(...compiledQuery.parameters);
27
+
28
+ return {
29
+ insertId: BigInt(result.lastInsertRowid),
30
+ numAffectedRows: BigInt(result.changes),
31
+ rows: [],
32
+ };
33
+ } finally {
34
+ statement.close();
35
+ }
36
+ }
37
+
38
+ // Kysely owns the row type: the caller's query builder declares it, and the driver only hands over
39
+ // what the database returned.
40
+ function narrowRows<Row>(rows: unknown[]): Row[] {
41
+ return rows.filter((value): value is Row => value !== undefined);
42
+ }
43
+
44
+ async function* streamRows<Row>(
45
+ database: Database,
46
+ compiledQuery: CompiledQuery,
47
+ ): AsyncIterableIterator<QueryResult<Row>> {
48
+ const statement = await database.prepare(compiledQuery.sql);
49
+
50
+ try {
51
+ for await (const row of statement.iterate(...compiledQuery.parameters)) {
52
+ const rows: unknown[] = [row];
53
+
54
+ yield { rows: narrowRows<Row>(rows) };
55
+ }
56
+ } finally {
57
+ statement.close();
58
+ }
59
+ }
@@ -0,0 +1,17 @@
1
+ import type { Database } from '@tursodatabase/database';
2
+ import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from 'kysely';
3
+ import type { Dialect, Kysely } from 'kysely';
4
+ import { createTursoDriver } from './create-turso-driver.ts';
5
+
6
+ export interface TursoDialectConfig {
7
+ readonly database: () => Promise<Database>;
8
+ }
9
+
10
+ export function createTursoDialect(config: TursoDialectConfig): Dialect {
11
+ return {
12
+ createDriver: () => createTursoDriver(config.database),
13
+ createQueryCompiler: () => new SqliteQueryCompiler(),
14
+ createAdapter: () => new SqliteAdapter(),
15
+ createIntrospector: (db: Kysely<unknown>) => new SqliteIntrospector(db),
16
+ };
17
+ }
@@ -0,0 +1,63 @@
1
+ import type { Database } from '@tursodatabase/database';
2
+ import type { DatabaseConnection, Driver } from 'kysely';
3
+ import invariant from 'tiny-invariant';
4
+ import { createTursoConnection } from './create-turso-connection.ts';
5
+
6
+ export function createTursoDriver(openDatabase: () => Promise<Database>): Driver {
7
+ let database: Database | undefined;
8
+ let connection: DatabaseConnection | undefined;
9
+ let queue: Promise<void> = Promise.resolve();
10
+ let releaseCurrent: (() => void) | undefined;
11
+
12
+ const getDatabase = (): Database => {
13
+ invariant(database, 'Kysely initialises the Turso driver before it uses it');
14
+
15
+ return database;
16
+ };
17
+
18
+ return {
19
+ init: async () => {
20
+ database = await openDatabase();
21
+
22
+ connection = createTursoConnection(database);
23
+ },
24
+
25
+ // one connection serves every query, so a transaction holds it until it ends
26
+ acquireConnection: async () => {
27
+ invariant(connection, 'Kysely initialises the Turso driver before it acquires a connection');
28
+
29
+ const acquired = connection;
30
+ const previous = queue;
31
+ const turn = Promise.withResolvers<void>();
32
+
33
+ queue = turn.promise;
34
+
35
+ await previous;
36
+
37
+ releaseCurrent = turn.resolve;
38
+
39
+ return acquired;
40
+ },
41
+ beginTransaction: async () => {
42
+ await getDatabase().exec('begin');
43
+ },
44
+ commitTransaction: async () => {
45
+ await getDatabase().exec('commit');
46
+ },
47
+ rollbackTransaction: async () => {
48
+ await getDatabase().exec('rollback');
49
+ },
50
+ releaseConnection: () => {
51
+ invariant(releaseCurrent, 'Kysely releases only a connection it acquired');
52
+ releaseCurrent();
53
+
54
+ return Promise.resolve();
55
+ },
56
+
57
+ // a connection in use finishes before the database closes
58
+ destroy: async () => {
59
+ await queue;
60
+ await getDatabase().close();
61
+ },
62
+ };
63
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export type { TursoDialectConfig } from './create-turso-dialect.ts';
2
+ export { createTursoDialect } from './create-turso-dialect.ts';