Skip to content

Commit 6580f29

Browse files
authored
feat: Drizzle - added support for custom column types in schema conversion (#475)
1 parent 1cc8a94 commit 6580f29

File tree

3 files changed

+91
-19
lines changed

3 files changed

+91
-19
lines changed

.changeset/lemon-moles-lie.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/drizzle-driver': patch
3+
---
4+
5+
Added support for custom column types when converting a Drizzle schema to a PowerSync app schema.

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

+30-18
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/sqlite/schema.test.ts

+56-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { column, Schema, Table } from '@powersync/common';
2-
import { index, integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core';
2+
import { customType, index, integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core';
33
import { describe, expect, it } from 'vitest';
44
import { DrizzleAppSchema, DrizzleTableWithPowerSyncOptions, toPowerSyncTable } from '../../src/utils/schema';
55
import { CasingCache } from 'drizzle-orm/casing';
@@ -105,6 +105,61 @@ describe('toPowerSyncTable', () => {
105105

106106
expect(convertedList).toEqual(expectedLists);
107107
});
108+
109+
it('custom column conversion', () => {
110+
const customSqliteText = customType<{ data: string; driverData: string }>({
111+
dataType() {
112+
return 'text';
113+
},
114+
fromDriver(value) {
115+
return value;
116+
},
117+
toDriver(value) {
118+
return value;
119+
}
120+
});
121+
122+
const customSqliteInteger = customType<{ data: number; driverData: number }>({
123+
dataType() {
124+
return 'integer';
125+
},
126+
fromDriver(value) {
127+
return Number(value);
128+
},
129+
toDriver(value) {
130+
return value;
131+
}
132+
});
133+
134+
const customSqliteReal = customType<{ data: number; driverData: number }>({
135+
dataType() {
136+
return 'real';
137+
},
138+
fromDriver(value) {
139+
return Number(value);
140+
},
141+
toDriver(value) {
142+
return value;
143+
}
144+
});
145+
146+
const lists = sqliteTable('lists', {
147+
id: text('id').primaryKey(),
148+
text_col: customSqliteText('text_col'),
149+
int_col: customSqliteInteger('int_col'),
150+
real_col: customSqliteReal('real_col')
151+
});
152+
153+
const convertedList = toPowerSyncTable(lists);
154+
155+
const expectedLists = new Table({
156+
text_col: column.text,
157+
int_col: column.integer,
158+
real_col: column.real
159+
});
160+
161+
expect(convertedList).toEqual(expectedLists);
162+
});
108163
});
109164

110165
describe('DrizzleAppSchema constructor', () => {

0 commit comments

Comments
 (0)