Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add query builder package #77

Merged
merged 32 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8410d2d
wip: tests
stevensJourney Feb 21, 2024
e384583
maximize worker URI compatibility
stevensJourney Feb 21, 2024
3463698
update lockfile
stevensJourney Feb 22, 2024
6b6bd17
added a sample test
stevensJourney Feb 22, 2024
0e133fe
Update .github/workflows/test.yml
stevensJourney Feb 22, 2024
4069e2e
wip: add crud tests
stevensJourney Feb 22, 2024
cf78538
feat(query-builder): add query builder package
Feb 22, 2024
bb603bc
added crud and schema tests
stevensJourney Feb 22, 2024
f48c8f8
feat(query-builder): add query builder package
Feb 23, 2024
e164400
chore: format files
Feb 23, 2024
5db1e1e
chore: merge main
Feb 23, 2024
e02735a
chore: pr reverts
Feb 23, 2024
c5b070d
chore: rename package
Feb 23, 2024
aeabe29
chore: move web-sdk to dev dep
Feb 23, 2024
64620d9
revert execute transaction change
stevensJourney Feb 23, 2024
05c4e9b
Merge branch 'web-tests' into feature/add-kysley
Feb 26, 2024
1526f58
chore: merge main
Feb 26, 2024
337c0a1
chore: pr feedback
Feb 26, 2024
9d058f2
chore: pr feedback
Feb 26, 2024
daadedc
fix: failing tests
Feb 26, 2024
1843faa
fix: failing tests
Feb 26, 2024
19b150b
docs: add readme
Feb 27, 2024
4f0478b
chore: pr feedback
Feb 27, 2024
92aa297
chore: pr feedback
Feb 27, 2024
8167f60
chore: add Kysely options
Feb 27, 2024
87299d2
chore: add JSON type
Feb 27, 2024
7a3f8a7
chore: merge main
Feb 27, 2024
7b7f166
fix: failing tests
Feb 27, 2024
0a7ec8e
fix: failing tests
Feb 27, 2024
459cd9e
fix: failing tests
Feb 27, 2024
bac8172
chore: format file
Feb 27, 2024
eb56df8
chore: pr feedback
Feb 27, 2024
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
47 changes: 47 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Ensures packages test correctly
name: Test Packages

on:
push:

jobs:
test:
name: Test Packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Setup NodeJS
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'

- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 8
run_install: false

- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install dependencies
run: pnpm install

- name: Build
run: pnpm build:packages

- name: Test
run: pnpm test
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
"description": "monorepo for powersync javascript sdks",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint .",
"clean": "pnpm run -r clean",
"ci:version": "changeset version && pnpm install --no-frozen-lockfile",
"build:packages": "pnpm run --filter './packages/**' -r build",
"build": "pnpm run -r build",
"ci:publish": "changeset publish && git push --follow-tags",
"docs:start": "pnpm --filter docs start",
"ci:version": "changeset version && pnpm install --no-frozen-lockfile",
"clean": "pnpm run -r clean",
"docs:build": "pnpm --filter docs build",
"build": "pnpm run -r build",
"build:packages": "pnpm run --filter './packages/**' -r build",
"docs:start": "pnpm --filter docs start",
"format": "prettier --write .",
"release": "pnpm build:packages && pnpm changeset publish"
"lint": "eslint .",
"release": "pnpm build:packages && pnpm changeset publish",
"test": "pnpm run -r test"
},
"keywords": [],
"type": "module",
Expand Down
45 changes: 45 additions & 0 deletions packages/kysely-driver/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "@powersync/kysely-driver",
stevensJourney marked this conversation as resolved.
Show resolved Hide resolved
"version": "0.0.1",
"description": "Kysely driver for PowerSync",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"author": "JOURNEYAPPS",
"license": "Apache-2.0",
"files": [
"lib"
],
"repository": "https://github.com/powersync-ja/powersync-js",
"bugs": {
"url": "https://github.com/powersync-ja/powersync-js/issues"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"homepage": "https://docs.powersync.com",
"scripts": {
"build": "tsc --build",
"clean": "rm -rf dist tsconfig.tsbuildinfo",
"watch": "tsc --build -w",
"test": "pnpm build && vitest"
},
"dependencies": {
"@journeyapps/powersync-sdk-common": "workspace:*",
"kysely": "^0.27.2"
},
"devDependencies": {
"@journeyapps/powersync-sdk-web": "workspace:*",
"@journeyapps/wa-sqlite": "^0.1.1",
"@types/node": "^20.11.17",
"@vitest/browser": "^1.3.1",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"typescript": "^5.3.3",
"vite": "^5.1.1",
"vite-plugin-top-level-await": "^1.4.1",
"vite-plugin-wasm": "^3.3.0",
"vitest": "^1.3.0",
"webdriverio": "^8.32.3"
}
}
3 changes: 3 additions & 0 deletions packages/kysely-driver/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createPowerSyncDb } from './sqlite/db';

