Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/dialects/sql.js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Forked from https://github.com/betarixm/kysely-sql-js
4 changes: 4 additions & 0 deletions packages/dialects/sql.js/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import config from '@zenstackhq/eslint-config/base.js';

/** @type {import("eslint").Linter.Config} */
export default config;
42 changes: 42 additions & 0 deletions packages/dialects/sql.js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@zenstackhq/kysely-sql-js",
"version": "3.0.0-alpha.16",
"description": "Kysely dialect for sql.js",
"type": "module",
"scripts": {
"build": "tsup-node",
"watch": "tsup-node --watch",
"lint": "eslint src --ext ts",
"pack": "pnpm pack"
},
"keywords": [],
"author": "ZenStack Team",
"license": "MIT",
"files": [
"dist"
],
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"devDependencies": {
"@types/sql.js": "^1.4.9",
"@zenstackhq/eslint-config": "workspace:*",
"@zenstackhq/typescript-config": "workspace:*",
"@zenstackhq/vitest-config": "workspace:*",
"sql.js": "^1.13.0",
"kysely": "catalog:"
},
"peerDependencies": {
"sql.js": "^1.13.0",
"kysely": "catalog:"
}
}
30 changes: 30 additions & 0 deletions packages/dialects/sql.js/src/connection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { DatabaseConnection, QueryResult } from 'kysely';
import type { BindParams, Database } from 'sql.js';

import { CompiledQuery } from 'kysely';

export class SqlJsConnection implements DatabaseConnection {
private database: Database;

constructor(database: Database) {
this.database = database;
}

async executeQuery<R>(compiledQuery: CompiledQuery<unknown>): Promise<QueryResult<R>> {
const executeResult = this.database.exec(compiledQuery.sql, compiledQuery.parameters as BindParams);
const rowsModified = this.database.getRowsModified();
return {
numAffectedRows: BigInt(rowsModified),
rows: executeResult
.map(({ columns, values }) =>
values.map((row) => columns.reduce((acc, column, i) => ({ ...acc, [column]: row[i] }), {}) as R),
)
.flat(),
};
}

// eslint-disable-next-line require-yield
async *streamQuery() {
throw new Error('Not supported with SQLite');
}
}
26 changes: 26 additions & 0 deletions packages/dialects/sql.js/src/dialect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Dialect } from 'kysely';

import type { SqlJsDialectConfig } from './types';

import { Kysely, SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from 'kysely';

import { SqlJsDriver } from './driver';

/**
* The SqlJsDialect is for testing purposes only and should not be used in production.
*/
export class SqlJsDialect implements Dialect {
private config: SqlJsDialectConfig;

constructor(config: SqlJsDialectConfig) {
this.config = config;
}

createAdapter = () => new SqliteAdapter();

createDriver = () => new SqlJsDriver(this.config);

createIntrospector = (db: Kysely<any>) => new SqliteIntrospector(db);

createQueryCompiler = () => new SqliteQueryCompiler();
}
38 changes: 38 additions & 0 deletions packages/dialects/sql.js/src/driver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { DatabaseConnection, Driver } from 'kysely';

import { CompiledQuery } from 'kysely';

import { SqlJsConnection } from './connection';
import type { SqlJsDialectConfig } from './types';

export class SqlJsDriver implements Driver {
private config: SqlJsDialectConfig;

constructor(config: SqlJsDialectConfig) {
this.config = config;
}

async acquireConnection(): Promise<DatabaseConnection> {
return new SqlJsConnection(this.config.sqlJs);
}

async beginTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw('BEGIN'));
}

async commitTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw('COMMIT'));
}

async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw('ROLLBACK'));
}

async destroy(): Promise<void> {
this.config.sqlJs.close();
}

async init() {}

async releaseConnection(_connection: DatabaseConnection): Promise<void> {}
}
4 changes: 4 additions & 0 deletions packages/dialects/sql.js/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './connection';
export * from './dialect';
export * from './driver';
export * from './types';
5 changes: 5 additions & 0 deletions packages/dialects/sql.js/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { Database } from 'sql.js';

export interface SqlJsDialectConfig {
sqlJs: Database;
}
12 changes: 12 additions & 0 deletions packages/dialects/sql.js/test/getting-started/database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Database } from './types';

import { Kysely } from 'kysely';
import initSqlJs from 'sql.js';

import { SqlJsDialect } from '../../src';

const SqlJsStatic = await initSqlJs();

