Skip to content

Commit 3f7e3cd

Browse files
committed
drizzle-driver: add smaller Drizzle beta support
1 parent 061868b commit 3f7e3cd

File tree

12 files changed

+436
-76
lines changed

12 files changed

+436
-76
lines changed

packages/drizzle-driver/README.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,28 @@ This package (`@powersync/drizzle-driver`) brings the benefits of an ORM through
66

77
The `drizzle-driver` package is currently in an Beta release.
88

9+
## Drizzle Version Support
10+
11+
- `drizzle-orm@0.44.x`
12+
- Pass `schema`.
13+
- `db.query` keeps working as before.
14+
- `db._query` is available as an alias to the same legacy relational query builder.
15+
- `drizzle-orm@1.0.0-beta`
16+
- Pass `relations`.
17+
- Use `db.query` for relational queries.
18+
- If you only pass `schema`, the driver exposes a basic `db.query.<table>.findMany()` surface for simple table queries, but relational `with` queries should use `relations`.
19+
20+
## Drizzle Version Support
21+
22+
- `drizzle-orm@0.44.x`
23+
- Use `schema`.
24+
- `db.query` continues to use the legacy relational query builder.
25+
- `db._query` is available as a compatibility alias to the same legacy query builder.
26+
- `drizzle-orm@1.0.0-beta`
27+
- Use `relations` for the new relational query builder and query via `db.query`.
28+
- Keep using `schema` if you want the legacy relational query builder on `db._query`.
29+
- If you only pass `schema`, the driver synthesizes a basic `db.query.<table>.findMany()` surface for simple table queries.
30+
931
## Getting Started
1032