export { createPowerSyncDb };
11 changes: 11 additions & 0 deletions packages/kysely-driver/src/sqlite/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PowerSyncDialect } from './sqlite-dialect';
import { Kysely } from 'kysely';
import { type AbstractPowerSyncDatabase } from '@journeyapps/powersync-sdk-common';

export const createPowerSyncDb = <T>(db: AbstractPowerSyncDatabase) => {
return new Kysely<T>({
dialect: new PowerSyncDialect({
db
})
});
};
111 changes: 111 additions & 0 deletions packages/kysely-driver/src/sqlite/sqlite-connection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { type AbstractPowerSyncDatabase, type Transaction } from '@journeyapps/powersync-sdk-common';
import { CompiledQuery, DatabaseConnection, QueryResult } from 'kysely';

/**
* Represent a Kysely connection to the PowerSync database.
*
* The actual locks are acquired on-demand when a transaction is started.
*
* When not using transactions, we rely on the automatic locks.
*
* This allows us to bypass write locks when doing pure select queries outside a transaction.
*/
export class PowerSyncConnection implements DatabaseConnection {
readonly #db: AbstractPowerSyncDatabase;
#release?: () => void;
#tx?: Transaction;

constructor(db: AbstractPowerSyncDatabase) {
this.#db = db;
}

async executeQuery<O>(compiledQuery: CompiledQuery): Promise<QueryResult<O>> {
const { sql, parameters, query } = compiledQuery;

const context = this.#tx ?? this.#db;

if (query.kind === 'SelectQueryNode') {
// Optimizaton: use getAll() instead of execute() if it's a select query
const rows = await context.getAll(sql, parameters as unknown[]);
return {
rows: rows as O[]
};
}

const result = await context.execute(sql, parameters as unknown[]);

return {
insertId: result.insertId ? BigInt(result.insertId!) : undefined,
numAffectedRows: BigInt(result.rowsAffected),
rows: result.rows?._array ?? []
};
}

async *streamQuery<R>(compiledQuery: CompiledQuery): AsyncIterableIterator<QueryResult<R>> {
// Not actually streamed
stevensJourney marked this conversation as resolved.
Show resolved Hide resolved
const results = await this.executeQuery<R>(compiledQuery);
yield {
rows: results.rows
};
}

async beginTransaction(): Promise<void> {
let doResolve: any;
let doReject: any;
let doRelease: any;

const lockPromise = new Promise<PowerSyncConnection>((resolve, reject) => {
doResolve = resolve;
doReject = reject;
});

this.#db
.writeTransaction(async (tx) => {
this.#tx = tx;
const releasePromise = new Promise<void>((reject) => {
doRelease = reject;
});
doResolve();
await releasePromise;
})
.catch(doReject);

await lockPromise;
this.#release = doRelease;
}

async commitTransaction(): Promise<void> {
if (!this.#tx) {
throw new Error('Transaction is not defined');
}

if (!this.#release) {
throw new Error('Release is not defined');
}

await this.#tx.commit();
this.#tx = undefined;
this.#release();
this.#release = undefined;
}

async rollbackTransaction(): Promise<void> {
if (!this.#tx) {
throw new Error('Transaction is not defined');
}

if (!this.#release) {
throw new Error('Release is not defined');
}

await this.#tx.rollback();
this.#tx = undefined;
this.#release();
this.#release = undefined;
}

async releaseConnection(): Promise<void> {
// is this write?
this.#db.disconnect();
}
}
36 changes: 36 additions & 0 deletions packages/kysely-driver/src/sqlite/sqlite-dialect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
DatabaseIntrospector,
Dialect,
DialectAdapter,
Driver,
Kysely,
QueryCompiler,
SqliteAdapter,
SqliteIntrospector,
SqliteQueryCompiler
} from 'kysely';
import { PowerSyncDialectConfig, PowerSyncDriver } from './sqlite-driver';

