Skip to content

Commit 53fd64e

Browse files
ImSingeeChriztiaan
andauthored
toPowerSyncTable support casing (#436)
Co-authored-by: Christiaan Landman <[email protected]>
1 parent 8f2daa8 commit 53fd64e

File tree

4 files changed

+97
-11
lines changed

4 files changed

+97
-11
lines changed

.changeset/strange-coins-leave.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+
Added support for casing option in the Drizzle schema helper functions.

packages/drizzle-driver/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { toCompilableQuery } from './utils/compilableQuery';
33
import {
44
DrizzleAppSchema,
55
toPowerSyncTable,
6+
type DrizzleAppSchemaOptions,
67
type DrizzleTablePowerSyncOptions,
78
type DrizzleTableWithPowerSyncOptions,
89
type Expand,
@@ -13,6 +14,7 @@ import {
1314

1415
export {
1516
DrizzleAppSchema,
17+
DrizzleAppSchemaOptions,
1618
DrizzleTablePowerSyncOptions,
1719
DrizzleTableWithPowerSyncOptions,
1820
DrizzleQuery,

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

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ import {
88
type TableV2Options
99
} from '@powersync/common';
1010
import { InferSelectModel, isTable, Relations } from 'drizzle-orm';
11+
import type { Casing } from 'drizzle-orm';
12+
import { CasingCache } from 'drizzle-orm/casing';
1113
import {
1214
getTableConfig,
1315
SQLiteInteger,
1416
SQLiteReal,
1517
SQLiteText,
1618
type SQLiteTableWithColumns,
17-
type TableConfig
19+
type TableConfig,
20+
type SQLiteColumn
1821
} from 'drizzle-orm/sqlite-core';
1922

2023
export type ExtractPowerSyncColumns<T extends SQLiteTableWithColumns<any>> = {
@@ -25,14 +28,17 @@ export type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
2528

2629
export function toPowerSyncTable<T extends SQLiteTableWithColumns<any>>(
2730
table: T,
28-
options?: Omit<TableV2Options, 'indexes'>
31+
options?: Omit<TableV2Options, 'indexes'> & { casingCache?: CasingCache }
2932
): Table<Expand<ExtractPowerSyncColumns<T>>> {
3033
const { columns: drizzleColumns, indexes: drizzleIndexes } = getTableConfig(table);
34+
const { casingCache } = options ?? {};
3135

3236
const columns: { [key: string]: BaseColumnType<number | string | null> } = {};
3337
for (const drizzleColumn of drizzleColumns) {
38+
const name = casingCache?.getColumnCasing(drizzleColumn) ?? drizzleColumn.name;
39+
3440
// Skip the id column
35-
if (drizzleColumn.name === 'id') {
41+
if (name === 'id') {
3642
continue;
3743
}
3844

@@ -50,7 +56,7 @@ export function toPowerSyncTable<T extends SQLiteTableWithColumns<any>>(
5056
default:
5157
throw new Error(`Unsupported column type: ${drizzleColumn.columnType}`);
5258
}
53-
columns[drizzleColumn.name] = mappedType;
59+
columns[name] = mappedType;
5460
}
5561
const indexes: IndexShorthand = {};
5662

@@ -61,7 +67,9 @@ export function toPowerSyncTable<T extends SQLiteTableWithColumns<any>>(
6167
}
6268
const columns: string[] = [];
6369
for (const indexColumn of index.config.columns) {
64-
columns.push((indexColumn as { name: string }).name);
70+
const name = casingCache?.getColumnCasing(indexColumn as SQLiteColumn) ?? (indexColumn as { name: string }).name;
71+
72+
columns.push(name);
6573
}
6674

6775
indexes[index.config.name] = columns;
@@ -73,7 +81,7 @@ export type DrizzleTablePowerSyncOptions = Omit<TableV2Options, 'indexes'>;
7381

7482
export type DrizzleTableWithPowerSyncOptions = {
7583
tableDefinition: SQLiteTableWithColumns<any>;
76-
options?: DrizzleTablePowerSyncOptions | undefined;
84+
options?: DrizzleTablePowerSyncOptions;
7785
};
7886

7987
export type TableName<T> =
@@ -97,7 +105,9 @@ export type TablesFromSchemaEntries<T> = {
97105

98106
function toPowerSyncTables<
99107
T extends Record<string, SQLiteTableWithColumns<any> | Relations | DrizzleTableWithPowerSyncOptions>
100-
>(schemaEntries: T) {
108+
>(schemaEntries: T, options?: DrizzleAppSchemaOptions) {
109+
const casingCache = options?.casing ? new CasingCache(options?.casing) : undefined;
110+
101111
const tables: Record<string, Table> = {};
102112
for (const schemaEntry of Object.values(schemaEntries)) {
103113
let maybeTable: SQLiteTableWithColumns<any> | Relations | undefined = undefined;
@@ -113,18 +123,24 @@ function toPowerSyncTables<
113123

114124
if (isTable(maybeTable)) {
115125
const { name } = getTableConfig(maybeTable);
116-
tables[name] = toPowerSyncTable(maybeTable as SQLiteTableWithColumns<TableConfig>, maybeOptions);
126+
tables[name] = toPowerSyncTable(maybeTable as SQLiteTableWithColumns<TableConfig>, {
127+
...maybeOptions,
128+
casingCache
129+
});
117130
}
118131
}
119132

120133
return tables;
121134
}
122135

136+
export type DrizzleAppSchemaOptions = {
137+
casing?: Casing;
138+
};
123139
export class DrizzleAppSchema<
124140
T extends Record<string, SQLiteTableWithColumns<any> | Relations | DrizzleTableWithPowerSyncOptions>
125141
> extends Schema {
126-
constructor(drizzleSchema: T) {
127-
super(toPowerSyncTables(drizzleSchema));
142+
constructor(drizzleSchema: T, options?: DrizzleAppSchemaOptions) {
143+
super(toPowerSyncTables(drizzleSchema, options));
128144
// This is just used for typing
129145
this.types = {} as SchemaTableType<Expand<TablesFromSchemaEntries<T>>>;
130146
}

packages/drizzle-driver/tests/sqlite/schema.test.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { column, Schema, Table } from '@powersync/common';
22
import { 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';
5+
import { CasingCache } from 'drizzle-orm/casing';
56

67
describe('toPowerSyncTable', () => {
78
it('basic conversion', () => {
@@ -55,7 +56,11 @@ describe('toPowerSyncTable', () => {
5556
name: text('name').notNull()
5657
});
5758

58-
const convertedList = toPowerSyncTable(lists, { localOnly: true, insertOnly: true, viewName: 'listsView' });
59+
const convertedList = toPowerSyncTable(lists, {
60+
localOnly: true,
61+
insertOnly: true,
62+
viewName: 'listsView'
63+
});
5964

6065
const expectedLists = new Table(
6166
{
@@ -66,6 +71,32 @@ describe('toPowerSyncTable', () => {
6671

6772
expect(convertedList).toEqual(expectedLists);
6873
});
74+
75+
it('conversion with casing', () => {
76+
const lists = sqliteTable(
77+
'lists',
78+
{
79+
id: text('id').primaryKey(),
80+
myName: text().notNull(),
81+
yourName: text('yourName').notNull() // explicitly set casing
82+
},
83+
(lists) => ({
84+
names: index('names').on(lists.myName, lists.yourName)
85+
})
86+
);
87+
88+
const convertedList = toPowerSyncTable(lists, { casingCache: new CasingCache('snake_case') });
89+
90+
const expectedLists = new Table(
91+
{
92+
my_name: column.text,
93+
yourName: column.text
94+
},
95+
{ indexes: { names: ['my_name', 'yourName'] } }
96+
);
97+
98+
expect(convertedList).toEqual(expectedLists);
99+
});
69100
});
70101

71102
describe('DrizzleAppSchema constructor', () => {
@@ -197,4 +228,36 @@ describe('DrizzleAppSchema constructor', () => {
197228

198229
expect(convertedSchema.tables).toEqual(expectedSchema.tables);
199230
});
231+
232+
it('conversion with casing', () => {
233+
const lists = sqliteTable(
234+
'lists',
235+
{
236+
id: text('id').primaryKey(),
237+
myName: text().notNull(),
238+
yourName: text('yourName').notNull() // explicitly set casing
239+
},
240+
(lists) => ({
241+
names: index('names').on(lists.myName, lists.yourName)
242+
})
243+
);
244+
245+
const drizzleSchemaWithOptions = {
246+
lists
247+
};
248+
249+
const convertedSchema = new DrizzleAppSchema(drizzleSchemaWithOptions, { casing: 'snake_case' });
250+
251+
const expectedSchema = new Schema({
252+
lists: new Table(
253+
{
254+
my_name: column.text,
255+
yourName: column.text
256+
},
257+
{ indexes: { names: ['my_name', 'yourName'] } }
258+
)
259+
});
260+
261+
expect(convertedSchema.tables).toEqual(expectedSchema.tables);
262+
});
200263
});

0 commit comments

Comments
 (0)