diff --git a/.changeset/clean-poems-dress.md b/.changeset/clean-poems-dress.md new file mode 100644 index 000000000..3474554d1 --- /dev/null +++ b/.changeset/clean-poems-dress.md @@ -0,0 +1,11 @@ +--- +'@powersync/attachments': major +'@powersync/react-native': major +'@powersync/common': major +'@powersync/web': major +'@powersync/capacitor': minor +'@powersync/node': minor +'@powersync/nuxt': minor +--- + +Remove the deprecated table v1 syntax. To create tables based on column arrays, use the new ResolvedTable class. diff --git a/demos/angular-supabase-todolist/src/app/powersync.service.ts b/demos/angular-supabase-todolist/src/app/powersync.service.ts index 31f5245d7..2c414c680 100644 --- a/demos/angular-supabase-todolist/src/app/powersync.service.ts +++ b/demos/angular-supabase-todolist/src/app/powersync.service.ts @@ -1,11 +1,8 @@ import { Injectable } from '@angular/core'; import { - AbstractPowerSyncDatabase, - Column, - ColumnType, + column, + CommonPowerSyncDatabase, createConsoleLogger, - Index, - IndexedColumn, LogLevels, PowerSyncBackendConnector, PowerSyncDatabase, @@ -36,35 +33,31 @@ export interface TodoRecord { export const LISTS_TABLE = 'lists'; export const TODOS_TABLE = 'todos'; -export const AppSchema = new Schema([ - new Table({ - name: TODOS_TABLE, - columns: [ - new Column({ name: 'list_id', type: ColumnType.TEXT }), - new Column({ name: 'created_at', type: ColumnType.TEXT }), - new Column({ name: 'completed_at', type: ColumnType.TEXT }), - new Column({ name: 'description', type: ColumnType.TEXT }), - new Column({ name: 'completed', type: ColumnType.INTEGER }), - new Column({ name: 'created_by', type: ColumnType.TEXT }), - new Column({ name: 'completed_by', type: ColumnType.TEXT }) - ], - indexes: [new Index({ name: 'list', columns: [new IndexedColumn({ name: 'list_id' })] })] - }), - new Table({ - name: LISTS_TABLE, - columns: [ - new Column({ name: 'created_at', type: ColumnType.TEXT }), - new Column({ name: 'name', type: ColumnType.TEXT }), - new Column({ name: 'owner_id', type: ColumnType.TEXT }) - ] +export const AppSchema = new Schema({ + [TODOS_TABLE]: new Table( + { + list_id: column.text, + created_at: column.text, + completed_at: column.text, + description: column.text, + completed: column.integer, + created_by: column.text, + completed_by: column.text + }, + { indexes: { list: ['list_id'] } } + ), + [LISTS_TABLE]: new Table({ + created_at: column.text, + name: column.text, + owner_id: column.text }) -]); +}); @Injectable({ providedIn: 'root' }) export class PowerSyncService { - db: AbstractPowerSyncDatabase; + db: CommonPowerSyncDatabase; constructor() { const factory = new WASQLiteOpenFactory({ diff --git a/demos/react-multi-client/src/definitions/Schema.ts b/demos/react-multi-client/src/definitions/Schema.ts index 748a05015..45b385f0b 100644 --- a/demos/react-multi-client/src/definitions/Schema.ts +++ b/demos/react-multi-client/src/definitions/Schema.ts @@ -1,4 +1,4 @@ -import { Column, ColumnType, Schema, Table } from '@powersync/web'; +import { column, Schema, Table } from '@powersync/web'; export const TABLE_NAME = 'pebbles'; export const MAX_PEBBLES = 5; @@ -19,29 +19,24 @@ export interface PebbleDef { user_id: string; } -export const AppSchema = new Schema([ - new Table({ - name: TABLE_NAME, - columns: [ - new Column({ name: 'shape', type: ColumnType.TEXT }), - new Column({ name: 'created_at', type: ColumnType.TEXT }), - new Column({ name: 'user_id', type: ColumnType.TEXT }) - ] +export const AppSchema = new Schema({ + [TABLE_NAME]: new Table({ + shape: column.text, + created_at: column.text, + user_id: column.text }), - new Table({ - name: 'operations', - columns: [ - new Column({ name: 'operation', type: ColumnType.TEXT }), - new Column({ name: 'created_at', type: ColumnType.TEXT }), - new Column({ name: 'user_id', type: ColumnType.TEXT }) - ] + operations: new Table({ + operation: column.text, + created_at: column.text, + user_id: column.text }), - new Table({ - name: 'settings', - localOnly: true, - columns: [new Column({ name: 'initialized', type: ColumnType.INTEGER })] - }) -]); + settings: new Table( + { + initialized: column.integer + }, + { localOnly: true } + ) +}); export function randomPebbleShape(): Shape { const colors = Object.values(Shape); diff --git a/packages/attachments/src/Schema.ts b/packages/attachments/src/Schema.ts index 93811c1f4..90b087a74 100644 --- a/packages/attachments/src/Schema.ts +++ b/packages/attachments/src/Schema.ts @@ -1,4 +1,4 @@ -import { Column, ColumnType, Table, TableOptions } from '@powersync/common'; +import { ColumnsType, Table, column, TableOptions } from '@powersync/common'; export const ATTACHMENT_TABLE = 'attachments'; @@ -22,25 +22,29 @@ export enum AttachmentState { export interface AttachmentTableOptions extends Omit { name?: string; - additionalColumns?: Column[]; + additionalColumns?: ColumnsType; } export class AttachmentTable extends Table { constructor(options?: AttachmentTableOptions) { - super({ - ...options, - name: options?.name ?? ATTACHMENT_TABLE, - localOnly: true, - insertOnly: false, - columns: [ - new Column({ name: 'filename', type: ColumnType.TEXT }), - new Column({ name: 'local_uri', type: ColumnType.TEXT }), - new Column({ name: 'timestamp', type: ColumnType.INTEGER }), - new Column({ name: 'size', type: ColumnType.INTEGER }), - new Column({ name: 'media_type', type: ColumnType.TEXT }), - new Column({ name: 'state', type: ColumnType.INTEGER }), // Corresponds to AttachmentState - ...(options?.additionalColumns ?? []) - ] - }); + const { additionalColumns = {}, ...tableOptions } = options ?? {}; + + super( + { + filename: column.text, + local_uri: column.text, + timestamp: column.integer, + size: column.integer, + media_type: column.text, + state: column.integer, + ...additionalColumns + }, + { + name: ATTACHMENT_TABLE, + ...tableOptions, + localOnly: true, + insertOnly: false + } + ); } } diff --git a/packages/common/etc/common.api.md b/packages/common/etc/common.api.md index 2ff0b0486..eb0917114 100644 --- a/packages/common/etc/common.api.md +++ b/packages/common/etc/common.api.md @@ -160,7 +160,7 @@ export class AttachmentTable extends Table { } // @alpha (undocumented) -export interface AttachmentTableOptions extends Omit { +export interface AttachmentTableOptions extends Omit { } // @public (undocumented) @@ -606,7 +606,7 @@ export class Index { // (undocumented) protected options: IndexOptions; // (undocumented) - toJSON(table: Table): { + toJSON(table: ResolvedTable): { name: string; columns: { name: string; @@ -636,7 +636,7 @@ export class IndexedColumn { // (undocumented) protected options: IndexColumnOptions; // (undocumented) - toJSON(table: Table): { + toJSON(table: ResolvedTable): { name: string; ascending: boolean | undefined; type: ColumnType; @@ -652,7 +652,7 @@ export interface IndexOptions { } // @public (undocumented) -export type IndexShorthand = Record; +export type IndexShorthand = Record; // Warning: (ae-internal-missing-underscore) The name "isBatchedUpdateNotification" should be prefixed with an underscore because the declaration is marked as @internal // @@ -828,12 +828,79 @@ export interface RemoteStorageAdapter { uploadFile(fileData: ArrayBuffer, attachment: AttachmentRecord): Promise; } +// @public +export class ResolvedTable { + constructor(options: ResolvedTableOptions); + // (undocumented) + get columns(): Column[]; + // (undocumented) + get ignoreEmptyUpdates(): boolean; + // (undocumented) + get indexes(): Index[]; + // (undocumented) + get insertOnly(): boolean; + // (undocumented) + get internalName(): string; + // (undocumented) + get localOnly(): boolean; + // (undocumented) + get name(): string; + // (undocumented) + readonly options: ResolvedTableOptions; + // (undocumented) + toJSON(): { + local_only: boolean | undefined; + insert_only: boolean | undefined; + include_old: any; + include_old_only_when_changed: boolean; + include_metadata: boolean | undefined; + ignore_empty_update: boolean | undefined; + name: string; + view_name: string; + columns: { + name: string; + type: ColumnType | undefined; + }[]; + indexes: { + name: string; + columns: { + name: string; + ascending: boolean | undefined; + type: ColumnType; + }[]; + }[]; + }; + // (undocumented) + get trackMetadata(): boolean; + // (undocumented) + get trackPrevious(): boolean | TrackPreviousOptions; + // (undocumented) + validate(): void; + // (undocumented) + get validName(): boolean; + // (undocumented) + get viewName(): string; + // (undocumented) + get viewNameOverride(): string | undefined; +} + +// Warning: (ae-forgotten-export) The symbol "SharedTableOptions" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export interface ResolvedTableOptions extends SharedTableOptions { + // (undocumented) + columns: Column[]; + // (undocumented) + indexes?: Index[]; + name: string; +} + // @public (undocumented) -export type RowType> = { - [K in keyof T['columnMap']]: ExtractColumnValueType; +export type RowType> = T extends Table ? { + [K in keyof Columns]: ExtractColumnValueType; } & { id: string; -}; +} : never; // @public export enum RowUpdateType { @@ -855,7 +922,7 @@ export function sanitizeUUID(uuid: string): string; // // @public export class Schema { - constructor(tables: Table[] | S); + constructor(tables: ResolvedTable[] | S); // (undocumented) readonly props: S; // Warning: (ae-forgotten-export) The symbol "RawTable" needs to be exported by the entry point index.d.ts @@ -864,7 +931,7 @@ export class Schema { readonly rawTables: RawTable[]; static rawTableToJson(table: RawTable): unknown; // (undocumented) - readonly tables: Table[]; + readonly tables: ResolvedTable[]; // (undocumented) toJSON(): unknown; // (undocumented) @@ -1041,85 +1108,23 @@ export interface SyncSubscriptionDescription extends SyncStreamDescription { lastSyncedAt: Date | null; } -// @public (undocumented) -export class Table { - constructor(columns: Columns, options?: TableV2Options); - // @deprecated - constructor(options: TableOptions); - // (undocumented) - get columnMap(): Columns; - // (undocumented) - get columns(): Column[]; - // (undocumented) - copyWithName(name: string): Table; - // (undocumented) - static createInsertOnly(options: TableOptions): Table; - // (undocumented) - static createLocalOnly(options: TableOptions): Table; - // @deprecated - static createTable(name: string, table: Table): Table; - // (undocumented) - get ignoreEmptyUpdates(): boolean; - // (undocumented) - get indexes(): Index[]; +// @public +export class Table extends ResolvedTable { + constructor(columns: Columns, options?: TableOptions); // (undocumented) - get insertOnly(): boolean; + copyWithName(name: string): ResolvedTable; // (undocumented) - get internalName(): string; + static createInsertOnly(columns: Columns, options?: TableOptions): Table; // (undocumented) - get localOnly(): boolean; + static createLocalOnly(columns: Columns, options?: TableOptions): Table; // (undocumented) protected _mappedColumns: Columns; - // (undocumented) - get name(): string; - // (undocumented) - protected options: TableOptions; - // (undocumented) - toJSON(): { - local_only: boolean | undefined; - insert_only: boolean | undefined; - include_old: any; - include_old_only_when_changed: boolean; - include_metadata: boolean | undefined; - ignore_empty_update: boolean | undefined; - name: string; - view_name: string; - columns: { - name: string; - type: ColumnType | undefined; - }[]; - indexes: { - name: string; - columns: { - name: string; - ascending: boolean | undefined; - type: ColumnType; - }[]; - }[]; - }; - // (undocumented) - get trackMetadata(): boolean; - // (undocumented) - get trackPrevious(): boolean | TrackPreviousOptions; - // (undocumented) - validate(): void; - // (undocumented) - get validName(): boolean; - // (undocumented) - get viewName(): string; - // (undocumented) - get viewNameOverride(): string | undefined; } -// Warning: (ae-forgotten-export) The symbol "SharedTableOptions" needs to be exported by the entry point index.d.ts -// // @public (undocumented) export interface TableOptions extends SharedTableOptions { // (undocumented) - columns: Column[]; - // (undocumented) - indexes?: Index[]; - name: string; + indexes?: IndexShorthand; } // @public @@ -1144,16 +1149,6 @@ export interface TableUpdateOperation { rowId: number; } -// @public @deprecated -export class TableV2 extends Table { -} - -// @public (undocumented) -export interface TableV2Options extends SharedTableOptions { - // (undocumented) - indexes?: IndexShorthand; -} - // @alpha export interface TrackDiffOptions extends BaseCreateDiffTriggerOptions { onChange: (context: TriggerDiffHandlerContext) => Promise; diff --git a/packages/common/src/attachments/Schema.ts b/packages/common/src/attachments/Schema.ts index 1714e1d75..82cc54277 100644 --- a/packages/common/src/attachments/Schema.ts +++ b/packages/common/src/attachments/Schema.ts @@ -1,6 +1,6 @@ import { column } from '../db/schema/Column.js'; import { Table } from '../db/schema/Table.js'; -import { TableV2Options } from '../db/schema/Table.js'; +import { TableOptions } from '../db/schema/Table.js'; /** * The default name of the local table storing attachment data. @@ -64,7 +64,7 @@ export enum AttachmentState { /** * @alpha */ -export interface AttachmentTableOptions extends Omit {} +export interface AttachmentTableOptions extends Omit {} /** * AttachmentTable defines the schema for the attachment queue table. diff --git a/packages/common/src/db/schema/Index.ts b/packages/common/src/db/schema/Index.ts index 31d23230a..e234eac27 100644 --- a/packages/common/src/db/schema/Index.ts +++ b/packages/common/src/db/schema/Index.ts @@ -1,5 +1,5 @@ import { IndexedColumn } from './IndexedColumn.js'; -import { Table } from './Table.js'; +import { ResolvedTable } from './Table.js'; /** * @public @@ -36,7 +36,7 @@ export class Index { return this.options.columns ?? []; } - toJSON(table: Table) { + toJSON(table: ResolvedTable) { return { name: this.name, columns: this.columns.map((c) => c.toJSON(table)) diff --git a/packages/common/src/db/schema/IndexedColumn.ts b/packages/common/src/db/schema/IndexedColumn.ts index baaa64b52..f0c74c0e8 100644 --- a/packages/common/src/db/schema/IndexedColumn.ts +++ b/packages/common/src/db/schema/IndexedColumn.ts @@ -1,5 +1,5 @@ import { ColumnType } from './Column.js'; -import { Table } from './Table.js'; +import { ResolvedTable } from './Table.js'; /** * @public @@ -38,7 +38,7 @@ export class IndexedColumn { return this.options.ascending; } - toJSON(table: Table) { + toJSON(table: ResolvedTable) { return { name: this.name, ascending: this.ascending, diff --git a/packages/common/src/db/schema/Schema.ts b/packages/common/src/db/schema/Schema.ts index c90c79889..9e173d24d 100644 --- a/packages/common/src/db/schema/Schema.ts +++ b/packages/common/src/db/schema/Schema.ts @@ -1,6 +1,6 @@ import { encodeTableOptions } from './internal.js'; import { RawTable, RawTableType } from './RawTable.js'; -import { RowType, Table } from './Table.js'; +import { ResolvedTable, RowType, Table } from './Table.js'; type SchemaType = Record>; @@ -22,10 +22,10 @@ export class Schema { */ readonly types!: SchemaTableType; readonly props!: S; - readonly tables: Table[]; + readonly tables: ResolvedTable[]; readonly rawTables: RawTable[]; - constructor(tables: Table[] | S) { + constructor(tables: ResolvedTable[] | S) { if (Array.isArray(tables)) { /* We need to validate that the tables have a name here because a user could pass in an array diff --git a/packages/common/src/db/schema/Table.ts b/packages/common/src/db/schema/Table.ts index 3e2c911eb..8494eb1de 100644 --- a/packages/common/src/db/schema/Table.ts +++ b/packages/common/src/db/schema/Table.ts @@ -1,8 +1,7 @@ -import { BaseColumnType, Column, ColumnsType, ColumnType, ExtractColumnValueType } from './Column.js'; +import { Column, ColumnsType, ExtractColumnValueType } from './Column.js'; import { Index } from './Index.js'; import { IndexedColumn } from './IndexedColumn.js'; import { encodeTableOptions } from './internal.js'; -import { TableV2 } from './TableV2.js'; /** * powersync-sqlite-core limits the number of column per table to 1999, due to internal SQLite limits. @@ -44,7 +43,7 @@ export interface TrackPreviousOptions { /** * @public */ -export interface TableOptions extends SharedTableOptions { +export interface ResolvedTableOptions extends SharedTableOptions { /** * The synced table name, matching sync rules */ @@ -56,22 +55,20 @@ export interface TableOptions extends SharedTableOptions { /** * @public */ -export type RowType> = { - [K in keyof T['columnMap']]: ExtractColumnValueType; -} & { - id: string; -}; +export type RowType> = + T extends Table + ? { [K in keyof Columns]: ExtractColumnValueType } & { id: string } + : never; /** * @public */ -export type IndexShorthand = Record; +export type IndexShorthand = Record; /** * @public */ - -export interface TableV2Options extends SharedTableOptions { +export interface TableOptions extends SharedTableOptions { indexes?: IndexShorthand; } @@ -87,161 +84,17 @@ const DEFAULT_TABLE_OPTIONS = { const InvalidSQLCharacters = /["'%,.#\s[\]]/; /** + * A resolved table in the PowerSync schema, with all columns, index definitions and options. + * + * When constructing tables for your schema, consider using {@link Table} instead. + * * @public */ -export class Table { - protected options!: TableOptions; - - protected _mappedColumns!: Columns; - - static createLocalOnly(options: TableOptions) { - return new Table({ ...options, localOnly: true, insertOnly: false }); - } - - static createInsertOnly(options: TableOptions) { - 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, - columns: table.columns, - indexes: table.indexes, - localOnly: table.options.localOnly, - insertOnly: table.options.insertOnly, - viewName: table.options.viewName - }); - } - - /** - * 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) - * - * @param columns - Either a Columns object (for V2 syntax) or a TableOptions object (for V1 syntax) - * @param options - Optional configuration options for V2 syntax - * - * @example - * ```javascript - * // New Constructor - * const table = new Table( - * { - * name: column.text, - * age: column.integer - * }, - * { indexes: { nameIndex: ['name'] } } - * ); - *``` - * - * - * @example - * ```javascript - * // Deprecated Constructor - * 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 as this does not show types. - * @example - * Use this instead - * ```javascript - * const table = new Table( - * { - * name: column.text, - * age: column.integer - * }, - * { indexes: { nameIndex: ['name'] } } - * ); - *``` - */ - constructor(options: TableOptions); - constructor(optionsOrColumns: Columns | TableOptions, v2Options?: TableV2Options) { - if (this.isTableV1(optionsOrColumns)) { - this.initTableV1(optionsOrColumns); - } else { - this.initTableV2(optionsOrColumns, v2Options); - } - } - - copyWithName(name: string): Table { - return new Table({ - ...this.options, - name - }); - } - - 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 || [] - }; +export class ResolvedTable { + constructor(readonly options: ResolvedTableOptions) { this.applyDefaultOptions(); } - private initTableV2(columns: Columns, options?: TableV2Options) { - const convertedColumns = Object.entries(columns).map( - ([name, columnInfo]) => new Column({ name, type: columnInfo.type }) - ); - - 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 = { - name: '', - columns: convertedColumns, - indexes: convertedIndexes, - viewName: options?.viewName, - insertOnly: options?.insertOnly, - localOnly: options?.localOnly, - trackPrevious: options?.trackPrevious, - trackMetadata: options?.trackMetadata, - ignoreEmptyUpdates: options?.ignoreEmptyUpdates - }; - this.applyDefaultOptions(); - - this._mappedColumns = columns; - } - - private applyDefaultOptions() { - this.options.insertOnly ??= DEFAULT_TABLE_OPTIONS.insertOnly; - this.options.localOnly ??= DEFAULT_TABLE_OPTIONS.localOnly; - this.options.trackPrevious ??= DEFAULT_TABLE_OPTIONS.trackPrevious; - this.options.trackMetadata ??= DEFAULT_TABLE_OPTIONS.trackMetadata; - this.options.ignoreEmptyUpdates ??= DEFAULT_TABLE_OPTIONS.ignoreEmptyUpdates; - } - get name() { return this.options.name; } @@ -258,16 +111,6 @@ 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 ?? []; } @@ -374,4 +217,93 @@ export class Table { ...encodeTableOptions(this) }; } + + private applyDefaultOptions() { + this.options.insertOnly ??= DEFAULT_TABLE_OPTIONS.insertOnly; + this.options.localOnly ??= DEFAULT_TABLE_OPTIONS.localOnly; + this.options.trackPrevious ??= DEFAULT_TABLE_OPTIONS.trackPrevious; + this.options.trackMetadata ??= DEFAULT_TABLE_OPTIONS.trackMetadata; + this.options.ignoreEmptyUpdates ??= DEFAULT_TABLE_OPTIONS.ignoreEmptyUpdates; + } +} + +/** + * A table with a statically-typed `Columns` record structure. + * + * This is the recommended way to declare tables in PowerSync schemas. + * + * @public + */ +export class Table extends ResolvedTable { + protected _mappedColumns!: Columns; + + static createLocalOnly(columns: Columns, options?: TableOptions) { + return new Table(columns, { localOnly: true, insertOnly: false, ...options }); + } + + static createInsertOnly(columns: Columns, options?: TableOptions) { + return new Table(columns, { localOnly: false, insertOnly: true, ...options }); + } + + /** + * Creates a new Table instance. + * + * @param columns - A Columns object (a record of column names to column types). + * @param options - Optional configuration options for the table. + * + * @example + * ```javascript + * const table = new Table( + * { + * name: column.text, + * age: column.integer + * }, + * { indexes: { nameIndex: ['name'] } } + * ); + *``` + */ + constructor(columns: Columns, options?: TableOptions) { + super(Table.optionsFromColumns(columns, options)); + this._mappedColumns = columns; + } + + copyWithName(name: string): ResolvedTable { + return new ResolvedTable({ ...this.options, name }); + } + + private static optionsFromColumns( + columns: Columns, + options?: TableOptions + ): ResolvedTableOptions { + const convertedColumns = Object.entries(columns).map( + ([name, columnInfo]) => new Column({ name, type: columnInfo.type }) + ); + + const convertedIndexes = Object.entries(options?.indexes ?? {}).map( + ([name, columnNames]) => + new Index({ + name, + columns: columnNames.map((nameOrIndexedColumn) => + typeof nameOrIndexedColumn === 'string' + ? new IndexedColumn({ + name: nameOrIndexedColumn.replace(/^-/, ''), + ascending: !nameOrIndexedColumn.startsWith('-') + }) + : nameOrIndexedColumn + ) + }) + ); + + return { + name: '', + columns: convertedColumns, + indexes: convertedIndexes, + viewName: options?.viewName, + insertOnly: options?.insertOnly, + localOnly: options?.localOnly, + trackPrevious: options?.trackPrevious, + trackMetadata: options?.trackMetadata, + ignoreEmptyUpdates: options?.ignoreEmptyUpdates + }; + } } diff --git a/packages/common/src/db/schema/TableV2.ts b/packages/common/src/db/schema/TableV2.ts deleted file mode 100644 index 6c456154b..000000000 --- a/packages/common/src/db/schema/TableV2.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ColumnsType } from './Column.js'; -import { Table } from './Table.js'; - -/** - 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. - - @public -*/ -export class TableV2 extends Table {} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 4df2cc690..90ab1e2ac 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -28,7 +28,6 @@ export * from './db/schema/IndexedColumn.js'; export { PendingStatement, PendingStatementParameter, RawTableType } from './db/schema/RawTable.js'; export * from './db/schema/Schema.js'; export * from './db/schema/Table.js'; -export * from './db/schema/TableV2.js'; export * from './client/Query.js'; export * from './client/triggers/sanitizeSQL.js'; diff --git a/packages/common/tests/db/schema/Schema.test.ts b/packages/common/tests/db/schema/Schema.test.ts index 1d19e2dff..bf8655f2e 100644 --- a/packages/common/tests/db/schema/Schema.test.ts +++ b/packages/common/tests/db/schema/Schema.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; import { Schema } from '../../../src/db/schema/Schema.js'; -import { Table } from '../../../src/db/schema/Table.js'; +import { ResolvedTable, Table } from '../../../src/db/schema/Table.js'; import { column, ColumnType, Column } from '../../../src/db/schema/Column.js'; describe('Schema', () => { @@ -10,12 +10,12 @@ describe('Schema', () => { expect(() => new Schema([table1, table2])).toThrow(); }); - it('should create a schema with an array of tables using the old syntax', () => { - const table1 = new Table({ + it('should create a schema with an array of tables using ResolvedTable', () => { + const table1 = new ResolvedTable({ name: 'table1', columns: [new Column({ name: 'name', type: ColumnType.TEXT })] }); - const table2 = new Table({ + const table2 = new ResolvedTable({ name: 'table2', columns: [new Column({ name: 'age', type: ColumnType.INTEGER })] }); diff --git a/packages/common/tests/db/schema/Table.test.ts b/packages/common/tests/db/schema/Table.test.ts index be5ecfa5d..37c2c5568 100644 --- a/packages/common/tests/db/schema/Table.test.ts +++ b/packages/common/tests/db/schema/Table.test.ts @@ -1,12 +1,12 @@ import { describe, it, expect } from 'vitest'; -import { Table, TableV2Options } from '../../../src/db/schema/Table.js'; +import { ResolvedTable, Table, TableOptions } from '../../../src/db/schema/Table.js'; import { column, Column, ColumnType } from '../../../src/db/schema/Column.js'; import { Index } from '../../../src/db/schema/Index.js'; import { IndexedColumn } from '../../../src/db/schema/IndexedColumn.js'; describe('Table', () => { - it('should create a table with V1 syntax', () => { - const table = new Table({ + it('can create ResolvedTable', () => { + const table = new ResolvedTable({ name: 'users', columns: [ new Column({ name: 'name', type: ColumnType.TEXT }), @@ -131,7 +131,7 @@ describe('Table', () => { }); it('should handle options', () => { - function createTable(options: TableV2Options) { + function createTable(options: TableOptions) { return new Table({ name: column.text }, options); } 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 908a867d2..000000000 --- a/packages/common/tests/db/schema/TableV2.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { describe, it, expect } from 'vitest'; -import { TableV2 } from '../../../src/db/schema/TableV2.js'; -import { column, Column, ColumnType } from '../../../src/db/schema/Column.js'; - -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, - ignore_empty_update: false, - include_metadata: false, - include_old: false, - include_old_only_when_changed: 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 1999 columns are provided', () => { - const columns = {}; - for (let i = 0; i < 2000; i++) { - columns[`column${i}`] = column.text; - } - - expect(() => new TableV2(columns).validate()).toThrowError( - 'Table has too many columns. The maximum number of columns is 1999.' - ); - }); - - 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'); - }); - }); -}); diff --git a/packages/drizzle-driver/src/utils/schema.ts b/packages/drizzle-driver/src/utils/schema.ts index 7a61dcefc..124bb6d9a 100644 --- a/packages/drizzle-driver/src/utils/schema.ts +++ b/packages/drizzle-driver/src/utils/schema.ts @@ -5,7 +5,7 @@ import { SchemaTableType, Table, type BaseColumnType, - type TableV2Options + type TableOptions } from '@powersync/common'; import { entityKind, InferSelectModel, isTable, Relations, type Casing } from 'drizzle-orm'; import { CasingCache } from 'drizzle-orm/casing'; @@ -31,7 +31,7 @@ export type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; export function toPowerSyncTable>( table: T, - options?: Omit & { casingCache?: CasingCache } + options?: Omit & { casingCache?: CasingCache } ): Table>> { const { columns: drizzleColumns, indexes: drizzleIndexes } = getTableConfig(table); const { casingCache } = options ?? {}; @@ -94,7 +94,7 @@ function mapDrizzleColumnToType(drizzleColumn: SQLiteColumn): BaseC } } -export type DrizzleTablePowerSyncOptions = Omit; +export type DrizzleTablePowerSyncOptions = Omit; export type DrizzleTableWithPowerSyncOptions = { tableDefinition: SQLiteTableWithColumns; diff --git a/packages/nuxt/src/runtime/utils/DynamicSchemaManager.ts b/packages/nuxt/src/runtime/utils/DynamicSchemaManager.ts index e4899191f..70dfd6b23 100644 --- a/packages/nuxt/src/runtime/utils/DynamicSchemaManager.ts +++ b/packages/nuxt/src/runtime/utils/DynamicSchemaManager.ts @@ -1,5 +1,5 @@ import type { DBAdapter } from '@powersync/web'; -import { Column, ColumnType, Schema, Table } from '@powersync/web'; +import { Column, ColumnType, ResolvedTable, Schema } from '@powersync/web'; import { DiagnosticsAppSchema as AppSchema } from './AppSchema'; import { JsSchemaGenerator } from './JsSchemaGenerator'; import type { SyncDataBucketJSON } from '@powersync/shared-internals/internal/sync_protocol'; @@ -81,7 +81,7 @@ export class DynamicSchemaManager { const tables = [...base.tables]; for (const [key, value] of Object.entries(this.tables)) { - const table = new Table({ + const table = new ResolvedTable({ name: key, columns: Object.entries(value).map( ([cname, ctype]) => diff --git a/packages/nuxt/src/runtime/utils/JsSchemaGenerator.ts b/packages/nuxt/src/runtime/utils/JsSchemaGenerator.ts index 99b1184ef..f47712613 100644 --- a/packages/nuxt/src/runtime/utils/JsSchemaGenerator.ts +++ b/packages/nuxt/src/runtime/utils/JsSchemaGenerator.ts @@ -1,4 +1,4 @@ -import type { Column, ColumnsType, Schema, Table } from '@powersync/web'; +import type { ResolvedTable, Column, Schema } from '@powersync/web'; export class JsSchemaGenerator { generate(schema: Schema): string { @@ -7,7 +7,7 @@ export class JsSchemaGenerator { return this.generateTables(tables) + '\n\n' + this.generateAppSchema(tables); } - private generateTables(tables: Table[]): string { + private generateTables(tables: ResolvedTable[]): string { return tables.map((table) => this.generateTable(table.name, table.columns)).join('\n\n'); } @@ -25,7 +25,7 @@ export class JsSchemaGenerator { return `${column.name}: column.${column.type!.toLowerCase()}`; } - private generateAppSchema(tables: Table[]): string { + private generateAppSchema(tables: ResolvedTable[]): string { return `export const AppSchema = new Schema({ ${tables.map((table) => table.name).join(',\n ')} }); diff --git a/packages/web/tests/crud.test.ts b/packages/web/tests/crud.test.ts index aae9dbb31..612dd36a4 100644 --- a/packages/web/tests/crud.test.ts +++ b/packages/web/tests/crud.test.ts @@ -1,4 +1,4 @@ -import { Column, ColumnType, Schema, Table, UpdateType } from '@powersync/common'; +import { column, Column, ColumnType, Schema, Table, UpdateType } from '@powersync/common'; import pDefer from 'p-defer'; import { v4 as uuid } from 'uuid'; import { describe, expect, it } from 'vitest'; @@ -177,16 +177,15 @@ describe('CRUD Tests', { sequential: true }, () => { dbFilename: 'test.db' + uuid(), enableMultiTabs: false }, - schema: new Schema([ - new Table({ - name: 'logs', - insertOnly: true, - columns: [ - new Column({ name: 'level', type: ColumnType.TEXT }), - new Column({ name: 'content', type: ColumnType.TEXT }) - ] - }) - ]) + schema: new Schema({ + logs: new Table( + { + level: column.text, + content: column.text + }, + { insertOnly: true } + ) + }) }); expect(await powersync.getAll('SELECT * FROM ps_crud')).empty; diff --git a/packages/web/tests/schema.test.ts b/packages/web/tests/schema.test.ts index 8e7cd1c8c..92546cebf 100644 --- a/packages/web/tests/schema.test.ts +++ b/packages/web/tests/schema.test.ts @@ -1,4 +1,4 @@ -import { CommonPowerSyncDatabase, Column, ColumnType, Index, IndexedColumn, Schema, Table } from '@powersync/common'; +import { CommonPowerSyncDatabase, IndexedColumn, Schema, Table, column } from '@powersync/common'; import { PowerSyncDatabase } from '@powersync/web'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; @@ -10,69 +10,61 @@ type SchemaVersionResult = { * Generates the Asset table with configurable options which * will be modified later. */ -const generateAssetsTable = (weightColumnName: string = 'weight', includeIndexes = true, indexAscending = true) => - new Table({ - name: 'assets', - columns: [ - new Column({ name: 'created_at', type: ColumnType.TEXT }), - new Column({ name: 'make', type: ColumnType.TEXT }), - new Column({ name: 'model', type: ColumnType.TEXT }), - new Column({ name: 'serial_number', type: ColumnType.TEXT }), - new Column({ name: 'quantity', type: ColumnType.INTEGER }), - new Column({ name: 'user_id', type: ColumnType.TEXT }), - new Column({ name: weightColumnName, type: ColumnType.REAL }), - new Column({ name: 'description', type: ColumnType.TEXT }) - ], - indexes: includeIndexes - ? [ - new Index({ - name: 'makemodel', - columns: [ - new IndexedColumn({ - name: 'make' - }), - new IndexedColumn({ name: 'model', ascending: indexAscending }) - ] - }) - ] - : [] - }); +const generateAssetsTable = (weightColumnName: string = 'weight', includeIndexes = true, indexAscending = true) => { + return new Table( + { + created_at: column.text, + make: column.text, + model: column.text, + serial_number: column.text, + quantity: column.integer, + user_id: column.text, + [weightColumnName]: column.real, + description: column.text + }, + { + indexes: includeIndexes + ? { + makemodel: ['make', new IndexedColumn({ name: 'model', ascending: indexAscending })] + } + : {} + } + ); +}; /** * Generates all the schema tables. * Allows for a custom assets table generator to be supplied. */ -const generateSchemaTables = (assetsTableGenerator: () => Table = generateAssetsTable) => [ - assetsTableGenerator(), - new Table({ - name: 'customers', - columns: [new Column({ name: 'name', type: ColumnType.TEXT }), new Column({ name: 'email', type: ColumnType.TEXT })] - }), - new Table({ - name: 'logs', - insertOnly: true, - columns: [ - new Column({ name: 'level', type: ColumnType.TEXT }), - new Column({ name: 'content', type: ColumnType.TEXT }) - ] - }), - - new Table({ - name: 'credentials', - localOnly: true, - columns: [new Column({ name: 'key', type: ColumnType.TEXT }), new Column({ name: 'value', type: ColumnType.TEXT })] - }), - new Table({ - name: 'aliased', - columns: [new Column({ name: 'name', type: ColumnType.TEXT })], - viewName: 'test1' - }) -]; +const generateSchema = (assetsTableGenerator: () => Table = generateAssetsTable) => { + return new Schema({ + assets: assetsTableGenerator(), + customers: new Table({ + name: column.text, + email: column.text + }), + logs: new Table( + { + level: column.text, + content: column.text + }, + { insertOnly: true } + ), + credentials: new Table( + { + key: column.text, + value: column.text + }, + { localOnly: true } + ), + aliased: new Table({ name: column.text }, { viewName: 'test1' }) + }); +}; /** * The default schema */ -const schema = new Schema(generateSchemaTables()); +const schema = generateSchema(); describe('Schema Tests', { sequential: true }, () => { let powersync: CommonPowerSyncDatabase; @@ -105,7 +97,7 @@ describe('Schema Tests', { sequential: true }, () => { expect(versionAfter['schema_version']).equals(versionBefore['schema_version']); // The `weight` columns is now `weights` - const schema2 = new Schema(generateSchemaTables(() => generateAssetsTable('weights'))); + const schema2 = generateSchema(() => generateAssetsTable('weights')); await powersync.updateSchema(schema2); @@ -115,7 +107,7 @@ describe('Schema Tests', { sequential: true }, () => { expect(versionAfter2['schema_version']).greaterThan(versionAfter['schema_version']); // The index is now descending - const schema3 = new Schema(generateSchemaTables(() => generateAssetsTable('weights', true, false))); + const schema3 = generateSchema(() => generateAssetsTable('weights', true, false)); await powersync.updateSchema(schema3); @@ -131,7 +123,7 @@ describe('Schema Tests', { sequential: true }, () => { expect(results.rows?._array?.[0]['detail']).contains('USING INDEX ps_data__assets__makemodel'); // Now drop the index - const schema2 = new Schema(generateSchemaTables(() => generateAssetsTable('weight', false))); + const schema2 = generateSchema(() => generateAssetsTable('weight', false)); await powersync.updateSchema(schema2); // Execute instead of getAll so that we don't get a cached query plan diff --git a/packages/web/tests/schemav2.test.ts b/packages/web/tests/schemav2.test.ts deleted file mode 100644 index 6915fc125..000000000 --- a/packages/web/tests/schemav2.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { CommonPowerSyncDatabase, Schema, TableV2, column } from '@powersync/common'; -import { PowerSyncDatabase } from '@powersync/web'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; - -type SchemaVersionResult = { - schema_version: number; -}; - -const assets = new TableV2( - { - created_at: column.text, - make: column.text, - model: column.text, - serial_number: column.text, - quantity: column.integer, - user_id: column.text, - weightColumnName: column.real, - description: column.text - }, - { - indexes: { makemodel: ['make', 'model'] } - } -); -const assetsNoIndex = new TableV2({ - created_at: column.text, - make: column.text, - model: column.text, - serial_number: column.text, - quantity: column.integer, - user_id: column.text, - weightColumnName: column.real, - description: column.text -}); -const customers = new TableV2({ - name: column.text, - email: column.text -}); -const logs = new TableV2( - { - level: column.text, - content: column.text - }, - { insertOnly: true } -); -const credentials = new TableV2( - { - key: column.text, - value: column.text - }, - { localOnly: true } -); -const aliased = new TableV2({ name: column.text }, { viewName: 'test1' }); - -/** - * The default schema - */ -const schema = new Schema({ assets, customers, logs, credentials, aliased }); - -describe('Schema Tests', { sequential: true }, () => { - let powersync: CommonPowerSyncDatabase; - - beforeEach(async () => { - powersync = new PowerSyncDatabase({ - /** - * Deleting the IndexDB seems to freeze the test. - * Use a new DB for each run to keep CRUD counters - * consistent - */ - database: { dbFilename: 'test.db', enableMultiTabs: false }, - schema - }); - }); - - afterEach(async () => { - await powersync.disconnectAndClear(); - await powersync.close(); - }); - - it('Schema versioning', async () => { - // Test that powersync_replace_schema() is a no-op when the schema is not - // modified. - const versionBefore = await powersync.get('PRAGMA schema_version'); - await powersync.updateSchema(schema); - const versionAfter = await powersync.get('PRAGMA schema_version'); - - // No change - expect(versionAfter['schema_version']).equals(versionBefore['schema_version']); - - // Remove a table - const schema2 = new Schema({ assets, customers, logs, credentials }); - - await powersync.updateSchema(schema2); - - const versionAfter2 = await powersync.get('PRAGMA schema_version'); - - // Updated - expect(versionAfter2['schema_version']).greaterThan(versionAfter['schema_version']); - }); - - it('Indexing', async () => { - const results = await powersync.execute('EXPLAIN QUERY PLAN SELECT * FROM assets WHERE make = ?', ['test']); - - expect(results.rows?._array?.[0]['detail']).contains('USING INDEX ps_data__assets__makemodel'); - - // Now drop the index - const schema2 = new Schema({ assetsNoIndex, customers, logs, credentials, aliased }); - await powersync.updateSchema(schema2); - - // Execute instead of getAll so that we don't get a cached query plan - // from a different connection - const results2 = await powersync.execute('EXPLAIN QUERY PLAN SELECT * FROM assetsNoIndex WHERE make = ?', ['test']); - - expect(results2.rows?._array?.[0]['detail']).contains('SCAN'); - }); - - it('Local Only', async () => { - const pscrudBeforeInsert = await powersync.getAll('SELECT * FROM ps_crud'); - expect(pscrudBeforeInsert.length).toEqual(0); - - await powersync.execute('INSERT INTO credentials (id, key, value) VALUES(uuid(),?,?)', ['test', 'test']); - - const pscrudAfterInsert = await powersync.getAll('SELECT * FROM ps_crud'); - expect(pscrudAfterInsert.length).toEqual(0); - }); - - it('Insert Only', async () => { - const pscrudBeforeInsert = await powersync.getAll('SELECT * FROM ps_crud'); - expect(pscrudBeforeInsert.length).toEqual(0); - const logsBeforeInsert = await powersync.getAll('SELECT * FROM logs'); - expect(logsBeforeInsert.length).toEqual(0); - - await powersync.execute('INSERT INTO logs (id, level, content) VALUES(uuid(),?,?)', ['test', 'test']); - - const pscrudAfterInsert = await powersync.getAll('SELECT * FROM ps_crud'); - expect(pscrudAfterInsert.length).toEqual(1); - const logsAfterInsert = await powersync.getAll('SELECT * FROM logs'); - expect(logsAfterInsert.length).toEqual(0); - }); - - it('ViewName', async () => { - const aliasedTable = await powersync.getAll('SELECT * FROM test1'); - expect(Array.isArray(aliasedTable)).toBe(true); - }); - - it('should have table names present in schema props', async () => { - // The table names aren't present when creating the Table instances. - // Passing Tables into a Schema should populate the table name based - // off the key of the prop - expect(schema.props.aliased.name).eq('aliased'); - expect(schema.props.aliased.internalName).eq('ps_data__aliased'); - expect(schema.props.aliased.viewName).eq('test1'); - - expect(schema.props.assets.name).eq('assets'); - expect(schema.props.assets.internalName).eq('ps_data__assets'); - expect(schema.props.assets.viewName).eq('assets'); - }); -}); diff --git a/packages/web/tests/utils/iframeInitializer.ts b/packages/web/tests/utils/iframeInitializer.ts index f5e522df0..7f476c491 100644 --- a/packages/web/tests/utils/iframeInitializer.ts +++ b/packages/web/tests/utils/iframeInitializer.ts @@ -3,7 +3,7 @@ import { LogLevels, Schema, SyncStreamConnectionMethod, - TableV2, + Table, column, createConsoleLogger } from '@powersync/common'; @@ -38,7 +38,7 @@ export async function setupPowerSyncInIframe( // Create a simple schema for testing const schema = new Schema({ - customers: new TableV2({ + customers: new Table({ name: column.text, email: column.text }) diff --git a/tools/diagnostics-app/src/library/powersync/DynamicSchemaManager.ts b/tools/diagnostics-app/src/library/powersync/DynamicSchemaManager.ts index 22257b0b6..e6a0f7c25 100644 --- a/tools/diagnostics-app/src/library/powersync/DynamicSchemaManager.ts +++ b/tools/diagnostics-app/src/library/powersync/DynamicSchemaManager.ts @@ -1,4 +1,4 @@ -import { CommonPowerSyncDatabase, Column, ColumnType, Schema, Table } from '@powersync/web'; +import { CommonPowerSyncDatabase, Column, ColumnType, Schema, ResolvedTable } from '@powersync/web'; import type { SyncDataBucketJSON } from '@powersync/shared-internals/internal/sync_protocol'; import { AppSchema } from './AppSchema'; import { JsSchemaGenerator } from './JsSchemaGenerator'; @@ -123,7 +123,7 @@ export class DynamicSchemaManager { const tables = [...base.tables]; for (const [key, value] of Object.entries(this.tables)) { - const table = new Table({ + const table = new ResolvedTable({ name: key, columns: Object.entries(value).map( ([cname, ctype]) => diff --git a/tools/diagnostics-app/src/library/powersync/JsSchemaGenerator.ts b/tools/diagnostics-app/src/library/powersync/JsSchemaGenerator.ts index f71b5ef18..de1aba558 100644 --- a/tools/diagnostics-app/src/library/powersync/JsSchemaGenerator.ts +++ b/tools/diagnostics-app/src/library/powersync/JsSchemaGenerator.ts @@ -1,4 +1,4 @@ -import { Column, ColumnsType, Schema, Table } from '@powersync/web'; +import { ResolvedTable, Column, Schema } from '@powersync/web'; export class JsSchemaGenerator { generate(schema: Schema): string { @@ -7,7 +7,7 @@ export class JsSchemaGenerator { return this.generateTables(tables) + '\n\n' + this.generateAppSchema(tables); } - private generateTables(tables: Table[]): string { + private generateTables(tables: ResolvedTable[]): string { return tables.map((table) => this.generateTable(table.name, table.columns)).join('\n\n'); } @@ -26,7 +26,7 @@ export class JsSchemaGenerator { return `${column.name}: column.${column.type!.toLowerCase()}`; } - private generateAppSchema(tables: Table[]): string { + private generateAppSchema(tables: ResolvedTable[]): string { return `export const AppSchema = new Schema({ ${tables.map((table) => table.name).join(',\n ')} });