Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/calm-rivers-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/drizzle-driver': minor
---

Rewrite the Drizzle driver against the Drizzle v1 beta `relations` + `db.query` API and drop `0.x` compatibility from this release line.
2 changes: 1 addition & 1 deletion demos/react-neon-tanstack-query-notes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"drizzle-kit": "^0.31.7",
"drizzle-orm": "^0.44.7",
"drizzle-orm": "1.0.0-beta.19",
"lucide-react": "^0.503.0",
"moment": "^2.30.1",
"react": "^19.2.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineRelations } from 'drizzle-orm';
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { relations } from 'drizzle-orm';

export const notes = sqliteTable('notes', {
id: text().primaryKey(),
Expand All @@ -17,10 +17,9 @@ export const paragraphs = sqliteTable('paragraphs', {
created_at: text().notNull(),
});

export const notesRelations = relations(notes, ({ many }) => ({
paragraphs: many(paragraphs),
}));

export const drizzleSchema = {
notes, paragraphs, notesRelations
};
notes,
paragraphs
};

export const drizzleRelations = defineRelations(drizzleSchema);
6 changes: 4 additions & 2 deletions demos/react-neon-tanstack-query-notes/src/lib/powersync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
DrizzleAppSchema,
} from "@powersync/drizzle-driver";

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

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

export const powersyncDrizzle = wrapPowerSyncWithDrizzle(powersync);
export const powersyncDrizzle = wrapPowerSyncWithDrizzle(powersync, {
relations: drizzleRelations,
});

let isInitialized = false;

Expand Down
48 changes: 28 additions & 20 deletions packages/drizzle-driver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This package (`@powersync/drizzle-driver`) brings the benefits of an ORM through

## Beta Release

The `drizzle-driver` package is currently in an Beta release.
This release line targets `drizzle-orm@1.0.0-beta` and the beta `relations` + `db.query` API.

## Getting Started

Expand All @@ -13,7 +13,7 @@ Set up the PowerSync Database and wrap it with Drizzle.
```js
import { wrapPowerSyncWithDrizzle } from '@powersync/drizzle-driver';
import { PowerSyncDatabase } from '@powersync/web';
import { relations } from 'drizzle-orm';
import { defineRelations } from 'drizzle-orm';
import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
import { AppSchema } from './schema';

Expand All @@ -29,27 +29,25 @@ export const todos = sqliteTable('todos', {
created_at: text('created_at')
});

export const listsRelations = relations(lists, ({ one, many }) => ({
todos: many(todos)
}));

export const todosRelations = relations(todos, ({ one, many }) => ({
list: one(lists, {
fields: [todos.list_id],
references: [lists.id]
})
export const relations = defineRelations({ lists, todos }, (r) => ({
lists: {
todos: r.many.todos({
from: r.lists.id,
to: r.todos.list_id
})
},
todos: {
list: r.one.lists({
from: r.todos.list_id,
to: r.lists.id,
optional: false
})
}
}));

export const drizzleSchema = {
lists,
todos,
listsRelations,
todosRelations
};

// As an alternative to manually defining a PowerSync schema, generate the local PowerSync schema from the Drizzle schema with the `DrizzleAppSchema` constructor:
// import { DrizzleAppSchema } from '@powersync/drizzle-driver';
// export const AppSchema = new DrizzleAppSchema(drizzleSchema);
// export const AppSchema = new DrizzleAppSchema({ lists, todos });
//
// This is optional, but recommended, since you will only need to maintain one schema on the client-side
// Read on to learn more.
Expand All @@ -63,7 +61,17 @@ export const powerSyncDb = new PowerSyncDatabase({

// This is the DB you will use in queries
export const db = wrapPowerSyncWithDrizzle(powerSyncDb, {
schema: drizzleSchema
relations
});
```

To make relational queries, use `db.query`:

```js
const listsWithTodos = await db.query.lists.findMany({
with: {
todos: true
}
});
```

