Skip to content

Commit ca5c9f9

Browse files
committed
Rewrite drizzle driver for v1 beta relations
1 parent 3f7e3cd commit ca5c9f9

File tree

22 files changed

+789
-488
lines changed

22 files changed

+789
-488
lines changed

.changeset/calm-rivers-appear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/drizzle-driver': minor
3+
---
4+
5+
Rewrite the Drizzle driver against the Drizzle v1 beta `relations` + `db.query` API and drop `0.x` compatibility from this release line.

demos/react-neon-tanstack-query-notes/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"class-variance-authority": "^0.7.1",
2929
"clsx": "^2.1.1",
3030
"drizzle-kit": "^0.31.7",
31-
"drizzle-orm": "^0.44.7",
31+
"drizzle-orm": "1.0.0-beta.19",
3232
"lucide-react": "^0.503.0",
3333
"moment": "^2.30.1",
3434
"react": "^19.2.0",

demos/react-neon-tanstack-query-notes/src/lib/powersync-schema.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { defineRelations } from 'drizzle-orm';
12
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
2-
import { relations } from 'drizzle-orm';
33

44
export const notes = sqliteTable('notes', {
55
id: text().primaryKey(),
@@ -17,10 +17,9 @@ export const paragraphs = sqliteTable('paragraphs', {
1717
created_at: text().notNull(),
1818
});
1919

20-
export const notesRelations = relations(notes, ({ many }) => ({
21-
paragraphs: many(paragraphs),
22-
}));
23-
2420
export const drizzleSchema = {
25-
notes, paragraphs, notesRelations
26-
};
21+
notes,
22+
paragraphs
23+
};
24+
25+
export const drizzleRelations = defineRelations(drizzleSchema);

demos/react-neon-tanstack-query-notes/src/lib/powersync.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
DrizzleAppSchema,
1616
} from "@powersync/drizzle-driver";
1717

18-
import { drizzleSchema } from "./powersync-schema";
18+
import { drizzleRelations, drizzleSchema } from "./powersync-schema";
1919

2020
/// Postgres Response codes that we cannot recover from by retrying.
2121
const FATAL_RESPONSE_CODES = [
@@ -205,7 +205,9 @@ export const powersync = new PowerSyncDatabase({
205205
},
206206
});
207207

208-
export const powersyncDrizzle = wrapPowerSyncWithDrizzle(powersync);
208+
export const powersyncDrizzle = wrapPowerSyncWithDrizzle(powersync, {
209+
relations: drizzleRelations,
210+
});
209211

210212
let isInitialized = false;
211213

packages/drizzle-driver/README.md

Lines changed: 16 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,7 @@ This package (`@powersync/drizzle-driver`) brings the benefits of an ORM through
44

55
## Beta Release
66

7-
The `drizzle-driver` package is currently in an Beta release.
8-
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.
7+
This release line targets `drizzle-orm@1.0.0-beta` and the beta `relations` + `db.query` API.
308

319
## Getting Started
3210

