From dfb81d4a6d42f8c3bca6fb1014b076a78ef68d76 Mon Sep 17 00:00:00 2001 From: Steven Ontong Date: Wed, 28 Aug 2024 10:15:32 +0200 Subject: [PATCH 01/14] experiment: merge tables --- .../src/library/powersync/AppSchema.ts | 6 +- packages/common/src/db/Column.ts | 30 +++++ packages/common/src/db/schema/Schema.ts | 15 ++- packages/common/src/db/schema/Table.ts | 115 ++++++++++++++++-- packages/common/src/db/schema/TableV2.ts | 98 +-------------- 5 files changed, 148 insertions(+), 116 deletions(-) diff --git a/demos/react-supabase-todolist/src/library/powersync/AppSchema.ts b/demos/react-supabase-todolist/src/library/powersync/AppSchema.ts index f84b60595..b2d12ebae 100644 --- a/demos/react-supabase-todolist/src/library/powersync/AppSchema.ts +++ b/demos/react-supabase-todolist/src/library/powersync/AppSchema.ts @@ -1,9 +1,9 @@ -import { column, Schema, TableV2 } from '@powersync/web'; +import { column, Schema, Table } from '@powersync/web'; export const LISTS_TABLE = 'lists'; export const TODOS_TABLE = 'todos'; -const todos = new TableV2( +const todos = new Table( { list_id: column.text, created_at: column.text, @@ -16,7 +16,7 @@ const todos = new TableV2( { indexes: { list: ['list_id'] } } ); -const lists = new TableV2({ +const lists = new Table({ created_at: column.text, name: column.text, owner_id: column.text diff --git a/packages/common/src/db/Column.ts b/packages/common/src/db/Column.ts index 0cd49bc68..923ea0718 100644 --- a/packages/common/src/db/Column.ts +++ b/packages/common/src/db/Column.ts @@ -10,6 +10,36 @@ export interface ColumnOptions { type?: ColumnType; } +export type BaseColumnType = { + type: ColumnType; +}; + +export type ColumnsType = Record>; + +export type ExtractColumnValueType> = T extends BaseColumnType ? R : unknown; + +const text: BaseColumnType = { + type: ColumnType.TEXT +}; + +const integer: BaseColumnType = { + type: ColumnType.INTEGER +}; + +const real: BaseColumnType = { + type: ColumnType.REAL +}; + +// There is maximum of 127 arguments for any function in SQLite. Currently we use json_object which uses 1 arg per key (column name) +// and one per value, which limits it to 63 arguments. +const MAX_AMOUNT_OF_COLUMNS = 63; + +export const column = { + text, + integer, + real +}; + export class Column { constructor(protected options: ColumnOptions) {} diff --git a/packages/common/src/db/schema/Schema.ts b/packages/common/src/db/schema/Schema.ts index eed148554..bd0018a42 100644 --- a/packages/common/src/db/schema/Schema.ts +++ b/packages/common/src/db/schema/Schema.ts @@ -1,7 +1,6 @@ -import { Table as ClassicTable } from './Table'; -import { RowType, TableV2 } from './TableV2'; +import { RowType, Table } from './Table'; -type SchemaType = Record>; +type SchemaType = Record>; type SchemaTableType = { [K in keyof S]: RowType; @@ -16,9 +15,9 @@ export class Schema { */ readonly types: SchemaTableType; readonly props: S; - readonly tables: ClassicTable[]; + readonly tables: Table[]; - constructor(tables: ClassicTable[] | S) { + constructor(tables: Table[] | S) { if (Array.isArray(tables)) { this.tables = tables; } else { @@ -28,20 +27,20 @@ export class Schema { } validate() { - for (const table of this.tables as ClassicTable[]) { + for (const table of this.tables) { table.validate(); } } toJSON() { return { - tables: (this.tables as ClassicTable[]).map((t) => t.toJSON()) + tables: this.tables.map((t) => t.toJSON()) }; } private convertToClassicTables(props: S) { return Object.entries(props).map(([name, table]) => { - return ClassicTable.createTable(name, table); + return Table.createTable(name, table); }); } } diff --git a/packages/common/src/db/schema/Table.ts b/packages/common/src/db/schema/Table.ts index 7059bb4c9..c09b0b4ea 100644 --- a/packages/common/src/db/schema/Table.ts +++ b/packages/common/src/db/schema/Table.ts @@ -1,5 +1,6 @@ -import { Column } from '../Column'; -import type { Index } from './Index'; +import { BaseColumnType, column, Column, ColumnsType, ColumnType, ExtractColumnValueType } from '../Column'; +import { Index } from './Index'; +import { IndexedColumn } from './IndexedColumn'; import { TableV2 } from './TableV2'; export interface TableOptions { @@ -14,19 +15,36 @@ export interface TableOptions { viewName?: string; } -export const DEFAULT_TABLE_OPTIONS: Partial = { +export type RowType> = { + [K in keyof T['columnMap']]: ExtractColumnValueType; +} & { + id: string; +}; + +export type IndexShorthand = Record; + +export interface TableV2Options { + indexes?: IndexShorthand; + localOnly?: boolean; + insertOnly?: boolean; + viewName?: string; +} + +export const DEFAULT_TABLE_OPTIONS = { indexes: [], insertOnly: false, localOnly: false }; -const MAX_AMOUNT_OF_COLUMNS = 63 +const MAX_AMOUNT_OF_COLUMNS = 63; export const InvalidSQLCharacters = /["'%,.#\s[\]]/; -export class Table { +export class Table { protected options: TableOptions; + protected _mappedColumns: Columns; + static createLocalOnly(options: TableOptions) { return new Table({ ...options, localOnly: true, insertOnly: false }); } @@ -35,7 +53,7 @@ export class Table { return new Table({ ...options, localOnly: false, insertOnly: true }); } - static createTable(name: string, table: TableV2) { + static createTable(name: string, table: Table) { return new Table({ name, columns: Object.entries(table.columns).map(([name, col]) => new Column({ name, type: col.type })), @@ -46,8 +64,58 @@ export class Table { }); } - constructor(options: TableOptions) { - this.options = { ...DEFAULT_TABLE_OPTIONS, ...options }; + constructor(columns: Columns, options?: TableV2Options); + constructor(options: TableOptions); + constructor(optionsOrColumns: Columns | TableOptions, v2Options?: TableV2Options) { + if (!Array.isArray(optionsOrColumns.columns)) { + this._mappedColumns = optionsOrColumns as Columns; + } + + // This is a WIP, might cleanup + + // Convert mappings to base columns and indexes + const columns = Array.isArray(optionsOrColumns.columns) + ? optionsOrColumns.columns + : Object.entries(optionsOrColumns).map( + ([name, columnInfo]) => + new Column({ + name, + type: columnInfo.type + }) + ); + + const indexes = Array.isArray(optionsOrColumns.indexes) + ? optionsOrColumns.indexes + : Object.entries(v2Options?.indexes ?? {}).map( + ([name, columnNames]) => + new Index({ + name: name, + columns: columnNames.map( + (name) => + new IndexedColumn({ + name: name.replace(/^-/, ''), + ascending: !name.startsWith('-') + }) + ) + }) + ); + + const insertOnly = + typeof optionsOrColumns.insertOnly == 'boolean' + ? optionsOrColumns.insertOnly + : (v2Options?.insertOnly ?? DEFAULT_TABLE_OPTIONS.insertOnly); + const viewName = typeof optionsOrColumns.viewName == 'string' ? optionsOrColumns.viewName : v2Options?.viewName; + const localOnly = + typeof optionsOrColumns.localOnly == 'boolean' ? optionsOrColumns.localOnly : v2Options?.localOnly; + + this.options = { + columns, + name: typeof optionsOrColumns.name == 'string' ? optionsOrColumns.name : 'missing', + indexes, + insertOnly, + localOnly, + viewName + }; } get name() { @@ -66,6 +134,16 @@ export class Table { return this.options.columns; } + get columnMap(): Columns { + return ( + this._mappedColumns ?? + this.columns.reduce((hash: Record>, column) => { + hash[column.name] = { type: column.type ?? ColumnType.TEXT }; + return hash; + }, {} as Columns) + ); + } + get indexes() { return this.options.indexes ?? []; } @@ -105,8 +183,10 @@ export class Table { throw new Error(`Invalid characters in view name: ${this.viewNameOverride}`); } - if(this.columns.length > MAX_AMOUNT_OF_COLUMNS) { - throw new Error(`Table ${this.name} has too many columns. The maximum number of columns is ${MAX_AMOUNT_OF_COLUMNS}.`); + if (this.columns.length > MAX_AMOUNT_OF_COLUMNS) { + throw new Error( + `Table ${this.name} has too many columns. The maximum number of columns is ${MAX_AMOUNT_OF_COLUMNS}.` + ); } const columnNames = new Set(); @@ -156,3 +236,18 @@ export class Table { }; } } + +const test = new Table( + { + list_id: column.text, + created_at: column.text, + completed_at: column.text, + description: column.text, + created_by: column.text, + completed_by: column.text, + completed: column.integer + }, + { indexes: { list: ['list_id'] } } +); + +const r = test.columnMap; diff --git a/packages/common/src/db/schema/TableV2.ts b/packages/common/src/db/schema/TableV2.ts index 7c61672dd..4a4c0fe9c 100644 --- a/packages/common/src/db/schema/TableV2.ts +++ b/packages/common/src/db/schema/TableV2.ts @@ -1,99 +1,7 @@ -import { ColumnType } from '../Column'; -import { Index } from './Index'; -import { IndexedColumn } from './IndexedColumn'; -import { InvalidSQLCharacters } from './Table'; - -export type BaseColumnType = { - type: ColumnType; -}; - -const text: BaseColumnType = { - type: ColumnType.TEXT -}; - -const integer: BaseColumnType = { - type: ColumnType.INTEGER -}; - -const real: BaseColumnType = { - type: ColumnType.REAL -}; - -// There is maximum of 127 arguments for any function in SQLite. Currently we use json_object which uses 1 arg per key (column name) -// and one per value, which limits it to 63 arguments. -const MAX_AMOUNT_OF_COLUMNS = 63; - -export const column = { - text, - integer, - real -}; - -export type ColumnsType = Record>; - -export type ExtractColumnValueType> = T extends BaseColumnType ? R : unknown; - -export type RowType> = { - [K in keyof T['columns']]: ExtractColumnValueType; -} & { - id: string; -}; - -export type IndexShorthand = Record; - -export interface TableV2Options { - indexes?: IndexShorthand; - localOnly?: boolean; - insertOnly?: boolean; - viewName?: string; -} +import { ColumnsType } from '../Column'; +import { Table } from './Table'; /* Generate a new table from the columns and indexes */ -export class TableV2 { - public indexes: Index[]; - - constructor( - public columns: Columns, - public options: TableV2Options = {} - ) { - this.validateTable(columns); - - if (options?.indexes) { - this.indexes = Object.entries(options.indexes).map(([name, columns]) => { - if (name.startsWith('-')) { - return new Index({ - name: name.substring(1), - columns: columns.map((c) => new IndexedColumn({ name: c, ascending: false })) - }); - } - - return new Index({ - name: name, - columns: columns.map((c) => new IndexedColumn({ name: c, ascending: true })) - }); - }); - } - } - - private validateTable(columns: Columns) { - const columnNames = Object.keys(columns); - const columnLength = columnNames.length; - - if (columnNames.includes('id')) { - throw new Error(`An id column is automatically added, custom id columns are not supported`); - } - - if (columnLength > MAX_AMOUNT_OF_COLUMNS) { - throw new Error(`TableV2 cannot have more than ${MAX_AMOUNT_OF_COLUMNS} columns`); - } - - columnNames - .map((column) => { - if (InvalidSQLCharacters.test(column)) { - throw new Error(`Invalid characters in column name: ${column}`); - } - }) - } -} +export class TableV2 extends Table {} From 67c0a613ad02c2d61c412092681b41a43e1cbb96 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Wed, 28 Aug 2024 14:47:28 +0200 Subject: [PATCH 02/14] chore: merge tables --- .prettierignore | 4 +- .../client/sync/bucket/SqliteBucketStorage.ts | 4 +- packages/common/src/db/{ => schema}/Column.ts | 2 +- .../common/src/db/schema/IndexedColumn.ts | 2 +- packages/common/src/db/schema/Table.ts | 127 ++++++------- packages/common/src/db/schema/TableV2.ts | 2 +- packages/common/src/index.ts | 6 +- packages/common/tests/db/schema/Table.test.ts | 177 ++++++++++++++++++ .../common/tests/db/schema/TableV2.test.ts | 86 --------- packages/kysely-driver/README.md | 2 +- tools/diagnostics-app/README.md | 2 +- 11 files changed, 247 insertions(+), 167 deletions(-) rename packages/common/src/db/{ => schema}/Column.ts (96%) create mode 100644 packages/common/tests/db/schema/Table.test.ts delete mode 100644 packages/common/tests/db/schema/TableV2.test.ts diff --git a/.prettierignore b/.prettierignore index b992e6cb7..325716636 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,4 +11,6 @@ **/android/** **/assets/** **/bin/** -**/ios/** \ No newline at end of file +**/ios/** + +pnpm-lock.yaml diff --git a/packages/common/src/client/sync/bucket/SqliteBucketStorage.ts b/packages/common/src/client/sync/bucket/SqliteBucketStorage.ts index 6a4feff53..96f256006 100644 --- a/packages/common/src/client/sync/bucket/SqliteBucketStorage.ts +++ b/packages/common/src/client/sync/bucket/SqliteBucketStorage.ts @@ -103,9 +103,7 @@ export class SqliteBucketStorage extends BaseObserver imp */ private async deleteBucket(bucket: string) { await this.writeTransaction(async (tx) => { - await tx.execute( - 'INSERT INTO powersync_operations(op, data) VALUES(?, ?)', - ['delete_bucket', bucket]); + await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', ['delete_bucket', bucket]); }); this.logger.debug('done deleting bucket'); diff --git a/packages/common/src/db/Column.ts b/packages/common/src/db/schema/Column.ts similarity index 96% rename from packages/common/src/db/Column.ts rename to packages/common/src/db/schema/Column.ts index 923ea0718..b3ce98609 100644 --- a/packages/common/src/db/Column.ts +++ b/packages/common/src/db/schema/Column.ts @@ -32,7 +32,7 @@ const real: BaseColumnType = { // There is maximum of 127 arguments for any function in SQLite. Currently we use json_object which uses 1 arg per key (column name) // and one per value, which limits it to 63 arguments. -const MAX_AMOUNT_OF_COLUMNS = 63; +export const MAX_AMOUNT_OF_COLUMNS = 63; export const column = { text, diff --git a/packages/common/src/db/schema/IndexedColumn.ts b/packages/common/src/db/schema/IndexedColumn.ts index 32c1fb408..8b92ee43d 100644 --- a/packages/common/src/db/schema/IndexedColumn.ts +++ b/packages/common/src/db/schema/IndexedColumn.ts @@ -1,4 +1,4 @@ -import { ColumnType } from '../Column'; +import { ColumnType } from './Column'; import { Table } from './Table'; export interface IndexColumnOptions { diff --git a/packages/common/src/db/schema/Table.ts b/packages/common/src/db/schema/Table.ts index c09b0b4ea..1286e71ac 100644 --- a/packages/common/src/db/schema/Table.ts +++ b/packages/common/src/db/schema/Table.ts @@ -1,4 +1,11 @@ -import { BaseColumnType, column, Column, ColumnsType, ColumnType, ExtractColumnValueType } from '../Column'; +import { + BaseColumnType, + Column, + ColumnsType, + ColumnType, + ExtractColumnValueType, + MAX_AMOUNT_OF_COLUMNS +} from './Column'; import { Index } from './Index'; import { IndexedColumn } from './IndexedColumn'; import { TableV2 } from './TableV2'; @@ -36,8 +43,6 @@ export const DEFAULT_TABLE_OPTIONS = { localOnly: false }; -const MAX_AMOUNT_OF_COLUMNS = 63; - export const InvalidSQLCharacters = /["'%,.#\s[\]]/; export class Table { @@ -67,55 +72,55 @@ export class Table { constructor(columns: Columns, options?: TableV2Options); constructor(options: TableOptions); constructor(optionsOrColumns: Columns | TableOptions, v2Options?: TableV2Options) { - if (!Array.isArray(optionsOrColumns.columns)) { - this._mappedColumns = optionsOrColumns as Columns; + if (this.isTableV1(optionsOrColumns)) { + this.initTableV1(optionsOrColumns); + } else { + this.initTableV2(optionsOrColumns, v2Options); } + } + + private isTableV1(arg: TableOptions | Columns): arg is TableOptions { + return 'columns' in arg && Array.isArray(arg.columns); + } + + private initTableV1(options: TableOptions) { + this.options = { + ...options, + indexes: options.indexes || [], + insertOnly: options.insertOnly ?? DEFAULT_TABLE_OPTIONS.insertOnly, + localOnly: options.localOnly ?? DEFAULT_TABLE_OPTIONS.localOnly + }; + } + + private initTableV2(columns: Columns, options?: TableV2Options) { + const convertedColumns = Object.entries(columns).map( + ([name, columnInfo]) => new Column({ name, type: columnInfo.type }) + ); - // This is a WIP, might cleanup - - // Convert mappings to base columns and indexes - const columns = Array.isArray(optionsOrColumns.columns) - ? optionsOrColumns.columns - : Object.entries(optionsOrColumns).map( - ([name, columnInfo]) => - new Column({ - name, - type: columnInfo.type - }) - ); - - const indexes = Array.isArray(optionsOrColumns.indexes) - ? optionsOrColumns.indexes - : Object.entries(v2Options?.indexes ?? {}).map( - ([name, columnNames]) => - new Index({ - name: name, - columns: columnNames.map( - (name) => - new IndexedColumn({ - name: name.replace(/^-/, ''), - ascending: !name.startsWith('-') - }) - ) - }) - ); - - const insertOnly = - typeof optionsOrColumns.insertOnly == 'boolean' - ? optionsOrColumns.insertOnly - : (v2Options?.insertOnly ?? DEFAULT_TABLE_OPTIONS.insertOnly); - const viewName = typeof optionsOrColumns.viewName == 'string' ? optionsOrColumns.viewName : v2Options?.viewName; - const localOnly = - typeof optionsOrColumns.localOnly == 'boolean' ? optionsOrColumns.localOnly : v2Options?.localOnly; + const convertedIndexes = Object.entries(options?.indexes ?? {}).map( + ([name, columnNames]) => + new Index({ + name, + columns: columnNames.map( + (name) => + new IndexedColumn({ + name: name.replace(/^-/, ''), + ascending: !name.startsWith('-') + }) + ) + }) + ); this.options = { - columns, - name: typeof optionsOrColumns.name == 'string' ? optionsOrColumns.name : 'missing', - indexes, - insertOnly, - localOnly, - viewName + name: '', + columns: convertedColumns, + indexes: convertedIndexes, + insertOnly: options?.insertOnly ?? DEFAULT_TABLE_OPTIONS.insertOnly, + localOnly: options?.localOnly ?? DEFAULT_TABLE_OPTIONS.localOnly, + viewName: options?.viewName }; + + this._mappedColumns = columns; } get name() { @@ -176,7 +181,7 @@ export class Table { validate() { if (InvalidSQLCharacters.test(this.name)) { - throw new Error(`Invalid characters in table name: ${this.name}`); + throw new Error(`Invalid characters in table name ${this.name ?? ""}`); } if (this.viewNameOverride && InvalidSQLCharacters.test(this.viewNameOverride!)) { @@ -185,7 +190,7 @@ export class Table { if (this.columns.length > MAX_AMOUNT_OF_COLUMNS) { throw new Error( - `Table ${this.name} has too many columns. The maximum number of columns is ${MAX_AMOUNT_OF_COLUMNS}.` + `Table has too many columns. The maximum number of columns is ${MAX_AMOUNT_OF_COLUMNS}.` ); } @@ -194,25 +199,24 @@ export class Table { for (const column of this.columns) { const { name: columnName } = column; if (column.name === 'id') { - throw new Error(`${this.name}: id column is automatically added, custom id columns are not supported`); + throw new Error(`An id column is automatically added, custom id columns are not supported`); } if (columnNames.has(columnName)) { throw new Error(`Duplicate column ${columnName}`); } if (InvalidSQLCharacters.test(columnName)) { - throw new Error(`Invalid characters in column name: $name.${column}`); + throw new Error(`Invalid characters in column name: ${column.name}`); } columnNames.add(columnName); } const indexNames = new Set(); - for (const index of this.indexes) { if (indexNames.has(index.name)) { - throw new Error(`Duplicate index $name.${index}`); + throw new Error(`Duplicate index ${index}`); } if (InvalidSQLCharacters.test(index.name)) { - throw new Error(`Invalid characters in index name: $name.${index}`); + throw new Error(`Invalid characters in index name: ${index}`); } for (const column of index.columns) { @@ -236,18 +240,3 @@ export class Table { }; } } - -const test = new Table( - { - list_id: column.text, - created_at: column.text, - completed_at: column.text, - description: column.text, - created_by: column.text, - completed_by: column.text, - completed: column.integer - }, - { indexes: { list: ['list_id'] } } -); - -const r = test.columnMap; diff --git a/packages/common/src/db/schema/TableV2.ts b/packages/common/src/db/schema/TableV2.ts index 4a4c0fe9c..c65fa5e2f 100644 --- a/packages/common/src/db/schema/TableV2.ts +++ b/packages/common/src/db/schema/TableV2.ts @@ -1,4 +1,4 @@ -import { ColumnsType } from '../Column'; +import { ColumnsType } from './Column'; import { Table } from './Table'; /* diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index d6b93809c..fb76bcefd 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -15,7 +15,7 @@ export * from './client/sync/bucket/OplogEntry'; export * from './client/sync/stream/AbstractRemote'; export * from './client/sync/stream/AbstractStreamingSyncImplementation'; export * from './client/sync/stream/streaming-sync-types'; -export { MAX_OP_ID } from './client/constants' +export { MAX_OP_ID } from './client/constants'; export * from './db/crud/SyncStatus'; export * from './db/crud/UploadQueueStatus'; @@ -23,11 +23,11 @@ export * from './db/schema/Schema'; export * from './db/schema/Table'; export * from './db/schema/Index'; export * from './db/schema/IndexedColumn'; +export * from './db/schema/Column'; +export * from './db/schema/TableV2'; export * from './db/crud/SyncStatus'; export * from './db/crud/UploadQueueStatus'; export * from './db/DBAdapter'; -export * from './db/Column'; -export * from './db/schema/TableV2'; export * from './utils/AbortOperation'; export * from './utils/BaseObserver'; diff --git a/packages/common/tests/db/schema/Table.test.ts b/packages/common/tests/db/schema/Table.test.ts new file mode 100644 index 000000000..2a4b61053 --- /dev/null +++ b/packages/common/tests/db/schema/Table.test.ts @@ -0,0 +1,177 @@ +import { describe, it, expect } from 'vitest'; +import { Table } from '../../../src/db/schema/Table'; +import { column, Column, ColumnType } from '../../../src/db/schema/Column'; +import { Index } from '../../../src/db/schema/Index'; +import { IndexedColumn } from '../../../src/db/schema/IndexedColumn'; + +describe('Table', () => { + it('should create a table with V1 syntax', () => { + const table = new Table({ + name: 'users', + columns: [ + new Column({ name: 'name', type: ColumnType.TEXT }), + new Column({ name: 'age', type: ColumnType.INTEGER }) + ], + indexes: [ + new Index({ + name: 'profile_id', + columns: [new IndexedColumn({ name: 'age' })] + }) + ] + }); + + expect(table.name).toBe('users'); + expect(table.columns.length).toBe(2); + expect(table.columns[0].name).toBe('name'); + expect(table.columns[1].name).toBe('age'); + expect(table.indexes[0].name).toBe('profile_id'); + }); + + it('should create a table with V2 syntax', () => { + const table = new Table( + { + name: column.text, + age: column.integer + }, + { indexes: { nameIndex: ['name'] } } + ); + + expect(table.columns.length).toBe(2); + expect(table.columns[0].name).toBe('name'); + expect(table.columns[1].name).toBe('age'); + expect(table.indexes.length).toBe(1); + expect(table.indexes[0].name).toBe('nameIndex'); + }); + + it('should create a local-only table', () => { + const table = new Table( + { + data: column.text + }, + { localOnly: true } + ); + + expect(table.localOnly).toBe(true); + expect(table.insertOnly).toBe(false); + }); + + it('should create an insert-only table', () => { + const table = new Table( + { + data: column.text + }, + { insertOnly: true } + ); + + expect(table.localOnly).toBe(false); + expect(table.insertOnly).toBe(true); + }); + + it('should create correct internal name', () => { + const normalTable = new Table({ + data: column.text + }); + + expect(normalTable.internalName).toBe('ps_data__'); + + const localTable = new Table( + { + data: column.text + }, + { localOnly: true } + ); + + expect(localTable.internalName).toBe('ps_data_local__'); + }); + + it('should generate correct JSON representation', () => { + const table = new Table( + { + name: column.text, + age: column.integer + }, + { + indexes: { nameIndex: ['name'] }, + viewName: 'customView' + } + ); + + const json = table.toJSON(); + + expect(json).toEqual({ + name: '', + view_name: 'customView', + local_only: false, + insert_only: false, + columns: [ + { name: 'name', type: 'TEXT' }, + { name: 'age', type: 'INTEGER' } + ], + indexes: [{ name: 'nameIndex', columns: [{ ascending: true, name: 'name', type: 'TEXT' },] }] + }); + }); + + it('should handle descending index', () => { + const table = new Table( + { + name: column.text, + age: column.integer + }, + { + indexes: { ageIndex: ['-age'] } + } + ); + + expect(table.indexes[0].columns[0].name).toBe('age'); + expect(table.indexes[0].columns[0].ascending).toBe(false); + }); + + describe("validate", () => { + it('should throw an error for invalid view names', () => { + expect(() => { + new Table( + { + data: column.text + }, + { viewName: 'invalid view' } + ).validate(); + }).toThrowError('Invalid characters in view name'); + }); + + it('should throw an error for custom id columns', () => { + expect(() => { + new Table({ + id: column.text + }).validate(); + }).toThrow('id column is automatically added, custom id columns are not supported'); + }); + + it('should throw an error if more than 63 columns are provided', () => { + const columns = {}; + for (let i = 0; i < 64; i++) { + columns[`column${i}`] = column.text; + } + + expect(() => new Table(columns).validate()).toThrowError('Table has too many columns. The maximum number of columns is 63.'); + }); + + it('should throw an error if an id column is provided', () => { + expect( + () => + new Table({ + id: column.text, + name: column.text + }).validate() + ).toThrowError('An id column is automatically added, custom id columns are not supported'); + }); + + it('should throw an error if a column name contains invalid SQL characters', () => { + expect( + () => + new Table({ + '#invalid-name': column.text + }).validate() + ).toThrowError('Invalid characters in column name: #invalid-name'); + }); + }); +}); diff --git a/packages/common/tests/db/schema/TableV2.test.ts b/packages/common/tests/db/schema/TableV2.test.ts deleted file mode 100644 index 3d708a48e..000000000 --- a/packages/common/tests/db/schema/TableV2.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { TableV2, column } from '../../../src/db/schema/TableV2'; // Adjust the import path as needed -import { ColumnType } from '../../../src/db/Column'; - -describe('TableV2', () => { - it('should create a table with valid columns', () => { - const table = new TableV2({ - name: column.text, - age: column.integer, - height: column.real - }); - - expect(table.columns).toEqual({ - name: { type: ColumnType.TEXT }, - age: { type: ColumnType.INTEGER }, - height: { type: ColumnType.REAL } - }); - }); - - it('should throw an error if more than 63 columns are provided', () => { - const columns = {}; - for (let i = 0; i < 64; i++) { - columns[`column${i}`] = column.text; - } - - expect(() => new TableV2(columns)).toThrowError('TableV2 cannot have more than 63 columns'); - }); - - it('should throw an error if an id column is provided', () => { - expect(() => new TableV2({ - id: column.text, - name: column.text - })).toThrowError('An id column is automatically added, custom id columns are not supported'); - }); - - it('should throw an error if a column name contains invalid SQL characters', () => { - expect(() => new TableV2({ - '#invalid-name': column.text - })).toThrowError('Invalid characters in column name: #invalid-name'); - }); - - it('should create indexes correctly', () => { - const table = new TableV2( - { - name: column.text, - age: column.integer - }, - { - indexes: { - nameIndex: ['name'], - '-ageIndex': ['age'] - } - } - ); - - expect(table.indexes).toHaveLength(2); - expect(table.indexes[0].name).toBe('nameIndex'); - expect(table.indexes[0].columns[0].ascending).toBe(true); - expect(table.indexes[1].name).toBe('ageIndex'); - expect(table.indexes[1].columns[0].ascending).toBe(false); - }); - - it('should allow creating a table with exactly 63 columns', () => { - const columns = {}; - for (let i = 0; i < 63; i++) { - columns[`column${i}`] = column.text; - } - - expect(() => new TableV2(columns)).not.toThrow(); - }); - - it('should allow creating a table with no columns', () => { - expect(() => new TableV2({})).not.toThrow(); - }); - - it('should allow creating a table with no options', () => { - const table = new TableV2({ name: column.text }); - expect(table.options).toEqual({}); - }); - - it('should correctly set options', () => { - const options = { localOnly: true, insertOnly: false, viewName: 'TestView' }; - const table = new TableV2({ name: column.text }, options); - expect(table.options).toEqual(options); - }); -}); diff --git a/packages/kysely-driver/README.md b/packages/kysely-driver/README.md index 846eb69de..82cc7ea52 100644 --- a/packages/kysely-driver/README.md +++ b/packages/kysely-driver/README.md @@ -21,7 +21,7 @@ export const powerSyncDb = new PowerSyncDatabase({ database: { dbFilename: 'test.sqlite' }, - schema: appSchema, + schema: appSchema }); export const db = wrapPowerSyncWithKysely(powerSyncDb); diff --git a/tools/diagnostics-app/README.md b/tools/diagnostics-app/README.md index 73aef4969..2a7d92169 100644 --- a/tools/diagnostics-app/README.md +++ b/tools/diagnostics-app/README.md @@ -32,7 +32,7 @@ The app is now available on [http://localhost:5173/](http://localhost:5173/). Signing in as a user requires a PowerSync Token (JWT) and Endpoint. -**PowerSync Token**: +**PowerSync Token**: Generate a [development token](https://docs.powersync.com/usage/installation/authentication-setup/development-tokens) for the user. From c3edb1be893a154cf58179be2ab971a20c996190 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Wed, 28 Aug 2024 14:51:56 +0200 Subject: [PATCH 03/14] chore: clean up --- demos/react-multi-client/.gitignore | 2 +- packages/common/src/db/schema/Table.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/demos/react-multi-client/.gitignore b/demos/react-multi-client/.gitignore index d857f4f02..7d8c0c88a 100644 --- a/demos/react-multi-client/.gitignore +++ b/demos/react-multi-client/.gitignore @@ -35,4 +35,4 @@ yarn-error.log* .vercel # typescript -*.tsbuildinfo \ No newline at end of file +*.tsbuildinfo diff --git a/packages/common/src/db/schema/Table.ts b/packages/common/src/db/schema/Table.ts index 1286e71ac..0ee28e55b 100644 --- a/packages/common/src/db/schema/Table.ts +++ b/packages/common/src/db/schema/Table.ts @@ -181,7 +181,7 @@ export class Table { validate() { if (InvalidSQLCharacters.test(this.name)) { - throw new Error(`Invalid characters in table name ${this.name ?? ""}`); + throw new Error(`Invalid characters in table`); } if (this.viewNameOverride && InvalidSQLCharacters.test(this.viewNameOverride!)) { @@ -213,10 +213,10 @@ export class Table { const indexNames = new Set(); for (const index of this.indexes) { if (indexNames.has(index.name)) { - throw new Error(`Duplicate index ${index}`); + throw new Error(`Duplicate index ${index.name}`); } if (InvalidSQLCharacters.test(index.name)) { - throw new Error(`Invalid characters in index name: ${index}`); + throw new Error(`Invalid characters in index name: ${index.name}`); } for (const column of index.columns) { From 23c97bdb8fb91e5adf5167baee9fa6ca0e9cf906 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Wed, 28 Aug 2024 14:58:54 +0200 Subject: [PATCH 04/14] chore: changeset --- .changeset/sharp-donuts-push.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sharp-donuts-push.md diff --git a/.changeset/sharp-donuts-push.md b/.changeset/sharp-donuts-push.md new file mode 100644 index 000000000..fc740cb24 --- /dev/null +++ b/.changeset/sharp-donuts-push.md @@ -0,0 +1,5 @@ +--- +'@powersync/common': minor +--- + +Merge `Table` and `TableV2` but kept `TableV2` to avoid making this a breaking change. From cded6550181bf7d4d7a3fc301cb4bd629c1d0c17 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Wed, 28 Aug 2024 16:03:02 +0200 Subject: [PATCH 05/14] fix: failing tests --- packages/common/src/db/schema/Schema.ts | 3 +- packages/common/src/db/schema/Table.ts | 7 + .../common/tests/db/schema/Schema.test.ts | 102 ++++++++++++ .../common/tests/db/schema/TableV2.test.ts | 153 ++++++++++++++++++ 4 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 packages/common/tests/db/schema/Schema.test.ts create mode 100644 packages/common/tests/db/schema/TableV2.test.ts diff --git a/packages/common/src/db/schema/Schema.ts b/packages/common/src/db/schema/Schema.ts index bd0018a42..e97b92fd7 100644 --- a/packages/common/src/db/schema/Schema.ts +++ b/packages/common/src/db/schema/Schema.ts @@ -40,7 +40,8 @@ export class Schema { private convertToClassicTables(props: S) { return Object.entries(props).map(([name, table]) => { - return Table.createTable(name, table); + const convertedTable = new Table({ name, columns: table.columns }); + return convertedTable; }); } } diff --git a/packages/common/src/db/schema/Table.ts b/packages/common/src/db/schema/Table.ts index 0ee28e55b..31fae3300 100644 --- a/packages/common/src/db/schema/Table.ts +++ b/packages/common/src/db/schema/Table.ts @@ -58,6 +58,13 @@ export class Table { return new Table({ ...options, localOnly: false, insertOnly: true }); } + /** + * Create a table. + * @deprecated This was only only included for TableV2 and is no longer necessary. + * Prefer to use new Table() directly. + * + * TODO remove in the next major release. + */ static createTable(name: string, table: Table) { return new Table({ name, diff --git a/packages/common/tests/db/schema/Schema.test.ts b/packages/common/tests/db/schema/Schema.test.ts new file mode 100644 index 000000000..031dc80a8 --- /dev/null +++ b/packages/common/tests/db/schema/Schema.test.ts @@ -0,0 +1,102 @@ +import { describe, it, expect } from 'vitest'; +import { Schema } from '../../../src/db/schema/Schema'; +import { Table } from '../../../src/db/schema/Table'; +import { column, ColumnType } from '../../../src/db/schema/Column'; + +describe('Schema', () => { + it('should create a schema with an array of tables', () => { + const tables = [ + new Table({ name: column.text, }), + new Table({ age: { type: ColumnType.INTEGER } }) + ]; + const schema = new Schema(tables); + + expect(schema.tables).toHaveLength(2); + expect(schema.tables[0].columns[0].name).toBe('name'); + expect(schema.tables[1].columns[0].name).toBe('age'); + }); + + it('should create a schema with a SchemaType object', () => { + const schemaDefinition = { + users: new Table({ + name: column.text, + age: { type: ColumnType.INTEGER } + }), + posts: new Table({ + title: column.text, + content: column.text, + }) + }; + const schema = new Schema(schemaDefinition); + + expect(schema.tables).toHaveLength(2); + expect(schema.props).toBeDefined(); + expect(schema.props.users).toBeDefined(); + expect(schema.props.posts).toBeDefined(); + }); + + it('should validate all tables in the schema', () => { + const schema = new Schema({ + users: new Table({ + name: column.text, + age: column.integer + }), + posts: new Table({ + title: column.text, + content: column.text + }) + }); + + expect(() => schema.validate()).not.toThrow(); + + const invalidSchema = new Schema({ + invalidTable: new Table({ + 'invalid name': column.text, + }) + }); + + expect(() => invalidSchema.validate()).toThrow(); + }); + + it('should generate correct JSON representation', () => { + const schema = new Schema({ + users: new Table({ + name: column.text, + age: { type: ColumnType.INTEGER } + }), + posts: new Table({ + title: column.text, + content: column.text, + }) + }); + + const json = schema.toJSON(); + + expect(json).toEqual({ + tables: [ + { + name: 'users', + view_name: 'users', + local_only: false, + insert_only: false, + columns: [ + { name: 'name', type: 'TEXT' }, + { name: 'age', type: 'INTEGER' } + ], + indexes: [] + }, + { + name: 'posts', + view_name: 'posts', + local_only: false, + insert_only: false, + columns: [ + { name: 'title', type: 'TEXT' }, + { name: 'content', type: 'TEXT' } + ], + indexes: [] + } + ] + }); + }); +}); diff --git a/packages/common/tests/db/schema/TableV2.test.ts b/packages/common/tests/db/schema/TableV2.test.ts new file mode 100644 index 000000000..29477b092 --- /dev/null +++ b/packages/common/tests/db/schema/TableV2.test.ts @@ -0,0 +1,153 @@ +import { describe, it, expect } from 'vitest'; +import { TableV2 } from '../../../src/db/schema/TableV2'; +import { column, Column, ColumnType } from '../../../src/db/schema/Column'; + +describe('TableV2', () => { + it('should create a table', () => { + const table = new TableV2( + { + name: column.text, + age: column.integer + }, + { indexes: { nameIndex: ['name'] } } + ); + + expect(table.columns.length).toBe(2); + expect(table.columns[0].name).toBe('name'); + expect(table.columns[1].name).toBe('age'); + expect(table.indexes.length).toBe(1); + expect(table.indexes[0].name).toBe('nameIndex'); + }); + + it('should create a local-only table', () => { + const table = new TableV2( + { + data: column.text + }, + { localOnly: true } + ); + + expect(table.localOnly).toBe(true); + expect(table.insertOnly).toBe(false); + }); + + it('should create an insert-only table', () => { + const table = new TableV2( + { + data: column.text + }, + { insertOnly: true } + ); + + expect(table.localOnly).toBe(false); + expect(table.insertOnly).toBe(true); + }); + + it('should create correct internal name', () => { + const normalTable = new TableV2({ + data: column.text + }); + + expect(normalTable.internalName).toBe('ps_data__'); + + const localTable = new TableV2( + { + data: column.text + }, + { localOnly: true } + ); + + expect(localTable.internalName).toBe('ps_data_local__'); + }); + + it('should generate correct JSON representation', () => { + const table = new TableV2( + { + name: column.text, + age: column.integer + }, + { + indexes: { nameIndex: ['name'] }, + viewName: 'customView' + } + ); + + const json = table.toJSON(); + + expect(json).toEqual({ + name: '', + view_name: 'customView', + local_only: false, + insert_only: false, + columns: [ + { name: 'name', type: 'TEXT' }, + { name: 'age', type: 'INTEGER' } + ], + indexes: [{ name: 'nameIndex', columns: [{ ascending: true, name: 'name', type: 'TEXT' },] }] + }); + }); + + it('should handle descending index', () => { + const table = new TableV2( + { + name: column.text, + age: column.integer + }, + { + indexes: { ageIndex: ['-age'] } + } + ); + + expect(table.indexes[0].columns[0].name).toBe('age'); + expect(table.indexes[0].columns[0].ascending).toBe(false); + }); + + describe("validate", () => { + it('should throw an error for invalid view names', () => { + expect(() => { + new TableV2( + { + data: column.text + }, + { viewName: 'invalid view' } + ).validate(); + }).toThrowError('Invalid characters in view name'); + }); + + it('should throw an error for custom id columns', () => { + expect(() => { + new TableV2({ + id: column.text + }).validate(); + }).toThrow('id column is automatically added, custom id columns are not supported'); + }); + + it('should throw an error if more than 63 columns are provided', () => { + const columns = {}; + for (let i = 0; i < 64; i++) { + columns[`column${i}`] = column.text; + } + + expect(() => new TableV2(columns).validate()).toThrowError('Table has too many columns. The maximum number of columns is 63.'); + }); + + it('should throw an error if an id column is provided', () => { + expect( + () => + new TableV2({ + id: column.text, + name: column.text + }).validate() + ).toThrowError('An id column is automatically added, custom id columns are not supported'); + }); + + it('should throw an error if a column name contains invalid SQL characters', () => { + expect( + () => + new TableV2({ + '#invalid-name': column.text + }).validate() + ).toThrowError('Invalid characters in column name: #invalid-name'); + }); + }); +}); From e0e8dcb6b451247f5cf69d9339f8de69700b196d Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Wed, 28 Aug 2024 17:41:39 +0200 Subject: [PATCH 06/14] chore: add comments --- packages/common/src/db/schema/Schema.ts | 10 ++++- packages/common/src/db/schema/Table.ts | 53 +++++++++++++++++++++++- packages/common/src/db/schema/TableV2.ts | 4 +- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/packages/common/src/db/schema/Schema.ts b/packages/common/src/db/schema/Schema.ts index e97b92fd7..6ab4ce54d 100644 --- a/packages/common/src/db/schema/Schema.ts +++ b/packages/common/src/db/schema/Schema.ts @@ -34,13 +34,21 @@ export class Schema { toJSON() { return { + // This is required because "name" field is not present in TableV2 tables: this.tables.map((t) => t.toJSON()) }; } private convertToClassicTables(props: S) { return Object.entries(props).map(([name, table]) => { - const convertedTable = new Table({ name, columns: table.columns }); + const convertedTable = new Table({ + name, + columns: table.columns, + indexes: table.indexes, + localOnly: table.localOnly, + insertOnly: table.insertOnly, + viewName: table.viewName + }); return convertedTable; }); } diff --git a/packages/common/src/db/schema/Table.ts b/packages/common/src/db/schema/Table.ts index 31fae3300..1ecea2930 100644 --- a/packages/common/src/db/schema/Table.ts +++ b/packages/common/src/db/schema/Table.ts @@ -68,7 +68,7 @@ export class Table { static createTable(name: string, table: Table) { return new Table({ name, - columns: Object.entries(table.columns).map(([name, col]) => new Column({ name, type: col.type })), + columns: table.columns, indexes: table.indexes, localOnly: table.options.localOnly, insertOnly: table.options.insertOnly, @@ -76,7 +76,58 @@ export class Table { }); } + /** + * Creates a new Table instance. + * + * This constructor supports two different versions: + * 1. New constructor: Using a Columns object and an optional TableV2Options object + * 2. Deprecated constructor: Using a TableOptions object (will be removed in the next major release) + * + * @constructor + * @param {Columns | TableOptions} optionsOrColumns - Either a Columns object (for V2 syntax) or a TableOptions object (for V1 syntax) + * @param {TableV2Options} [v2Options] - Optional configuration options for V2 syntax + * + * @example + * New constructor example + * ```javascript + * const table = new Table( + * { + * name: { type: ColumnType.TEXT }, + * age: { type: ColumnType.INTEGER } + * }, + * { indexes: { nameIndex: ['name'] } } + * ); + *``` + * + * + * @example + * Deprecated constructor example + * ```javascript + * const table = new Table({ + * name: 'users', + * columns: [ + * new Column({ name: 'name', type: ColumnType.TEXT }), + * new Column({ name: 'age', type: ColumnType.INTEGER }) + * ] + * }); + *``` + */ constructor(columns: Columns, options?: TableV2Options); + /** + * @deprecated This constructor will be removed in the next major release. + * Use the new constructor shown below instead. + * @example + * Use this instead + * ```javascript + * const table = new Table( + * { + * name: { type: ColumnType.TEXT }, + * age: { type: ColumnType.INTEGER } + * }, + * { indexes: { nameIndex: ['name'] } } + * ); + *``` + */ constructor(options: TableOptions); constructor(optionsOrColumns: Columns | TableOptions, v2Options?: TableV2Options) { if (this.isTableV1(optionsOrColumns)) { diff --git a/packages/common/src/db/schema/TableV2.ts b/packages/common/src/db/schema/TableV2.ts index c65fa5e2f..47e81c0f1 100644 --- a/packages/common/src/db/schema/TableV2.ts +++ b/packages/common/src/db/schema/TableV2.ts @@ -1,7 +1,9 @@ import { ColumnsType } from './Column'; import { Table } from './Table'; -/* +/** Generate a new table from the columns and indexes + @deprecated You should use {@link Table} instead as it now allows TableV2 syntax. + This will be removed in the next major release. */ export class TableV2 extends Table {} From 2a6a5b227c06a8c02cc93b689711ddfca4109438 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Wed, 28 Aug 2024 17:59:31 +0200 Subject: [PATCH 07/14] chore: use createTable: --- packages/common/src/db/schema/Schema.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/common/src/db/schema/Schema.ts b/packages/common/src/db/schema/Schema.ts index 6ab4ce54d..a8650cb40 100644 --- a/packages/common/src/db/schema/Schema.ts +++ b/packages/common/src/db/schema/Schema.ts @@ -41,14 +41,7 @@ export class Schema { private convertToClassicTables(props: S) { return Object.entries(props).map(([name, table]) => { - const convertedTable = new Table({ - name, - columns: table.columns, - indexes: table.indexes, - localOnly: table.localOnly, - insertOnly: table.insertOnly, - viewName: table.viewName - }); + const convertedTable = Table.createTable(name, table); return convertedTable; }); } From 9473cd635a62b26d8769ab36db8d9f5696fab2d5 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Wed, 28 Aug 2024 18:14:14 +0200 Subject: [PATCH 08/14] fix: getter issue --- packages/common/src/db/schema/Schema.ts | 9 ++++++++- packages/common/src/db/schema/Table.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/common/src/db/schema/Schema.ts b/packages/common/src/db/schema/Schema.ts index a8650cb40..6ab4ce54d 100644 --- a/packages/common/src/db/schema/Schema.ts +++ b/packages/common/src/db/schema/Schema.ts @@ -41,7 +41,14 @@ export class Schema { private convertToClassicTables(props: S) { return Object.entries(props).map(([name, table]) => { - const convertedTable = Table.createTable(name, table); + const convertedTable = new Table({ + name, + columns: table.columns, + indexes: table.indexes, + localOnly: table.localOnly, + insertOnly: table.insertOnly, + viewName: table.viewName + }); return convertedTable; }); } diff --git a/packages/common/src/db/schema/Table.ts b/packages/common/src/db/schema/Table.ts index 1ecea2930..5d9c33904 100644 --- a/packages/common/src/db/schema/Table.ts +++ b/packages/common/src/db/schema/Table.ts @@ -190,7 +190,7 @@ export class Table { } get viewName() { - return this.viewNameOverride ?? this.name; + return this.viewNameOverride ? this.viewNameOverride : this.name; } get columns() { From c2f56e4ca468646fa0c5985f961d4ce8ed128963 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Fri, 6 Sep 2024 12:56:16 +0200 Subject: [PATCH 09/14] chore: pr reverts --- demos/example-vite/src/index.js | 6 +++--- demos/example-webpack/src/index.js | 8 ++++---- packages/common/src/db/schema/Schema.ts | 2 +- packages/common/src/db/schema/Table.ts | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/demos/example-vite/src/index.js b/demos/example-vite/src/index.js index 3bb7fddc2..6e872cbbe 100644 --- a/demos/example-vite/src/index.js +++ b/demos/example-vite/src/index.js @@ -19,9 +19,9 @@ class DummyConnector { async uploadData(database) {} } -export const AppSchema = new Schema([ - new Table({ name: 'customers', columns: [new Column({ name: 'name', type: ColumnType.TEXT })] }) -]); +const customers = new Table({ name: column.text }) + +export const AppSchema = new Schema(customers); let PowerSync; diff --git a/demos/example-webpack/src/index.js b/demos/example-webpack/src/index.js index 3bb7fddc2..037f65ce4 100644 --- a/demos/example-webpack/src/index.js +++ b/demos/example-webpack/src/index.js @@ -1,4 +1,4 @@ -import { Column, ColumnType, Schema, Table, PowerSyncDatabase } from '@powersync/web'; +import { Column, ColumnType, Schema, Table, PowerSyncDatabase, column } from '@powersync/web'; import Logger from 'js-logger'; Logger.useDefaults(); @@ -19,9 +19,9 @@ class DummyConnector { async uploadData(database) {} } -export const AppSchema = new Schema([ - new Table({ name: 'customers', columns: [new Column({ name: 'name', type: ColumnType.TEXT })] }) -]); +const customers = new Table({ name: column.text }) + +export const AppSchema = new Schema(customers); let PowerSync; diff --git a/packages/common/src/db/schema/Schema.ts b/packages/common/src/db/schema/Schema.ts index 6ab4ce54d..42fc4f930 100644 --- a/packages/common/src/db/schema/Schema.ts +++ b/packages/common/src/db/schema/Schema.ts @@ -47,7 +47,7 @@ export class Schema { indexes: table.indexes, localOnly: table.localOnly, insertOnly: table.insertOnly, - viewName: table.viewName + viewName: table.viewNameOverride || name }); return convertedTable; }); diff --git a/packages/common/src/db/schema/Table.ts b/packages/common/src/db/schema/Table.ts index 5d9c33904..48b66b35e 100644 --- a/packages/common/src/db/schema/Table.ts +++ b/packages/common/src/db/schema/Table.ts @@ -92,8 +92,8 @@ export class Table { * ```javascript * const table = new Table( * { - * name: { type: ColumnType.TEXT }, - * age: { type: ColumnType.INTEGER } + * name: column.text, + * age: column.integer * }, * { indexes: { nameIndex: ['name'] } } * ); @@ -190,7 +190,7 @@ export class Table { } get viewName() { - return this.viewNameOverride ? this.viewNameOverride : this.name; + return this.viewNameOverride ?? this.name; } get columns() { From a3bed751051a43e640dc673a400493fbbbc66630 Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Fri, 6 Sep 2024 13:09:06 +0200 Subject: [PATCH 10/14] chore: pr reverts --- packages/common/src/db/schema/Table.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/db/schema/Table.ts b/packages/common/src/db/schema/Table.ts index 48b66b35e..9f2876cbd 100644 --- a/packages/common/src/db/schema/Table.ts +++ b/packages/common/src/db/schema/Table.ts @@ -239,7 +239,7 @@ export class Table { validate() { if (InvalidSQLCharacters.test(this.name)) { - throw new Error(`Invalid characters in table`); + throw new Error(`Invalid characters in table name: ${this.name}`); } if (this.viewNameOverride && InvalidSQLCharacters.test(this.viewNameOverride!)) { From 642a89cc6273c9ae43f2be9bc645448c1f8da17c Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Fri, 6 Sep 2024 14:40:02 +0200 Subject: [PATCH 11/14] chore: update types --- packages/common/src/db/schema/Table.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/src/db/schema/Table.ts b/packages/common/src/db/schema/Table.ts index 9f2876cbd..02b35112c 100644 --- a/packages/common/src/db/schema/Table.ts +++ b/packages/common/src/db/schema/Table.ts @@ -115,7 +115,7 @@ export class Table { constructor(columns: Columns, options?: TableV2Options); /** * @deprecated This constructor will be removed in the next major release. - * Use the new constructor shown below instead. + * Use the new constructor shown below instead as this does not show types. * @example * Use this instead * ```javascript From 0cecf1bdc66f2216e3eeb1d76e79dc6483be972c Mon Sep 17 00:00:00 2001 From: DominicGBauer Date: Fri, 6 Sep 2024 14:57:34 +0200 Subject: [PATCH 12/14] chore: pr revert --- packages/common/src/db/schema/Table.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/common/src/db/schema/Table.ts b/packages/common/src/db/schema/Table.ts index 02b35112c..191f17c42 100644 --- a/packages/common/src/db/schema/Table.ts +++ b/packages/common/src/db/schema/Table.ts @@ -121,8 +121,8 @@ export class Table { * ```javascript * const table = new Table( * { - * name: { type: ColumnType.TEXT }, - * age: { type: ColumnType.INTEGER } + * name: column.text, + * age: column.integer * }, * { indexes: { nameIndex: ['name'] } } * ); From 54b3e77e27c5dcdd3faa8a658da7ee7193fb45e4 Mon Sep 17 00:00:00 2001 From: Dominic Gunther Bauer <46312751+DominicGBauer@users.noreply.github.com> Date: Fri, 6 Sep 2024 16:03:26 +0200 Subject: [PATCH 13/14] Update demos/example-vite/src/index.js Co-authored-by: Ralf Kistner --- demos/example-vite/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/example-vite/src/index.js b/demos/example-vite/src/index.js index 6e872cbbe..b1aa743de 100644 --- a/demos/example-vite/src/index.js +++ b/demos/example-vite/src/index.js @@ -21,7 +21,7 @@ class DummyConnector { const customers = new Table({ name: column.text }) -export const AppSchema = new Schema(customers); +export const AppSchema = new Schema({ customers }); let PowerSync; From ad832202743c9b72be44df5fe3ee4157059ca9b8 Mon Sep 17 00:00:00 2001 From: Dominic Gunther Bauer <46312751+DominicGBauer@users.noreply.github.com> Date: Fri, 6 Sep 2024 16:03:31 +0200 Subject: [PATCH 14/14] Update demos/example-webpack/src/index.js Co-authored-by: Ralf Kistner --- demos/example-webpack/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/example-webpack/src/index.js b/demos/example-webpack/src/index.js index 037f65ce4..56a673d40 100644 --- a/demos/example-webpack/src/index.js +++ b/demos/example-webpack/src/index.js @@ -21,7 +21,7 @@ class DummyConnector { const customers = new Table({ name: column.text }) -export const AppSchema = new Schema(customers); +export const AppSchema = new Schema({ customers }); let PowerSync;