Expand Down
4 changes: 2 additions & 2 deletions packages/drizzle-driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
},
"peerDependencies": {
"@powersync/common": "workspace:^1.51.0",
"drizzle-orm": "<1.0.0"
"drizzle-orm": ">=1.0.0-beta.19 <1.0.0"
},
"devDependencies": {
"@journeyapps/wa-sqlite": "catalog:",
Expand All @@ -56,7 +56,7 @@
"@rollup/plugin-node-resolve": "catalog:",
"@rollup/plugin-typescript": "catalog:",
"@types/node": "catalog:",
"drizzle-orm": "catalog:",
"drizzle-orm": "1.0.0-beta.19",
"rollup": "catalog:",
"rollup-plugin-dts": "catalog:",
"vite": "catalog:"
Expand Down
62 changes: 47 additions & 15 deletions packages/drizzle-driver/src/sqlite/PowerSyncSQLiteBaseSession.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { QueryResult } from '@powersync/common';
import type { AbstractPowerSyncDatabase, QueryResult } from '@powersync/common';
import type { WithCacheConfig } from 'drizzle-orm/cache/core/types';
import { entityKind } from 'drizzle-orm/entity';
import type { Logger } from 'drizzle-orm/logger';
import { NoopLogger } from 'drizzle-orm/logger';
import type { RelationalSchemaConfig, TablesRelationalConfig } from 'drizzle-orm/relations';
import type { AnyRelations, EmptyRelations } from 'drizzle-orm/relations';
import { type Query } from 'drizzle-orm/sql/sql';
import type { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core/dialect';
import type { SelectedFieldsOrdered } from 'drizzle-orm/sqlite-core/query-builders/select.types';
Expand All @@ -16,34 +16,45 @@ import {
} from 'drizzle-orm/sqlite-core/session';
import { PowerSyncSQLitePreparedQuery, type ContextProvider } from './PowerSyncSQLitePreparedQuery.js';

type ResultMapper = (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown;
type RelationalResultMapper = (
rows: Record<string, unknown>[],
mapColumnValue?: (value: unknown) => unknown
) => unknown;

export interface PowerSyncSQLiteSessionOptions {
logger?: Logger;
db: AbstractPowerSyncDatabase;
}

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

export class PowerSyncSQLiteTransaction<
TFullSchema extends Record<string, unknown>,
TSchema extends TablesRelationalConfig
> extends SQLiteTransaction<'async', QueryResult, TFullSchema, TSchema> {
export class PowerSyncSQLiteTransaction<TRelations extends AnyRelations = EmptyRelations> extends SQLiteTransaction<
'async',
QueryResult,
Record<string, never>,
TRelations
> {
static readonly [entityKind]: string = 'PowerSyncSQLiteTransaction';
}

export class PowerSyncSQLiteBaseSession<
TFullSchema extends Record<string, unknown>,
TSchema extends TablesRelationalConfig
> extends SQLiteSession<'async', QueryResult, TFullSchema, TSchema> {
export class PowerSyncSQLiteBaseSession<TRelations extends AnyRelations = EmptyRelations> extends SQLiteSession<
'async',
QueryResult,
Record<string, never>,
TRelations
> {
static readonly [entityKind]: string = 'PowerSyncSQLiteBaseSession';

protected logger: Logger;

constructor(
protected contextProvider: ContextProvider,
protected dialect: SQLiteAsyncDialect,
protected schema: RelationalSchemaConfig<TSchema> | undefined,
protected options: PowerSyncSQLiteSessionOptions = {}
protected relations: TRelations,
protected options: PowerSyncSQLiteSessionOptions
) {
super(dialect);
this.logger = options.logger ?? new NoopLogger();
Expand All @@ -54,7 +65,7 @@ export class PowerSyncSQLiteBaseSession<
fields: SelectedFieldsOrdered | undefined,
executeMethod: SQLiteExecuteMethod,
isResponseInArrayMode: boolean,
customResultMapper?: (rows: unknown[][], mapColumnValue?: (value: unknown) => unknown) => unknown,
customResultMapper?: ResultMapper,
queryMetadata?: {
type: 'select' | 'update' | 'delete' | 'insert';
tables: string[];
Expand All @@ -69,14 +80,35 @@ export class PowerSyncSQLiteBaseSession<
executeMethod,
isResponseInArrayMode,
customResultMapper,
undefined, // cache not supported yet
undefined,
queryMetadata,
cacheConfig
);
}

prepareRelationalQuery<T extends PreparedQueryConfigBase & { type: 'async' }>(
query: Query,
fields: SelectedFieldsOrdered | undefined,
executeMethod: SQLiteExecuteMethod,
customResultMapper: RelationalResultMapper
): PowerSyncSQLitePreparedQuery<T> {
return new PowerSyncSQLitePreparedQuery(
this.contextProvider,
query,
this.logger,
fields,
executeMethod,
false,
customResultMapper,
undefined,
{ type: 'select', tables: [] },
undefined,
true
);
}

transaction<T>(
_transaction: (tx: PowerSyncSQLiteTransaction<TFullSchema, TSchema>) => T,
_transaction: (tx: PowerSyncSQLiteTransaction<TRelations>) => T,
_config: PowerSyncSQLiteTransactionConfig = {}
): T {
throw new Error('Nested transactions are not supported');
Expand Down
Loading