@@ -35,7 +13,7 @@ Set up the PowerSync Database and wrap it with Drizzle.
3513
```js
3614
import { wrapPowerSyncWithDrizzle } from '@powersync/drizzle-driver';
3715
import { PowerSyncDatabase } from '@powersync/web';
38-
import { relations } from 'drizzle-orm';
16+
import { defineRelations } from 'drizzle-orm';
3917
import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
4018
import { AppSchema } from './schema';
4119

@@ -51,52 +29,6 @@ export const todos = sqliteTable('todos', {
5129
created_at: text('created_at')
5230
});
5331

54-
export const listsRelations = relations(lists, ({ one, many }) => ({
55-
todos: many(todos)
56-
}));
57-
58-
export const todosRelations = relations(todos, ({ one, many }) => ({
59-
list: one(lists, {
60-
fields: [todos.list_id],
61-
references: [lists.id]
62-
})
63-
}));
64-
65-
export const drizzleSchema = {
66-
lists,
67-
todos,
68-
listsRelations,
69-
todosRelations
70-
};
71-
72-
// As an alternative to manually defining a PowerSync schema, generate the local PowerSync schema from the Drizzle schema with the `DrizzleAppSchema` constructor:
73-
// import { DrizzleAppSchema } from '@powersync/drizzle-driver';
74-
// export const AppSchema = new DrizzleAppSchema(drizzleSchema);
75-
//
76-
// This is optional, but recommended, since you will only need to maintain one schema on the client-side
77-
// Read on to learn more.
78-
79-
export const powerSyncDb = new PowerSyncDatabase({
80-
database: {
81-
dbFilename: 'test.sqlite'
82-
},
83-
schema: AppSchema
84-
});
85-
86-
// This is the DB you will use in queries
87-
export const db = wrapPowerSyncWithDrizzle(powerSyncDb, {
88-
schema: drizzleSchema
89-
});
90-
```
91-
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-
10032
export const relations = defineRelations({ lists, todos }, (r) => ({
10133
lists: {
10234
todos: r.many.todos({
@@ -113,82 +45,30 @@ export const relations = defineRelations({ lists, todos }, (r) => ({
11345
}
11446
}));
11547

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';
48+
// As an alternative to manually defining a PowerSync schema, generate the local PowerSync schema from the Drizzle schema with the `DrizzleAppSchema` constructor:
49+
// import { DrizzleAppSchema } from '@powersync/drizzle-driver';
50+
// export const AppSchema = new DrizzleAppSchema({ lists, todos });
51+
//
52+
// This is optional, but recommended, since you will only need to maintain one schema on the client-side
53+
// Read on to learn more.
13454

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-
})
55+
export const powerSyncDb = new PowerSyncDatabase({
56+
database: {
57+
dbFilename: 'test.sqlite'
14158
},
142-
todos: {
143-
list: r.one.lists({
144-
from: r.todos.list_id,
145-
to: r.lists.id,
146-
optional: false
147-
})
148-
}
149-
}));
59+
schema: AppSchema
60+
});
15061

62+
// This is the DB you will use in queries
15163
export const db = wrapPowerSyncWithDrizzle(powerSyncDb, {
15264
relations
15365
});
154-
155-
const listsWithTodos = await db.query.lists.findMany({
156-
with: {
157-
todos: true
158-
}
159-
});
16066
```
16167

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`.
68+
To make relational queries, use `db.query`:
16569

