Skip to content

Commit f8cad95

Browse files
committed
Merge branch 'main' into configurable-vfs
2 parents c84a8e0 + e79ed9b commit f8cad95

27 files changed

+319
-197
lines changed

packages/drizzle-driver/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# @powersync/drizzle-driver
22

3+
## 0.3.2
4+
5+
### Patch Changes
6+
7+
- 6580f29: Added support for custom column types when converting a Drizzle schema to a PowerSync app schema.
8+
9+
## 0.3.1
10+
11+
### Patch Changes
12+
13+
- 86a753f: Fixed Drizzle transactions breaking for react-native projects, correctly using lock context for transactions.
14+
315
## 0.3.0
416

517
### Minor Changes

packages/drizzle-driver/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@powersync/drizzle-driver",
3-
"version": "0.3.0",
3+
"version": "0.3.2",
44
"description": "Drizzle driver for PowerSync",
55
"main": "lib/src/index.js",
66
"types": "lib/src/index.d.ts",

packages/drizzle-driver/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { wrapPowerSyncWithDrizzle, type DrizzleQuery, type PowerSyncSQLiteDatabase } from './sqlite/db';
1+
import {
2+
wrapPowerSyncWithDrizzle,
3+
type DrizzleQuery,
4+
type PowerSyncSQLiteDatabase
5+
} from './sqlite/PowerSyncSQLiteDatabase';
26
import { toCompilableQuery } from './utils/compilableQuery';
37
import {
48
DrizzleAppSchema,
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { AbstractPowerSyncDatabase, QueryResult } from '@powersync/common';
1+
import { LockContext, QueryResult } from '@powersync/common';
22
import { entityKind } from 'drizzle-orm/entity';
33
import type { Logger } from 'drizzle-orm/logger';
44
import { NoopLogger } from 'drizzle-orm/logger';
55
import type { RelationalSchemaConfig, TablesRelationalConfig } from 'drizzle-orm/relations';
6-
import { type Query, sql } from 'drizzle-orm/sql/sql';
6+
import { type Query } from 'drizzle-orm/sql/sql';
77
import type { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core/dialect';
88
import type { SelectedFieldsOrdered } from 'drizzle-orm/sqlite-core/query-builders/select.types';
99
import {
@@ -13,7 +13,7 @@ import {
1313
SQLiteTransaction,
1414
type SQLiteTransactionConfig
1515
} from 'drizzle-orm/sqlite-core/session';
16-
import { PowerSyncSQLitePreparedQuery } from './sqlite-query';
16+
import { PowerSyncSQLitePreparedQuery } from './PowerSyncSQLitePreparedQuery';
1717

1818
export interface PowerSyncSQLiteSessionOptions {
1919
logger?: Logger;
@@ -30,19 +30,19 @@ export class PowerSyncSQLiteTransaction<
3030
static readonly [entityKind]: string = 'PowerSyncSQLiteTransaction';
3131
}
3232

33-
export class PowerSyncSQLiteSession<
33+
export class PowerSyncSQLiteBaseSession<
3434
TFullSchema extends Record<string, unknown>,
3535
TSchema extends TablesRelationalConfig
3636
> extends SQLiteSession<'async', QueryResult, TFullSchema, TSchema> {
37-
static readonly [entityKind]: string = 'PowerSyncSQLiteSession';
37+
static readonly [entityKind]: string = 'PowerSyncSQLiteBaseSession';
3838

39-
private logger: Logger;
39+
protected logger: Logger;
4040

4141
constructor(
42-
private db: AbstractPowerSyncDatabase,
43-
dialect: SQLiteAsyncDialect,
44-
private schema: RelationalSchemaConfig<TSchema> | undefined,
45-
options: PowerSyncSQLiteSessionOptions = {}
42+
protected db: LockContext,
43+
protected dialect: SQLiteAsyncDialect,
44+
protected schema: RelationalSchemaConfig<TSchema> | undefined,
45+
protected options: PowerSyncSQLiteSessionOptions = {}
4646
) {
4747
super(dialect);
4848
this.logger = options.logger ?? new NoopLogger();
@@ -66,33 +66,10 @@ export class PowerSyncSQLiteSession<
6666
);
6767
}
6868

69-
override transaction<T>(
70-
transaction: (tx: PowerSyncSQLiteTransaction<TFullSchema, TSchema>) => T,
71-
config: PowerSyncSQLiteTransactionConfig = {}
69+
transaction<T>(
70+
_transaction: (tx: PowerSyncSQLiteTransaction<TFullSchema, TSchema>) => T,
71+
_config: PowerSyncSQLiteTransactionConfig = {}
7272
): T {
73-
const { accessMode = 'read write' } = config;
74-
75-
if (accessMode === 'read only') {
76-
return this.db.readLock(async () => this.internalTransaction(transaction, config)) as T;
77-
}
78-
79-
return this.db.writeLock(async () => this.internalTransaction(transaction, config)) as T;
80-
}
81-
82-
async internalTransaction<T>(
83-
transaction: (tx: PowerSyncSQLiteTransaction<TFullSchema, TSchema>) => T,
84-
config: PowerSyncSQLiteTransactionConfig = {}
85-
): Promise<T> {
86-
const tx = new PowerSyncSQLiteTransaction('async', (this as any).dialect, this, this.schema);
87-
88-
await this.run(sql.raw(`begin${config?.behavior ? ' ' + config.behavior : ''}`));
89-
try {
90-
const result = await transaction(tx);
91-
await this.run(sql`commit`);
92-
return result;
93-
} catch (err) {
94-
await this.run(sql`rollback`);
95-
throw err;
96-
}
73+
throw new Error('Nested transactions are not supported');
9774
}
9875
}

packages/drizzle-driver/src/sqlite/db.ts renamed to packages/drizzle-driver/src/sqlite/PowerSyncSQLiteDatabase.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core/db';
1919
import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core/dialect';
2020
import type { DrizzleConfig } from 'drizzle-orm/utils';
2121
import { toCompilableQuery } from './../utils/compilableQuery';
22-
import { PowerSyncSQLiteSession, PowerSyncSQLiteTransactionConfig } from './sqlite-session';
22+
import { PowerSyncSQLiteSession } from './PowerSyncSQLiteSession';
23+
import { PowerSyncSQLiteTransactionConfig } from './PowerSyncSQLiteBaseSession';
2324

2425
export type DrizzleQuery<T> = { toSQL(): Query; execute(): Promise<T | T[]> };
2526

@@ -55,7 +56,7 @@ export class PowerSyncSQLiteDatabase<
5556
this.db = db;
5657
}
5758

58-
override transaction<T>(
59+
transaction<T>(
5960
transaction: (
6061
tx: SQLiteTransaction<'async', QueryResult, TSchema, ExtractTablesWithRelations<TSchema>>
6162
) => Promise<T>,

packages/drizzle-driver/src/sqlite/sqlite-query.ts renamed to packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AbstractPowerSyncDatabase, QueryResult } from '@powersync/common';
1+
import { LockContext, QueryResult } from '@powersync/common';
22
import { Column, DriverValueDecoder, getTableName, SQL } from 'drizzle-orm';
33
import { entityKind, is } from 'drizzle-orm/entity';
44
import type { Logger } from 'drizzle-orm/logger';
@@ -26,7 +26,7 @@ export class PowerSyncSQLitePreparedQuery<
2626
static readonly [entityKind]: string = 'PowerSyncSQLitePreparedQuery';
2727

2828
constructor(
29-
private db: AbstractPowerSyncDatabase,
29+
private db: LockContext,
3030
query: Query,
3131
private logger: Logger,
3232
private fields: SelectedFieldsOrdered | undefined,
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { AbstractPowerSyncDatabase, DBAdapter } from '@powersync/common';
2+
import { entityKind } from 'drizzle-orm/entity';
3+
import type { RelationalSchemaConfig, TablesRelationalConfig } from 'drizzle-orm/relations';
4+
import { type Query } from 'drizzle-orm/sql/sql';
5+
import type { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core/dialect';
6+
import type { SelectedFieldsOrdered } from 'drizzle-orm/sqlite-core/query-builders/select.types';
7+
import {
8+
type PreparedQueryConfig as PreparedQueryConfigBase,
9+
type SQLiteExecuteMethod
10+
} from 'drizzle-orm/sqlite-core/session';
11+
import { PowerSyncSQLitePreparedQuery } from './PowerSyncSQLitePreparedQuery';
12+
import {
13+
PowerSyncSQLiteSessionOptions,
14+
PowerSyncSQLiteTransaction,
15+
PowerSyncSQLiteTransactionConfig,
16+
PowerSyncSQLiteBaseSession
17+
} from './PowerSyncSQLiteBaseSession';
18+
19+
export class PowerSyncSQLiteSession<
20+
TFullSchema extends Record<string, unknown>,
21+
TSchema extends TablesRelationalConfig
22+
> extends PowerSyncSQLiteBaseSession<TFullSchema, TSchema> {
23+
static readonly [entityKind]: string = 'PowerSyncSQLiteSession';
24+
protected client: AbstractPowerSyncDatabase;
25+
constructor(
26+
db: AbstractPowerSyncDatabase,
27+
dialect: SQLiteAsyncDialect,
28+
schema: RelationalSchemaConfig<TSchema> | undefined,
29+
options: PowerSyncSQLiteSessionOptions = {}
30+
) {
31+
super(db, dialect, schema, options);
32+
this.client = db;
33+
}
34+
35+
transaction<T>(
36+
transaction: (tx: PowerSyncSQLiteTransaction<TFullSchema, TSchema>) => T,
37+
config: PowerSyncSQLiteTransactionConfig = {}
38+
): T {
39+
const { accessMode = 'read write' } = config;
40+
41+
if (accessMode === 'read only') {
42+
return this.client.readLock(async (ctx) => this.internalTransaction(ctx, transaction, config)) as T;
43+
}
44+
45+
return this.client.writeLock(async (ctx) => this.internalTransaction(ctx, transaction, config)) as T;
46+
}
47+
48+
protected async internalTransaction<T>(
49+
connection: DBAdapter,
50+
fn: (tx: PowerSyncSQLiteTransaction<TFullSchema, TSchema>) => T,
51+
config: PowerSyncSQLiteTransactionConfig = {}
52+
): Promise<T> {
53+
const tx = new PowerSyncSQLiteTransaction<TFullSchema, TSchema>(
54+
'async',
55+
(this as any).dialect,
56+
new PowerSyncSQLiteBaseSession(connection, this.dialect, this.schema, this.options),
57+
this.schema
58+
);
59+
60+
await connection.execute(`begin${config?.behavior ? ' ' + config.behavior : ''}`);
61+
try {
62+
const result = await fn(tx);
63+
await connection.execute(`commit`);
64+
return result;
65+
} catch (err) {
66+
await connection.execute(`rollback`);
67+
throw err;
68+
}
69+
}
70+
}

packages/drizzle-driver/src/utils/schema.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { CasingCache } from 'drizzle-orm/casing';
1212
import {
1313
getTableConfig,
1414
SQLiteBoolean,
15+
SQLiteCustomColumn,
1516
SQLiteInteger,
1617
SQLiteReal,
1718
SQLiteText,
@@ -44,24 +45,7 @@ export function toPowerSyncTable<T extends SQLiteTableWithColumns<any>>(
4445
continue;
4546
}
4647

47-
let mappedType: BaseColumnType<number | string | null>;
48-
switch (drizzleColumn.columnType) {
49-
case SQLiteText[entityKind]:
50-
case SQLiteTextJson[entityKind]:
51-
mappedType = column.text;
52-
break;
53-
case SQLiteInteger[entityKind]:
54-
case SQLiteTimestamp[entityKind]:
55-
case SQLiteBoolean[entityKind]:
56-
mappedType = column.integer;
57-
break;
58-
case SQLiteReal[entityKind]:
59-
mappedType = column.real;
60-
break;
61-
default:
62-
throw new Error(`Unsupported column type: ${drizzleColumn.columnType}`);
63-
}
64-
columns[name] = mappedType;
48+
columns[name] = mapDrizzleColumnToType(drizzleColumn);
6549
}
6650
const indexes: IndexShorthand = {};
6751

@@ -82,6 +66,34 @@ export function toPowerSyncTable<T extends SQLiteTableWithColumns<any>>(
8266
return new Table(columns, { ...options, indexes }) as Table<Expand<ExtractPowerSyncColumns<T>>>;
8367
}
8468

69+
function mapDrizzleColumnToType(drizzleColumn: SQLiteColumn<any, object>): BaseColumnType<number | string | null> {
70+
switch (drizzleColumn.columnType) {
71+
case SQLiteText[entityKind]:
72+
case SQLiteTextJson[entityKind]:
73+
return column.text;
74+
case SQLiteInteger[entityKind]:
75+
case SQLiteTimestamp[entityKind]:
76+
case SQLiteBoolean[entityKind]:
77+
return column.integer;
78+
case SQLiteReal[entityKind]:
79+
return column.real;
80+
case SQLiteCustomColumn[entityKind]:
81+
const sqlName = (drizzleColumn as SQLiteCustomColumn<any>).getSQLType();
82+
switch (sqlName) {
83+
case 'text':
84+
return column.text;
85+
case 'integer':
86+
return column.integer;
87+
case 'real':
88+
return column.real;
89+
default:
90+
throw new Error(`Unsupported custom column type: ${drizzleColumn.columnType}: ${sqlName}`);
91+
}
92+
default:
93+
throw new Error(`Unsupported column type: ${drizzleColumn.columnType}`);
94+
}
95+
}
96+
8597
export type DrizzleTablePowerSyncOptions = Omit<TableV2Options, 'indexes'>;
8698

8799
export type DrizzleTableWithPowerSyncOptions = {

packages/drizzle-driver/tests/setup/db.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Schema, PowerSyncDatabase, column, Table, AbstractPowerSyncDatabase } from '@powersync/web';
1+
import { AbstractPowerSyncDatabase, column, PowerSyncDatabase, Schema, Table } from '@powersync/web';
22
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
3-
import { wrapPowerSyncWithDrizzle, PowerSyncSQLiteDatabase } from '../../src/sqlite/db';
3+
import { wrapPowerSyncWithDrizzle } from '../../src/sqlite/PowerSyncSQLiteDatabase';
44

55
const users = new Table({
66
name: column.text

packages/drizzle-driver/tests/sqlite/db.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { AbstractPowerSyncDatabase } from '@powersync/common';
22
import { eq, sql } from 'drizzle-orm';
33
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
4-
import * as SUT from '../../src/sqlite/db';
4+
import * as SUT from '../../src/sqlite/PowerSyncSQLiteDatabase';
55
import { DrizzleSchema, drizzleUsers, getDrizzleDb, getPowerSyncDb } from '../setup/db';
66

77
describe('Database operations', () => {

0 commit comments

Comments
 (0)