1133
Set up the PowerSync Database and wrap it with Drizzle.
@@ -67,6 +89,112 @@ export const db = wrapPowerSyncWithDrizzle(powerSyncDb, {
6789
});
6890
```
6991

92+
### Drizzle v1 beta
93+
94+
For Drizzle beta relational queries, pass `relations` and use `db.query`.
95+
96+
```js
97+
import { defineRelations } from 'drizzle-orm';
98+
import { wrapPowerSyncWithDrizzle } from '@powersync/drizzle-driver';
99+
100+
export const relations = defineRelations({ lists, todos }, (r) => ({
101+
lists: {
102+
todos: r.many.todos({
103+
from: r.lists.id,
104+
to: r.todos.list_id
105+
})
106+
},
107+
todos: {
108+
list: r.one.lists({
109+
from: r.todos.list_id,
110+
to: r.lists.id,
111+
optional: false
112+
})
113+
}
114+
}));
115+
116+
export const db = wrapPowerSyncWithDrizzle(powerSyncDb, {
117+
relations
118+
});
119+
120+
const listsWithTodos = await db.query.lists.findMany({
121+
with: {
122+
todos: true
123+
}
124+
});
125+
```
126+
127+
### Drizzle v1 beta
128+
129+
For Drizzle beta relational queries, pass `relations` and use `db.query`.
130+
131+
```js
132+
import { defineRelations } from 'drizzle-orm';
133+
import { wrapPowerSyncWithDrizzle } from '@powersync/drizzle-driver';
134+
135+
export const relations = defineRelations({ lists, todos }, (r) => ({
136+
lists: {
137+
todos: r.many.todos({
138+
from: r.lists.id,
139+
to: r.todos.list_id
140+
})
141+
},
142+
todos: {
143+
list: r.one.lists({
144+
from: r.todos.list_id,
145+
to: r.lists.id,
146+
optional: false
147+
})
148+
}
149+
}));
150+
151+
export const db = wrapPowerSyncWithDrizzle(powerSyncDb, {
152+
relations
153+
});
154+
155+
const listsWithTodos = await db.query.lists.findMany({
156+
with: {
157+
todos: true
158+
}
159+
});
160+
```
161+
162+
### Partial upgrade on Drizzle beta
163+
164+
If you are upgrading incrementally, keep legacy relation definitions in `drizzle-orm/_relations`, pass them via `schema`, and use `db._query`.
165+
166+
```js
167+
import { relations } from 'drizzle-orm/_relations';
168+
169+
const listsRelations = relations(lists, ({ many }) => ({
170+
todos: many(todos)
171+
}));
172+
173+
const todosRelations = relations(todos, ({ one }) => ({
174+
list: one(lists, {
175+
fields: [todos.list_id],
176+
references: [lists.id]
177+
})
178+
}));
179+
180+
const legacySchema = {
181+
lists,
182+
todos,
183+
listsRelations,
184+
todosRelations
185+
};
186+
187+
export const db = wrapPowerSyncWithDrizzle(powerSyncDb, {
188+
schema: legacySchema
189+
});
190+
191+
const listsWithTodos = await db._query.lists.findMany({
192+
with: {
193+
todos: true
194+
}
195+
});
196+
```
197+
70198
## Schema Conversion
71199

72200
The `DrizzleAppSchema` constructor simplifies the process of integrating Drizzle with PowerSync. It infers the local [PowerSync schema](https://docs.powersync.com/installation/client-side-setup/define-your-schema) from your Drizzle schema definition, providing a unified development experience.

packages/drizzle-driver/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
},
4848
"peerDependencies": {
4949
"@powersync/common": "workspace:^1.50.0",
50-
"drizzle-orm": "<1.0.0"
50+
"drizzle-orm": ">=0.44.7 <1.0.0 || >=1.0.0-beta.1 <1.0.0"
5151
},
5252
"devDependencies": {
5353
"@journeyapps/wa-sqlite": "catalog:",

packages/drizzle-driver/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
wrapPowerSyncWithDrizzle,
33
type DrizzleQuery,
4+
type PowerSyncDrizzleConfig,
45
type PowerSyncSQLiteDatabase
56
} from './sqlite/PowerSyncSQLiteDatabase.js';
67
import { toCompilableQuery } from './utils/compilableQuery.js';
@@ -24,6 +25,7 @@ export {
2425
DrizzleTableWithPowerSyncOptions,
2526
Expand,
2627
ExtractPowerSyncColumns,
28+
PowerSyncDrizzleConfig,
2729
PowerSyncSQLiteDatabase,
2830
TableName,
2931
TablesFromSchemaEntries,

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import {
1616
} from 'drizzle-orm/sqlite-core/session';
1717
import { PowerSyncSQLitePreparedQuery, type ContextProvider } from './PowerSyncSQLitePreparedQuery.js';
1818

19+
const SQLiteSessionBase = SQLiteSession as unknown as abstract new (...args: any[]) => any;
20+
const SQLiteTransactionBase = SQLiteTransaction as unknown as abstract new (...args: any[]) => any;
21+
1922
export interface PowerSyncSQLiteSessionOptions {
2023
logger?: Logger;
2124
}
@@ -27,14 +30,14 @@ export type PowerSyncSQLiteTransactionConfig = SQLiteTransactionConfig & {
2730
export class PowerSyncSQLiteTransaction<
2831
TFullSchema extends Record<string, unknown>,
2932
TSchema extends TablesRelationalConfig
30-
> extends SQLiteTransaction<'async', QueryResult, TFullSchema, TSchema> {
33+
> extends SQLiteTransactionBase {
3134
static readonly [entityKind]: string = 'PowerSyncSQLiteTransaction';
3235
}
3336

3437
export class PowerSyncSQLiteBaseSession<
3538
TFullSchema extends Record<string, unknown>,
3639
TSchema extends TablesRelationalConfig
37-
> extends SQLiteSession<'async', QueryResult, TFullSchema, TSchema> {
40+
> extends SQLiteSessionBase {
3841
static readonly [entityKind]: string = 'PowerSyncSQLiteBaseSession';
3942

4043
protected logger: Logger;
@@ -43,7 +46,8 @@ export class PowerSyncSQLiteBaseSession<
4346
protected contextProvider: ContextProvider,
4447
protected dialect: SQLiteAsyncDialect,
4548
protected schema: RelationalSchemaConfig<TSchema> | undefined,
46-
protected options: PowerSyncSQLiteSessionOptions = {}
49+
protected options: PowerSyncSQLiteSessionOptions = {},
50+
protected relations: Record<string, unknown> = {}
4751
) {
4852
super(dialect);
4953
this.logger = options.logger ?? new NoopLogger();
@@ -75,6 +79,27 @@ export class PowerSyncSQLiteBaseSession<
7579
);
7680
}
7781

82+
prepareRelationalQuery<T extends PreparedQueryConfigBase & { type: 'async' }>(
83+
query: Query,
84+
fields: SelectedFieldsOrdered | undefined,
85+
executeMethod: SQLiteExecuteMethod,
86+
customResultMapper: (rows: Record<string, unknown>[], mapColumnValue?: (value: unknown) => unknown) => unknown
87+
): PowerSyncSQLitePreparedQuery<T> {
88+
return new PowerSyncSQLitePreparedQuery(
89+
this.contextProvider,
90+
query,
91+
this.logger,
92+
fields,
93+
executeMethod,
94+
false,
95+
customResultMapper,
96+
undefined,
97+
{ type: 'select', tables: [] },
98+
undefined,
99+
true
100+
);
101+
}
102+
78103
transaction<T>(
79104
_transaction: (tx: PowerSyncSQLiteTransaction<TFullSchema, TSchema>) => T,
80105
_config: PowerSyncSQLiteTransactionConfig = {}

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

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,53 @@ import {
88
import { Query } from 'drizzle-orm';
99
import { DefaultLogger } from 'drizzle-orm/logger';
1010
import {
11-
createTableRelationsHelpers,
12-
extractTablesRelationalConfig,
1311
ExtractTablesWithRelations,
1412
TableRelationalConfig,
1513
type RelationalSchemaConfig,
1614
type TablesRelationalConfig
1715
} from 'drizzle-orm/relations';
16+
import * as DrizzleRelations from 'drizzle-orm/relations';
1817
import { SQLiteSession, SQLiteTable, SQLiteTransaction } from 'drizzle-orm/sqlite-core';
1918
import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core/db';
2019
import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core/dialect';
2120
import { RelationalQueryBuilder } from 'drizzle-orm/sqlite-core/query-builders/query';
2221
import type { DrizzleConfig } from 'drizzle-orm/utils';
22+
import { extractFallbackRelations, isDrizzleBetaRuntime } from '../utils/drizzleCompat.js';
2323
import { toCompilableQuery } from './../utils/compilableQuery.js';
2424
import { PowerSyncSQLiteBaseSession, PowerSyncSQLiteTransactionConfig } from './PowerSyncSQLiteBaseSession.js';
2525
import { PowerSyncSQLiteSession } from './PowerSyncSQLiteSession.js';
2626

27+
const BaseSQLiteDatabaseBase = BaseSQLiteDatabase as unknown as abstract new (...args: any[]) => any;
28+
2729
export type DrizzleQuery<T> = { toSQL(): Query; execute(): Promise<T | T[]> };
2830

31+
export type PowerSyncDrizzleConfig<
32+
TSchema extends Record<string, unknown> = Record<string, never>,
33+
TRelations extends Record<string, unknown> = Record<string, never>
34+
> = DrizzleConfig<TSchema> & {
35+
relations?: TRelations;
36+
};
37+
2938
export class PowerSyncSQLiteDatabase<
30-
TSchema extends Record<string, unknown> = Record<string, never>
31-
> extends BaseSQLiteDatabase<'async', QueryResult, TSchema> {
39+
TSchema extends Record<string, unknown> = Record<string, never>,
40+
TRelations extends Record<string, unknown> = Record<string, never>
41+
> extends BaseSQLiteDatabaseBase {
42+
declare _:
43+
| {
44+
schema: TablesRelationalConfig | undefined;
45+
fullSchema: TSchema;
46+
tableNamesMap: Record<string, string>;
47+
}
48+
| undefined;
49+
50+
declare query: Record<string, unknown>;
51+
declare _query?: Record<string, unknown>;
52+
3253
private db: AbstractPowerSyncDatabase;
3354

34-
constructor(db: AbstractPowerSyncDatabase, config: DrizzleConfig<TSchema> = {}) {
55+
constructor(db: AbstractPowerSyncDatabase, config: PowerSyncDrizzleConfig<TSchema, TRelations> = {}) {
3556
const dialect = new SQLiteAsyncDialect({ casing: config.casing });
57+
const drizzleBetaRuntime = isDrizzleBetaRuntime(dialect);
3658
let logger;
3759
if (config.logger === true) {
3860
logger = new DefaultLogger();
@@ -41,40 +63,47 @@ export class PowerSyncSQLiteDatabase<
4163
}
4264

4365
let schema: RelationalSchemaConfig<TablesRelationalConfig> | undefined;
44-
if (config.schema) {
45-
const tablesConfig = extractTablesRelationalConfig(config.schema, createTableRelationsHelpers);
66+
if (config.schema && !drizzleBetaRuntime) {
67+
const tablesConfig = (DrizzleRelations as any).extractTablesRelationalConfig(
68+
config.schema,
69+
(DrizzleRelations as any).createTableRelationsHelpers
70+
);
4671
schema = {
4772
fullSchema: config.schema,
4873
schema: tablesConfig.tables,
4974
tableNamesMap: tablesConfig.tableNamesMap
5075
};
5176
}
5277

53-
const session = new PowerSyncSQLiteSession(db, dialect, schema, {
54-
logger
55-
});
78+
const relations = (config.relations ??
79+
(drizzleBetaRuntime ? extractFallbackRelations(config.schema) : {})) as Record<string, unknown>;
5680

57-
super('async', dialect, session as any, schema as any);
81+
const session = new PowerSyncSQLiteSession(db, dialect, schema, { logger }, relations);
82+
83+
if (drizzleBetaRuntime) {
84+
super('async', dialect, session as any, relations as any, undefined, undefined, true);
85+
} else {
86+
super('async', dialect, session as any, schema as any);
87+
}
5888
this.db = db;
5989

60-
/**
61-
* A hack in order to use read locks for `db.query.users.findMany()` etc queries.
62-
* We don't currently get queryMetadata for these queries, so we can't use the regular session.
63-
* This session always uses read locks.
64-
*/
65-
const querySession = new PowerSyncSQLiteBaseSession(
66-
{
67-
useReadContext: (callback) => db.readLock(callback),
68-
useWriteContext: (callback) => db.readLock(callback)
69-
},
70-
dialect,
71-
schema,
72-
{
73-
logger
74-
}
75-
);
76-
if (this._.schema) {
77-
// https://github.com/drizzle-team/drizzle-orm/blob/ad4ddd444d066b339ffd5765cb6ec3bf49380189/drizzle-orm/src/sqlite-core/db.ts#L72
90+
if (!drizzleBetaRuntime && this._?.schema) {
91+
/**
92+
* A hack in order to use read locks for `db.query.users.findMany()` etc queries.
93+
* We don't currently get queryMetadata for these queries, so we can't use the regular session.
94+
* This session always uses read locks.
95+
*/
96+
const querySession = new PowerSyncSQLiteBaseSession(
97+
{
98+
useReadContext: (callback) => db.readLock(callback),
99+
useWriteContext: (callback) => db.readLock(callback)
100+
},
101+
dialect,
102+
schema,
103+
{
104+
logger
105+
}
106+
);
78107
const query = this.query as {
79108
[K in keyof TSchema]: RelationalQueryBuilder<'async', any, any, any>;
80109
};
@@ -87,9 +116,11 @@ export class PowerSyncSQLiteDatabase<
87116
schema!.fullSchema[tableName] as SQLiteTable,
88117
columns as TableRelationalConfig,
89118
dialect,
90-
querySession as SQLiteSession<'async', any, any, any>
119+
querySession as unknown as SQLiteSession<'async', any, any, any>
91120
);
92121
}
122+
123+
this._query = this.query;
93124
}
94125
}
95126

@@ -107,9 +138,12 @@ export class PowerSyncSQLiteDatabase<
107138
}
108139
}
109140

110-
export function wrapPowerSyncWithDrizzle<TSchema extends Record<string, unknown> = Record<string, never>>(
141+
export function wrapPowerSyncWithDrizzle<
142+
TSchema extends Record<string, unknown> = Record<string, never>,
143+
TRelations extends Record<string, unknown> = Record<string, never>
144+
>(
111145
db: AbstractPowerSyncDatabase,
112-
config: DrizzleConfig<TSchema> = {}
113-
): PowerSyncSQLiteDatabase<TSchema> {
114-
return new PowerSyncSQLiteDatabase<TSchema>(db, config);
146+
config: PowerSyncDrizzleConfig<TSchema, TRelations> = {}
147+
): PowerSyncSQLiteDatabase<TSchema, TRelations> {
148+
return new PowerSyncSQLiteDatabase<TSchema, TRelations>(db, config);
115149
}

0 commit comments

Comments
 (0)