Skip to content

Commit 8da2252

Browse files
committed
feat: add sql.js dialect
1 parent 2d917f3 commit 8da2252

File tree

15 files changed

+383
-0
lines changed

15 files changed

+383
-0
lines changed

packages/dialects/sql.js/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Forked from https://github.com/betarixm/kysely-sql-js
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import config from '@zenstackhq/eslint-config/base.js';
2+
3+
/** @type {import("eslint").Linter.Config} */
4+
export default config;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "@zenstackhq/kysely-sql-js",
3+
"version": "3.0.0-alpha.16",
4+
"description": "Kysely dialect for sql.js",
5+
"type": "module",
6+
"scripts": {
7+
"build": "tsup-node",
8+
"watch": "tsup-node --watch",
9+
"lint": "eslint src --ext ts",
10+
"pack": "pnpm pack"
11+
},
12+
"keywords": [],
13+
"author": "ZenStack Team",
14+
"license": "MIT",
15+
"files": [
16+
"dist"
17+
],
18+
"exports": {
19+
".": {
20+
"import": {
21+
"types": "./dist/index.d.ts",
22+
"default": "./dist/index.js"
23+
},
24+
"require": {
25+
"types": "./dist/index.d.cts",
26+
"default": "./dist/index.cjs"
27+
}
28+
}
29+
},
30+
"devDependencies": {
31+
"@types/sql.js": "^1.4.9",
32+
"@zenstackhq/eslint-config": "workspace:*",
33+
"@zenstackhq/typescript-config": "workspace:*",
34+
"@zenstackhq/vitest-config": "workspace:*",
35+
"sql.js": "^1.13.0",
36+
"kysely": "catalog:"
37+
},
38+
"peerDependencies": {
39+
"sql.js": "^1.13.0",
40+
"kysely": "catalog:"
41+
}
42+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { DatabaseConnection, QueryResult } from 'kysely';
2+
import type { BindParams, Database } from 'sql.js';
3+
4+
import { CompiledQuery } from 'kysely';
5+
6+
export class SqlJsConnection implements DatabaseConnection {
7+
private database: Database;
8+
9+
constructor(database: Database) {
10+
this.database = database;
11+
}
12+
13+
async executeQuery<R>(compiledQuery: CompiledQuery<unknown>): Promise<QueryResult<R>> {
14+
const executeResult = this.database.exec(compiledQuery.sql, compiledQuery.parameters as BindParams);
15+
const rowsModified = this.database.getRowsModified();
16+
return {
17+
numAffectedRows: BigInt(rowsModified),
18+
rows: executeResult
19+
.map(({ columns, values }) =>
20+
values.map((row) => columns.reduce((acc, column, i) => ({ ...acc, [column]: row[i] }), {}) as R),
21+
)
22+
.flat(),
23+
};
24+
}
25+
26+
// eslint-disable-next-line require-yield
27+
async *streamQuery() {
28+
throw new Error('Not supported with SQLite');
29+
}
30+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { Dialect } from 'kysely';
2+
3+
import type { SqlJsDialectConfig } from './types';
4+
5+
import { Kysely, SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from 'kysely';
6+
7+
import { SqlJsDriver } from './driver';
8+
9+
/**
10+
* The SqlJsDialect is for testing purposes only and should not be used in production.
11+
*/
12+
export class SqlJsDialect implements Dialect {
13+
private config: SqlJsDialectConfig;
14+
15+
constructor(config: SqlJsDialectConfig) {
16+
this.config = config;
17+
}
18+
19+
createAdapter = () => new SqliteAdapter();
20+
21+
createDriver = () => new SqlJsDriver(this.config);
22+
23+
createIntrospector = (db: Kysely<any>) => new SqliteIntrospector(db);
24+
25+
createQueryCompiler = () => new SqliteQueryCompiler();
26+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { DatabaseConnection, Driver } from 'kysely';
2+
3+
import { CompiledQuery } from 'kysely';
4+
5+
import { SqlJsConnection } from './connection';
6+
import type { SqlJsDialectConfig } from './types';
7+
8+
export class SqlJsDriver implements Driver {
9+
private config: SqlJsDialectConfig;
10+
11+
constructor(config: SqlJsDialectConfig) {
12+
this.config = config;
13+
}
14+
15+
async acquireConnection(): Promise<DatabaseConnection> {
16+
return new SqlJsConnection(this.config.sqlJs);
17+
}
18+
19+
async beginTransaction(connection: DatabaseConnection): Promise<void> {
20+
await connection.executeQuery(CompiledQuery.raw('BEGIN'));
21+
}
22+
23+
async commitTransaction(connection: DatabaseConnection): Promise<void> {
24+
await connection.executeQuery(CompiledQuery.raw('COMMIT'));
25+
}
26+
27+
async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
28+
await connection.executeQuery(CompiledQuery.raw('ROLLBACK'));
29+
}
30+
31+
async destroy(): Promise<void> {
32+
this.config.sqlJs.close();
33+
}
34+
35+
async init() {}
36+
37+
async releaseConnection(_connection: DatabaseConnection): Promise<void> {}
38+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './connection';
2+
export * from './dialect';
3+
export * from './driver';
4+
export * from './types';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { Database } from 'sql.js';
2+
3+
export interface SqlJsDialectConfig {
4+
sqlJs: Database;
5+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { Database } from './types';
2+
3+
import { Kysely } from 'kysely';
4+
import initSqlJs from 'sql.js';
5+
6+
import { SqlJsDialect } from '../../src';
7+
8+
const SqlJsStatic = await initSqlJs();
9+
10+
export const db = new Kysely<Database>({
11+
dialect: new SqlJsDialect({ sqlJs: new SqlJsStatic.Database() }),
12+
});
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { sql } from 'kysely';
2+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
3+
import { db } from './database';
4+
import * as PersonRepository from './person-repository';
5+
6+
describe('person-repository', () => {
7+
beforeEach(async () => {
8+
await db
9+
.insertInto('person')
10+
.values({
11+
id: 123,
12+
first_name: 'Arnold',
13+
last_name: 'Schwarzenegger',
14+
gender: 'other',
15+
})
16+
.executeTakeFirstOrThrow();
17+
});
18+
19+
beforeAll(async () => {
20+
await db.schema
21+
.createTable('person')
22+
.addColumn('id', 'integer', (cb) => cb.primaryKey().autoIncrement().notNull())
23+
.addColumn('first_name', 'varchar(255)', (cb) => cb.notNull())
24+
.addColumn('last_name', 'varchar(255)')
25+
.addColumn('gender', 'varchar(50)', (cb) => cb.notNull())
26+
.addColumn('created_at', 'timestamp', (cb) => cb.notNull().defaultTo(sql`current_timestamp`))
27+
.execute();
28+
});
29+
30+
afterEach(async () => {
31+
await sql`delete from ${sql.table('person')}`.execute(db);
32+
});
33+
34+
afterAll(async () => {
35+
await db.schema.dropTable('person').execute();
36+
});
37+
38+
it('should find a person with a given id', async () => {
39+
expect(await PersonRepository.findPersonById(123)).toMatchObject({
40+
id: 123,
41+
first_name: 'Arnold',
42+
last_name: 'Schwarzenegger',
43+
gender: 'other',
44+
});
45+
});
46+
47+
it('should find all people named Arnold', async () => {
48+
const people = await PersonRepository.findPeople({ first_name: 'Arnold' });
49+
50+
expect(people).toHaveLength(1);
51+
expect(people[0]).toMatchObject({
52+
id: 123,
53+
first_name: 'Arnold',
54+
last_name: 'Schwarzenegger',
55+
gender: 'other',
56+
});
57+
});
58+
59+
it('should update gender of a person with a given id', async () => {
60+
await PersonRepository.updatePerson(123, { gender: 'woman' });
61+
62+
expect(await PersonRepository.findPersonById(123)).toMatchObject({
63+
id: 123,
64+
first_name: 'Arnold',
65+
last_name: 'Schwarzenegger',
66+
gender: 'woman',
67+
});
68+
});
69+
70+
it('should create a person', async () => {
71+
await PersonRepository.createPerson({
72+
first_name: 'Jennifer',
73+
last_name: 'Aniston',
74+
gender: 'woman',
75+
});
76+
77+
expect(await PersonRepository.findPeople({ first_name: 'Jennifer' })).toHaveLength(1);
78+
});
79+
80+
it('should create multiple persons', async () => {
81+
const created = await PersonRepository.createPersons([
82+
{ first_name: 'Brad', last_name: 'Pitt', gender: 'man' },
83+
{ first_name: 'Angelina', last_name: 'Jolie', gender: 'woman' },
84+
]);
85+
console.log(created);
86+
87+
await expect(PersonRepository.findPeople({ first_name: 'Brad' })).resolves.toBeTruthy();
88+
await expect(PersonRepository.findPeople({ first_name: 'Angelina' })).resolves.toBeTruthy();
89+
});
90+
91+
it('should delete a person with a given id', async () => {
92+
await PersonRepository.deletePerson(123);
93+
94+
expect(await PersonRepository.findPersonById(123)).toBeUndefined();
95+
});
96+
});

0 commit comments

Comments
 (0)