export class PowerSyncDialect implements Dialect {
readonly #config: PowerSyncDialectConfig;

constructor(config: PowerSyncDialectConfig) {
this.#config = Object.freeze({ ...config });
}

createDriver(): Driver {
return new PowerSyncDriver(this.#config);
}

createQueryCompiler(): QueryCompiler {
return new SqliteQueryCompiler();
}

createAdapter(): DialectAdapter {
return new SqliteAdapter();
}

createIntrospector(db: Kysely<unknown>): DatabaseIntrospector {
return new SqliteIntrospector(db);
}
}
42 changes: 42 additions & 0 deletions packages/kysely-driver/src/sqlite/sqlite-driver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { type AbstractPowerSyncDatabase } from '@journeyapps/powersync-sdk-common';
import { DatabaseConnection, Driver } from 'kysely';
import { PowerSyncConnection } from './sqlite-connection';

export interface PowerSyncDialectConfig {
db: AbstractPowerSyncDatabase;
}

export class PowerSyncDriver implements Driver {
readonly #db: AbstractPowerSyncDatabase;

constructor(config: PowerSyncDialectConfig) {
this.#db = config.db;
}

async init(): Promise<void> { }

async acquireConnection(): Promise<DatabaseConnection> {
// Always create a new connection instance
return new PowerSyncConnection(this.#db);
}

async beginTransaction(connection: DatabaseConnection): Promise<void> {
await (connection as PowerSyncConnection).beginTransaction();
DominicGBauer marked this conversation as resolved.
Show resolved Hide resolved
}

async commitTransaction(connection: DatabaseConnection): Promise<void> {
await (connection as PowerSyncConnection).commitTransaction();
}

async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
await (connection as PowerSyncConnection).rollbackTransaction();
}

async releaseConnection(connection: DatabaseConnection): Promise<void> {
await (connection as PowerSyncConnection).releaseConnection();
}

async destroy(): Promise<void> {
this.#db.disconnectAndClear();
}
}
33 changes: 33 additions & 0 deletions packages/kysely-driver/tests/setup/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Kysely } from 'kysely';
import { PowerSyncDialect } from '../../src/sqlite/sqlite-dialect';
import { Database } from './types';
import {
Column,
ColumnType,
Schema,
Table,
WASQLitePowerSyncDatabaseOpenFactory
} from '@journeyapps/powersync-sdk-web';

const TestSchema = new Schema([
new Table({
name: 'users',
columns: [new Column({ name: 'name', type: ColumnType.TEXT })]
})
]);

export const getPowerSyncDb = () => {
const factory = new WASQLitePowerSyncDatabaseOpenFactory({
dbFilename: 'test.db',
schema: TestSchema
});

return factory.getInstance();
};

export const getKyselyDb = () =>
new Kysely<Database>({
dialect: new PowerSyncDialect({
db: getPowerSyncDb()
})
});
14 changes: 14 additions & 0 deletions packages/kysely-driver/tests/setup/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ColumnType, Insertable, Selectable, Updateable } from 'kysely';

export interface Database {
users: UsersTable;
}

export interface UsersTable {
id: ColumnType<string, string, never>;
name: string;
}

export type Users = Selectable<UsersTable>;
export type NewUsers = Insertable<UsersTable>;
export type UsersUpdate = Updateable<UsersTable>;
Loading
Loading