16670
```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({
71+
const listsWithTodos = await db.query.lists.findMany({
19272
with: {
19373
todos: true
19474
}

packages/drizzle-driver/package.json

Lines changed: 2 additions & 2 deletions
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": ">=0.44.7 <1.0.0 || >=1.0.0-beta.1 <1.0.0"
50+
"drizzle-orm": ">=1.0.0-beta.19 <1.0.0"
5151
},
5252
"devDependencies": {
5353
"@journeyapps/wa-sqlite": "catalog:",
@@ -56,7 +56,7 @@
5656
"@rollup/plugin-node-resolve": "catalog:",
5757
"@rollup/plugin-typescript": "catalog:",
5858
"@types/node": "catalog:",
59-
"drizzle-orm": "catalog:",
59+
"drizzle-orm": "1.0.0-beta.19",
6060
"rollup": "catalog:",
6161
"rollup-plugin-dts": "catalog:",
6262
"vite": "catalog:"

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

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import type { QueryResult } from '@powersync/common';
1+
import type { AbstractPowerSyncDatabase, QueryResult } from '@powersync/common';
22
import type { WithCacheConfig } from 'drizzle-orm/cache/core/types';
33
import { entityKind } from 'drizzle-orm/entity';
44
import type { Logger } from 'drizzle-orm/logger';
55
import { NoopLogger } from 'drizzle-orm/logger';
6-
import type { RelationalSchemaConfig, TablesRelationalConfig } from 'drizzle-orm/relations';
6+
import type { AnyRelations, EmptyRelations } from 'drizzle-orm/relations';
77
import { type Query } from 'drizzle-orm/sql/sql';
88
import type { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core/dialect';
99
import type { SelectedFieldsOrdered } from 'drizzle-orm/sqlite-core/query-builders/select.types';
@@ -16,38 +16,45 @@ 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;
19+
type ResultMapper = (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown;
20+
type RelationalResultMapper = (
21+
rows: Record<string, unknown>[],
22+
mapColumnValue?: (value: unknown) => unknown
23+
) => unknown;
2124

2225
export interface PowerSyncSQLiteSessionOptions {
2326
logger?: Logger;
27+
db: AbstractPowerSyncDatabase;
2428
}
2529

2630
export type PowerSyncSQLiteTransactionConfig = SQLiteTransactionConfig & {
2731
accessMode?: 'read only' | 'read write';
2832
};
2933

30-
export class PowerSyncSQLiteTransaction<
31-
TFullSchema extends Record<string, unknown>,
32-
TSchema extends TablesRelationalConfig
33-
> extends SQLiteTransactionBase {
34+
export class PowerSyncSQLiteTransaction<TRelations extends AnyRelations = EmptyRelations> extends SQLiteTransaction<
35+
'async',
36+
QueryResult,
37+
Record<string, never>,
38+
TRelations
39+
> {
3440
static readonly [entityKind]: string = 'PowerSyncSQLiteTransaction';
3541
}
3642

37-
export class PowerSyncSQLiteBaseSession<
38-
TFullSchema extends Record<string, unknown>,
39-
TSchema extends TablesRelationalConfig
40-
> extends SQLiteSessionBase {
43+
export class PowerSyncSQLiteBaseSession<TRelations extends AnyRelations = EmptyRelations> extends SQLiteSession<
44+
'async',
45+
QueryResult,
46+
Record<string, never>,
47+
TRelations
48+
> {
4149
static readonly [entityKind]: string = 'PowerSyncSQLiteBaseSession';
4250

4351
protected logger: Logger;
4452

4553
constructor(
4654
protected contextProvider: ContextProvider,
4755
protected dialect: SQLiteAsyncDialect,
48-
protected schema: RelationalSchemaConfig<TSchema> | undefined,
49-
protected options: PowerSyncSQLiteSessionOptions = {},
50-
protected relations: Record<string, unknown> = {}
56+
protected relations: TRelations,
57+
protected options: PowerSyncSQLiteSessionOptions
5158
) {
5259
super(dialect);
5360
this.logger = options.logger ?? new NoopLogger();
@@ -58,7 +65,7 @@ export class PowerSyncSQLiteBaseSession<
5865
fields: SelectedFieldsOrdered | undefined,
5966
executeMethod: SQLiteExecuteMethod,
6067
isResponseInArrayMode: boolean,
61-
customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown,
68+
customResultMapper?: ResultMapper,
6269
queryMetadata?: {
6370
type: 'select' | 'update' | 'delete' | 'insert';
6471
tables: string[];
@@ -73,7 +80,7 @@ export class PowerSyncSQLiteBaseSession<
7380
executeMethod,
7481
isResponseInArrayMode,
7582
customResultMapper,
76-
undefined, // cache not supported yet
83+
undefined,
7784
queryMetadata,
7885
cacheConfig
7986
);
@@ -83,7 +90,7 @@ export class PowerSyncSQLiteBaseSession<
8390
query: Query,
8491
fields: SelectedFieldsOrdered | undefined,
8592
executeMethod: SQLiteExecuteMethod,
86-
customResultMapper: (rows: Record<string, unknown>[], mapColumnValue?: (value: unknown) => unknown) => unknown
93+
customResultMapper: RelationalResultMapper
8794
): PowerSyncSQLitePreparedQuery<T> {
8895
return new PowerSyncSQLitePreparedQuery(
8996
this.contextProvider,
@@ -101,7 +108,7 @@ export class PowerSyncSQLiteBaseSession<
101108
}
102109

103110
transaction<T>(
104-
_transaction: (tx: PowerSyncSQLiteTransaction<TFullSchema, TSchema>) => T,
111+
_transaction: (tx: PowerSyncSQLiteTransaction<TRelations>) => T,
105112
_config: PowerSyncSQLiteTransactionConfig = {}
106113
): T {
107114
throw new Error('Nested transactions are not supported');

0 commit comments

Comments
 (0)