export const db = new Kysely<Database>({
dialect: new SqlJsDialect({ sqlJs: new SqlJsStatic.Database() }),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { sql } from 'kysely';
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { db } from './database';
import * as PersonRepository from './person-repository';

describe('person-repository', () => {
beforeEach(async () => {
await db
.insertInto('person')
.values({
id: 123,
first_name: 'Arnold',
last_name: 'Schwarzenegger',
gender: 'other',
})
.executeTakeFirstOrThrow();
});

beforeAll(async () => {
await db.schema
.createTable('person')
.addColumn('id', 'integer', (cb) => cb.primaryKey().autoIncrement().notNull())
.addColumn('first_name', 'varchar(255)', (cb) => cb.notNull())
.addColumn('last_name', 'varchar(255)')
.addColumn('gender', 'varchar(50)', (cb) => cb.notNull())
.addColumn('created_at', 'timestamp', (cb) => cb.notNull().defaultTo(sql`current_timestamp`))
.execute();
});

afterEach(async () => {
await sql`delete from ${sql.table('person')}`.execute(db);
});

afterAll(async () => {
await db.schema.dropTable('person').execute();
});

it('should find a person with a given id', async () => {
expect(await PersonRepository.findPersonById(123)).toMatchObject({
id: 123,
first_name: 'Arnold',
last_name: 'Schwarzenegger',
gender: 'other',
});
});

it('should find all people named Arnold', async () => {
const people = await PersonRepository.findPeople({ first_name: 'Arnold' });

expect(people).toHaveLength(1);
expect(people[0]).toMatchObject({
id: 123,
first_name: 'Arnold',
last_name: 'Schwarzenegger',
gender: 'other',
});
});

it('should update gender of a person with a given id', async () => {
await PersonRepository.updatePerson(123, { gender: 'woman' });

expect(await PersonRepository.findPersonById(123)).toMatchObject({
id: 123,
first_name: 'Arnold',
last_name: 'Schwarzenegger',
gender: 'woman',
});
});

it('should create a person', async () => {
await PersonRepository.createPerson({
first_name: 'Jennifer',
last_name: 'Aniston',
gender: 'woman',
});

expect(await PersonRepository.findPeople({ first_name: 'Jennifer' })).toHaveLength(1);
});

it('should create multiple persons', async () => {
const created = await PersonRepository.createPersons([
{ first_name: 'Brad', last_name: 'Pitt', gender: 'man' },
{ first_name: 'Angelina', last_name: 'Jolie', gender: 'woman' },
]);
console.log(created);

await expect(PersonRepository.findPeople({ first_name: 'Brad' })).resolves.toBeTruthy();
await expect(PersonRepository.findPeople({ first_name: 'Angelina' })).resolves.toBeTruthy();
});

it('should delete a person with a given id', async () => {
await PersonRepository.deletePerson(123);

expect(await PersonRepository.findPersonById(123)).toBeUndefined();
});
});
48 changes: 48 additions & 0 deletions packages/dialects/sql.js/test/getting-started/person-repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { PersonUpdate, Person, NewPerson } from './types';

import { db } from './database';

export async function findPersonById(id: number) {
return await db.selectFrom('person').where('id', '=', id).selectAll().executeTakeFirst();
}

export async function findPeople(criteria: Partial<Person>) {
let query = db.selectFrom('person');

if (criteria.id) {
query = query.where('id', '=', criteria.id); // Kysely is immutable, you must re-assign!
}

if (criteria.first_name) {
query = query.where('first_name', '=', criteria.first_name);
}

if (criteria.last_name !== undefined) {
query = query.where('last_name', criteria.last_name === null ? 'is' : '=', criteria.last_name);
}

if (criteria.gender) {
query = query.where('gender', '=', criteria.gender);
}

if (criteria.created_at) {
query = query.where('created_at', '=', criteria.created_at);
}

return await query.selectAll().execute();
}

export async function updatePerson(id: number, updateWith: PersonUpdate) {
await db.updateTable('person').set(updateWith).where('id', '=', id).execute();
}

export async function createPerson(person: NewPerson) {
return await db.insertInto('person').values(person).returningAll().executeTakeFirstOrThrow();
}

export async function createPersons(persons: NewPerson[]) {
return await db.insertInto('person').values(persons).executeTakeFirstOrThrow();
}
export async function deletePerson(id: number) {
return await db.deleteFrom('person').where('id', '=', id).returningAll().executeTakeFirst();
}
53 changes: 53 additions & 0 deletions packages/dialects/sql.js/test/getting-started/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { ColumnType, Generated, Insertable, Selectable, Updateable } from 'kysely';

export interface Database {
person: PersonTable;
pet: PetTable;
}

// This interface describes the `person` table to Kysely. Table
// interfaces should only be used in the `Database` type above
// and never as a result type of a query!. See the `Person`,
// `NewPerson` and `PersonUpdate` types below.
export interface PersonTable {
// Columns that are generated by the database should be marked
// using the `Generated` type. This way they are automatically
// made optional in inserts and updates.
id: Generated<number>;

first_name: string;
gender: 'man' | 'woman' | 'other';

// If the column is nullable in the database, make its type nullable.
// Don't use optional properties. Optionality is always determined
// automatically by Kysely.
last_name: string | null;

// You can specify a different type for each operation (select, insert and
// update) using the `ColumnType<SelectType, InsertType, UpdateType>`
// wrapper. Here we define a column `created_at` that is selected as
// a `Date`, can optionally be provided as a `string` in inserts and
// can never be updated:
created_at: ColumnType<Date, string | undefined, never>;
}

// You should not use the table schema interfaces directly. Instead, you should
// use the `Selectable`, `Insertable` and `Updateable` wrappers. These wrappers
// make sure that the correct types are used in each operation.
//
// Most of the time you should trust the type inference and not use explicit
// types at all. These types can be useful when typing function arguments.
export type Person = Selectable<PersonTable>;
export type NewPerson = Insertable<PersonTable>;
export type PersonUpdate = Updateable<PersonTable>;

export interface PetTable {
id: Generated<number>;
name: string;
owner_id: number;
species: 'dog' | 'cat';
}

export type Pet = Selectable<PetTable>;
export type NewPet = Insertable<PetTable>;
export type PetUpdate = Updateable<PetTable>;
7 changes: 7 additions & 0 deletions packages/dialects/sql.js/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@zenstackhq/typescript-config/base.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src/**/*", "test/**/*"]
}
13 changes: 13 additions & 0 deletions packages/dialects/sql.js/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from 'tsup';

export default defineConfig({
entry: {
index: 'src/index.ts',
},
outDir: 'dist',
splitting: false,
sourcemap: true,
clean: true,
dts: true,
format: ['cjs', 'esm'],
});
4 changes: 4 additions & 0 deletions packages/dialects/sql.js/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { defineConfig, mergeConfig } from 'vitest/config';
import base from '@zenstackhq/vitest-config/base';

export default mergeConfig(base, defineConfig({}));