Skip to content

Commit f16ac38

Browse files
wip: add sync compatibility
1 parent 03831dc commit f16ac38

File tree

3 files changed

+145
-59
lines changed

3 files changed

+145
-59
lines changed

packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Logger } from 'drizzle-orm/logger';
44
import { NoopLogger } from 'drizzle-orm/logger';
55
import type { RelationalSchemaConfig, TablesRelationalConfig } from 'drizzle-orm/relations';
66
import { type Query } from 'drizzle-orm/sql/sql';
7-
import type { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core/dialect';
7+
import type { SQLiteAsyncDialect, SQLiteSyncDialect } from 'drizzle-orm/sqlite-core/dialect';
88
import type { SelectedFieldsOrdered } from 'drizzle-orm/sqlite-core/query-builders/select.types';
99
import {
1010
type PreparedQueryConfig as PreparedQueryConfigBase,
@@ -26,35 +26,35 @@ export type PowerSyncSQLiteTransactionConfig = SQLiteTransactionConfig & {
2626
export class PowerSyncSQLiteTransaction<
2727
TFullSchema extends Record<string, unknown>,
2828
TSchema extends TablesRelationalConfig
29-
> extends SQLiteTransaction<'async', QueryResult, TFullSchema, TSchema> {
29+
> extends SQLiteTransaction<'async' | 'sync', QueryResult, TFullSchema, TSchema> {
3030
static readonly [entityKind]: string = 'PowerSyncSQLiteTransaction';
3131
}
3232

3333
export class PowerSyncSQLiteBaseSession<
3434
TFullSchema extends Record<string, unknown>,
3535
TSchema extends TablesRelationalConfig
36-
> extends SQLiteSession<'async', QueryResult, TFullSchema, TSchema> {
36+
> extends SQLiteSession<'async' | 'sync', QueryResult, TFullSchema, TSchema> {
3737
static readonly [entityKind]: string = 'PowerSyncSQLiteBaseSession';
3838

3939
protected logger: Logger;
4040

4141
constructor(
4242
protected db: LockContext,
43-
protected dialect: SQLiteAsyncDialect,
43+
protected dialect: { sync: SQLiteSyncDialect; async: SQLiteAsyncDialect }['async' | 'sync'],
4444
protected schema: RelationalSchemaConfig<TSchema> | undefined,
4545
protected options: PowerSyncSQLiteSessionOptions = {}
4646
) {
4747
super(dialect);
4848
this.logger = options.logger ?? new NoopLogger();
4949
}
5050

51-
prepareQuery<T extends PreparedQueryConfigBase & { type: 'async' }>(
51+
prepareQuery<T extends PreparedQueryConfigBase>(
5252
query: Query,
5353
fields: SelectedFieldsOrdered | undefined,
5454
executeMethod: SQLiteExecuteMethod,
5555
isResponseInArrayMode: boolean,
5656
customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown
57-
): PowerSyncSQLitePreparedQuery<T> {
57+
): PowerSyncSQLitePreparedQuery<T, 'async' | 'sync'> {
5858
return new PowerSyncSQLitePreparedQuery(
5959
this.db,
6060
query,

packages/drizzle-driver/src/sqlite/PowerSyncSQLiteDatabase.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ export type DrizzleQuery<T> = { toSQL(): Query; execute(): Promise<T | T[]> };
2626

2727
export class PowerSyncSQLiteDatabase<
2828
TSchema extends Record<string, unknown> = Record<string, never>
29-
> extends BaseSQLiteDatabase<'async', QueryResult, TSchema> {
29+
> extends BaseSQLiteDatabase<'async' | 'sync', QueryResult, TSchema> {
3030
private db: AbstractPowerSyncDatabase;
3131

32-
constructor(db: AbstractPowerSyncDatabase, config: DrizzleConfig<TSchema> = {}) {
32+
constructor(db: AbstractPowerSyncDatabase, config: DrizzleConfig<TSchema> = {}, mode: 'async' | 'sync' = 'async') {
3333
const dialect = new SQLiteAsyncDialect({ casing: config.casing });
3434
let logger;
3535
if (config.logger === true) {
@@ -52,16 +52,16 @@ export class PowerSyncSQLiteDatabase<
5252
logger
5353
});
5454

55-
super('async', dialect, session as any, schema as any);
55+
super(mode, dialect, session as any, schema as any);
5656
this.db = db;
5757
}
5858

5959
transaction<T>(
6060
transaction: (
61-
tx: SQLiteTransaction<'async', QueryResult, TSchema, ExtractTablesWithRelations<TSchema>>
61+
tx: SQLiteTransaction<'async' | 'sync', QueryResult, TSchema, ExtractTablesWithRelations<TSchema>>
6262
) => Promise<T>,
6363
config?: PowerSyncSQLiteTransactionConfig
64-
): Promise<T> {
64+
) {
6565
return super.transaction(transaction, config);
6666
}
6767

packages/drizzle-driver/src/sqlite/PowerSyncSQLitePreparedQuery.ts

Lines changed: 134 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { LockContext, QueryResult } from '@powersync/common';
2-
import { Column, DriverValueDecoder, getTableName, SQL } from 'drizzle-orm';
2+
import { Column, DriverValueDecoder, getTableName, NoopLogger, SQL } from 'drizzle-orm';
33
import { entityKind, is } from 'drizzle-orm/entity';
44
import type { Logger } from 'drizzle-orm/logger';
55
import { fillPlaceholders, type Query } from 'drizzle-orm/sql/sql';
@@ -8,24 +8,31 @@ import type { SelectedFieldsOrdered } from 'drizzle-orm/sqlite-core/query-builde
88
import {
99
type PreparedQueryConfig as PreparedQueryConfigBase,
1010
type SQLiteExecuteMethod,
11-
SQLitePreparedQuery
11+
SQLitePreparedQuery,
12+
type Result,
13+
ExecuteResultSync
1214
} from 'drizzle-orm/sqlite-core/session';
1315

14-
type PreparedQueryConfig = Omit<PreparedQueryConfigBase, 'statement' | 'run'>;
16+
type PreparedQueryConfig = Omit<PreparedQueryConfigBase, 'statement'>;
1517

16-
export class PowerSyncSQLitePreparedQuery<
17-
T extends PreparedQueryConfig = PreparedQueryConfig
18-
> extends SQLitePreparedQuery<{
19-
type: 'async';
18+
// Define the result types for both sync and async modes
19+
type PowerSyncPreparedQueryConfig<TSync extends 'sync' | 'async', T> = {
20+
type: TSync;
2021
run: QueryResult;
21-
all: T['all'];
22-
get: T['get'];
23-
values: T['values'];
24-
execute: T['execute'];
25-
}> {
22+
all: QueryResult[];
23+
get: T;
24+
values: T[][];
25+
execute: T;
26+
};
27+
28+
export class PowerSyncSQLitePreparedQuery<
29+
T extends PreparedQueryConfig = PreparedQueryConfig,
30+
TSync extends 'async' | 'sync' = 'async'
31+
> extends SQLitePreparedQuery<PowerSyncPreparedQueryConfig<TSync, T> & T> {
2632
static readonly [entityKind]: string = 'PowerSyncSQLitePreparedQuery';
2733

2834
constructor(
35+
private mode: TSync,
2936
private db: LockContext,
3037
query: Query,
3138
private logger: Logger,
@@ -34,62 +41,108 @@ export class PowerSyncSQLitePreparedQuery<
3441
private _isResponseInArrayMode: boolean,
3542
private customResultMapper?: (rows: unknown[][]) => unknown
3643
) {
37-
super('async', executeMethod, query);
44+
super(mode, executeMethod, query);
3845
}
3946

40-
async run(placeholderValues?: Record<string, unknown>): Promise<QueryResult> {
41-
const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
42-
this.logger.logQuery(this.query.sql, params);
43-
const rs = await this.db.execute(this.query.sql, params);
44-
return rs;
45-
}
47+
run(placeholderValues?: Record<string, unknown>): Result<TSync, QueryResult & T['run']> {
48+
const runFn = async (): Promise<QueryResult> => {
49+
const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
50+
this.logger.logQuery(this.query.sql, params);
51+
return await this.db.execute(this.query.sql, params);
52+
};
4653

47-
async all(placeholderValues?: Record<string, unknown>): Promise<T['all']> {
48-
const { fields, query, logger, customResultMapper } = this;
49-
if (!fields && !customResultMapper) {
50-
const params = fillPlaceholders(query.params, placeholderValues ?? {});
51-
logger.logQuery(query.sql, params);
52-
const rs = await this.db.execute(this.query.sql, params);
53-
return rs.rows?._array ?? [];
54+
if (this.mode === 'sync') {
55+
return new ExecuteResultSync(() => {
56+
// For sync mode, you'd need a synchronous version of your db operations
57+
// This is just a placeholder - you'd need to implement sync versions
58+
throw new Error('Sync mode not implemented for PowerSync');
59+
}) as unknown as Result<TSync, QueryResult>;
5460
}
5561

56-
const rows = (await this.values(placeholderValues)) as unknown[][];
62+
return runFn() as Result<TSync, QueryResult>;
63+
}
64+
65+
all(placeholderValues?: Record<string, unknown>): Result<TSync, QueryResult[] & T['all']> {
66+
const allFn = async (): Promise<T['all']> => {
67+
const { fields, query, logger, customResultMapper } = this;
68+
69+
if (!fields && !customResultMapper) {
70+
const params = fillPlaceholders(query.params, placeholderValues ?? {});
71+
logger.logQuery(query.sql, params);
72+
const rs = await this.db.execute(this.query.sql, params);
73+
return rs.rows?._array ?? [];
74+
}
75+
76+
const rows = (await this.valuesAsync(placeholderValues)) as unknown[][];
5777

58-
if (customResultMapper) {
59-
const mapped = customResultMapper(rows) as T['all'];
60-
return mapped;
78+
if (customResultMapper) {
79+
return customResultMapper(rows) as T['all'];
80+
}
81+
82+
return rows.map((row) => mapResultRow(fields!, row, (this as any).joinsNotNullableMap));
83+
};
84+
85+
if (this.mode === 'sync') {
86+
return new ExecuteResultSync(() => {
87+
throw new Error('Sync mode not implemented for PowerSync');
88+
}) as unknown as Result<TSync, QueryResult[] & T['all']>;
6189
}
62-
return rows.map((row) => mapResultRow(fields!, row, (this as any).joinsNotNullableMap));
90+
91+
return allFn() as Result<TSync, QueryResult[] & T['all']>;
6392
}
6493

65-
async get(placeholderValues?: Record<string, unknown>): Promise<T['get']> {
66-
const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
67-
this.logger.logQuery(this.query.sql, params);
94+
get(placeholderValues?: Record<string, unknown>): Result<TSync, T & T['get']> {
95+
const getFn = async (): Promise<T['get']> => {
96+
const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
97+
this.logger.logQuery(this.query.sql, params);
6898

69-
const { fields, customResultMapper } = this;
70-
const joinsNotNullableMap = (this as any).joinsNotNullableMap;
71-
if (!fields && !customResultMapper) {
72-
return this.db.get(this.query.sql, params);
73-
}
99+
const { fields, customResultMapper } = this;
100+
const joinsNotNullableMap = (this as any).joinsNotNullableMap;
101+
102+
if (!fields && !customResultMapper) {
103+
return this.db.get(this.query.sql, params);
104+
}
105+
106+
const rows = (await this.valuesAsync(placeholderValues)) as unknown[][];
107+
const row = rows[0];
108+
109+
if (!row) {
110+
return undefined;
111+
}
112+
113+
if (customResultMapper) {
114+
return customResultMapper(rows) as T['get'];
115+
}
74116

75-
const rows = (await this.values(placeholderValues)) as unknown[][];
76-
const row = rows[0];
117+
return mapResultRow(fields!, row, joinsNotNullableMap);
118+
};
77119

78-
if (!row) {
79-
return undefined;
120+
if (this.mode === 'sync') {
121+
return new ExecuteResultSync(() => {
122+
throw new Error('Sync mode not implemented for PowerSync');
123+
}) as unknown as Result<TSync, T & T['get']>;
80124
}
81125

82-
if (customResultMapper) {
83-
return customResultMapper(rows) as T['get'];
126+
return getFn() as Result<TSync, T & T['get']>;
127+
}
128+
129+
values(placeholderValues?: Record<string, unknown>): Result<TSync, T[][] & T['values']> {
130+
const valuesFn = async (): Promise<T['values']> => {
131+
return this.valuesAsync(placeholderValues);
132+
};
133+
134+
if (this.mode === 'sync') {
135+
return new ExecuteResultSync(() => {
136+
throw new Error('Sync mode not implemented for PowerSync');
137+
}) as unknown as Result<TSync, T[][] &T['values']>;
84138
}
85139

86-
return mapResultRow(fields!, row, joinsNotNullableMap);
140+
return valuesFn() as Result<TSync, T[][] &T['values']>;
87141
}
88142

89-
async values(placeholderValues?: Record<string, unknown>): Promise<T['values']> {
143+
private async valuesAsync(placeholderValues?: Record<string, unknown>): Promise<T['values']> {
90144
const params = fillPlaceholders(this.query.params, placeholderValues ?? {});
91145
this.logger.logQuery(this.query.sql, params);
92-
93146
return await this.db.executeRaw(this.query.sql, params);
94147
}
95148

@@ -98,6 +151,39 @@ export class PowerSyncSQLitePreparedQuery<
98151
}
99152
}
100153

154+
// If you want to create specific sync/async versions for easier usage:
155+
export class PowerSyncSQLiteAsyncPreparedQuery<
156+
T extends PreparedQueryConfig = PreparedQueryConfig
157+
> extends PowerSyncSQLitePreparedQuery<T, 'async'> {
158+
constructor(
159+
db: LockContext,
160+
query: Query,
161+
logger: Logger,
162+
fields: SelectedFieldsOrdered | undefined,
163+
executeMethod: SQLiteExecuteMethod,
164+
isResponseInArrayMode: boolean,
165+
customResultMapper?: (rows: unknown[][]) => unknown
166+
) {
167+
super('async', db, query, logger, fields, executeMethod, isResponseInArrayMode, customResultMapper);
168+
}
169+
}
170+
171+
export class PowerSyncSQLiteSyncPreparedQuery<
172+
T extends PreparedQueryConfig = PreparedQueryConfig
173+
> extends PowerSyncSQLitePreparedQuery<T, 'sync'> {
174+
constructor(
175+
db: LockContext, // You'd need a sync version of LockContext
176+
query: Query,
177+
logger: Logger,
178+
fields: SelectedFieldsOrdered | undefined,
179+
executeMethod: SQLiteExecuteMethod,
180+
isResponseInArrayMode: boolean,
181+
customResultMapper?: (rows: unknown[][]) => unknown
182+
) {
183+
super('sync', db, query, logger, fields, executeMethod, isResponseInArrayMode, customResultMapper);
184+
}
185+
}
186+
101187
/**
102188
* Maps a flat array of database row values to a result object based on the provided column definitions.
103189
* It reconstructs the hierarchical structure of the result by following the specified paths for each field.

0 commit comments

Comments
 (0)