Skip to content

Commit f817140

Browse files
DominicGBauerDominicGBauerrkistner
authored
feat(common-sdk): add schema and table enhancements (#86)
Co-authored-by: DominicGBauer <dominic@nomanini.com> Co-authored-by: Ralf Kistner <ralf@journeyapps.com>
1 parent 613facd commit f817140

File tree

29 files changed

+403
-174
lines changed

29 files changed

+403
-174
lines changed

demos/angular-supabase-todolist/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"@angular/platform-browser-dynamic": "^17.0.4",
2121
"@angular/router": "^17.0.4",
2222
"@angular/service-worker": "^17.0.4",
23-
"@journeyapps/powersync-sdk-web": "^0.0.3",
23+
"@journeyapps/powersync-sdk-web": "workspace:*",
2424
"@journeyapps/wa-sqlite": "^0.0.2",
2525
"@supabase/supabase-js": "^2.38.5",
2626
"rxjs": "~7.8.1",
Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,35 @@
1-
import { Column, ColumnType, Index, IndexedColumn, Schema, Table } from '@journeyapps/powersync-sdk-web';
1+
import { column, Schema, TableV2 } from '@journeyapps/powersync-sdk-web';
22

3-
// TODO look into template app composition
3+
export const LISTS_TABLE = 'lists';
4+
export const TODOS_TABLE = 'todos';
45

5-
export interface ListRecord {
6-
id: string;
7-
name: string;
8-
created_at: string;
9-
owner_id?: string;
10-
}
6+
const todos = new TableV2(
7+
{
8+
list_id: column.text,
9+
created_at: column.text,
10+
completed_at: column.text,
11+
description: column.text,
12+
created_by: column.text,
13+
completed_by: column.text,
14+
completed: column.integer
15+
},
16+
{ indexes: { list: ['list_id'] } }
17+
);
1118

12-
export interface TodoRecord {
13-
id: string;
14-
created_at: string;
15-
completed: boolean;
16-
description: string;
17-
completed_at?: string;
19+
const lists = new TableV2({
20+
created_at: column.text,
21+
name: column.text,
22+
owner_id: column.text
23+
});
1824

19-
created_by: string;
20-
completed_by?: string;
21-
list_id: string;
22-
}
25+
export const AppSchema = new Schema({
26+
todos,
27+
lists
28+
});
2329

24-
export const LISTS_TABLE = 'lists';
25-
export const TODOS_TABLE = 'todos';
30+
export type Database = (typeof AppSchema)['types'];
31+
export type TodoRecord = Database['todos'];
32+
// OR:
33+
// export type Todo = RowType<typeof todos>;
2634

27-
export const AppSchema = new Schema([
28-
new Table({
29-
name: TODOS_TABLE,
30-
columns: [
31-
new Column({ name: 'list_id', type: ColumnType.TEXT }),
32-
new Column({ name: 'created_at', type: ColumnType.TEXT }),
33-
new Column({ name: 'completed_at', type: ColumnType.TEXT }),
34-
new Column({ name: 'description', type: ColumnType.TEXT }),
35-
new Column({ name: 'completed', type: ColumnType.INTEGER }),
36-
new Column({ name: 'created_by', type: ColumnType.TEXT }),
37-
new Column({ name: 'completed_by', type: ColumnType.TEXT })
38-
],
39-
indexes: [new Index({ name: 'list', columns: [new IndexedColumn({ name: 'list_id' })] })]
40-
}),
41-
new Table({
42-
name: LISTS_TABLE,
43-
columns: [
44-
new Column({ name: 'created_at', type: ColumnType.TEXT }),
45-
new Column({ name: 'name', type: ColumnType.TEXT }),
46-
new Column({ name: 'owner_id', type: ColumnType.TEXT })
47-
]
48-
})
49-
]);
35+
export type ListRecord = Database['lists'];
Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
1-
import { Column, ColumnType, Index, IndexedColumn, Schema, Table } from '@journeyapps/powersync-sdk-web';
1+
import { column, Schema, TableV2 } from '@journeyapps/powersync-sdk-web';
22

3-
export const AppSchema = new Schema([
4-
new Table({
5-
name: 'documents',
6-
columns: [
7-
new Column({ name: 'title', type: ColumnType.TEXT }),
8-
new Column({ name: 'created_at', type: ColumnType.TEXT })
9-
]
10-
}),
11-
new Table({
12-
name: 'document_updates',
13-
columns: [
14-
new Column({ name: 'created_at', type: ColumnType.TEXT }),
15-
new Column({ name: 'document_id', type: ColumnType.TEXT }),
16-
new Column({ name: 'update_b64', type: ColumnType.TEXT })
17-
],
18-
indexes: [new Index({ name: 'by_document', columns: [new IndexedColumn({ name: 'document_id' })] })]
19-
})
20-
]);
3+
const documents = new TableV2(
4+
{
5+
title: column.text,
6+
created_at: column.text
7+
},
8+
{ indexes: { list: ['list_id'] } }
9+
);
10+
11+
const document_updates = new TableV2(
12+
{
13+
document_id: column.text,
14+
created_at: column.text,
15+
update_b64: column.text
16+
},
17+
{ indexes: { by_document: ['document_id'] } }
18+
);
19+
20+
export const AppSchema = new Schema({
21+
documents,
22+
document_updates
23+
});
24+
25+
export type Database = (typeof AppSchema)['types'];
26+
export type Documents = Database['documents'];
27+
28+
export type DocumentUpdates = Database['document_updates'];

demos/yjs-nextjs-supabase-text-collab/src/library/powersync/SupabaseConnector.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
import {
1010
SupabaseClient,
1111
createClient,
12-
PostgrestError,
1312
FunctionsHttpError,
1413
FunctionsRelayError,
1514
FunctionsFetchError

packages/kysely-driver/tests/setup/db.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
1-
import {
2-
Column,
3-
ColumnType,
4-
Schema,
5-
Table,
6-
WASQLitePowerSyncDatabaseOpenFactory
7-
} from '@journeyapps/powersync-sdk-web';
1+
import { Schema, TableV2, WASQLitePowerSyncDatabaseOpenFactory, column } from '@journeyapps/powersync-sdk-web';
82
import { wrapPowerSyncWithKysely } from '../../src/sqlite/db';
93
import { Database } from './types';
104

11-
const TestSchema = new Schema([
12-
new Table({
13-
name: 'users',
14-
columns: [new Column({ name: 'name', type: ColumnType.TEXT })]
15-
})
16-
]);
5+
const users = new TableV2({
6+
name: column.text
7+
});
8+
9+
export const TestSchema = new Schema({ users });
1710

1811
export const getPowerSyncDb = () => {
1912
const factory = new WASQLitePowerSyncDatabaseOpenFactory({

packages/kysely-driver/tests/setup/types.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import { ColumnType, Insertable, Selectable, Updateable } from 'kysely';
1+
import { Insertable, Selectable, Updateable } from 'kysely';
2+
import { TestSchema } from './db';
23

3-
export interface Database {
4-
users: UsersTable;
5-
}
4+
export type Database = (typeof TestSchema)['types'];
65

7-
export interface UsersTable {
8-
id: ColumnType<string, string, never>;
9-
name: string;
10-
}
6+
export type UsersTable = Database['users'];
117

128
export type Users = Selectable<UsersTable>;
139
export type NewUsers = Insertable<UsersTable>;

packages/kysely-driver/tests/sqlite/db.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
22
import * as SUT from '../../src/sqlite/db';
33
import { Kysely } from 'kysely';
4-
import { Database } from '../setup/types';
54
import { getPowerSyncDb } from '../setup/db';
65
import { AbstractPowerSyncDatabase } from '@journeyapps/powersync-sdk-common';
6+
import { Database } from '../setup/types';
77

88
describe('CRUD operations', () => {
99
let powerSyncDb: AbstractPowerSyncDatabase;

packages/kysely-driver/tsconfig.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55
"declaration": true /* Generates corresponding '.d.ts' file. */,
66
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
77
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
8-
"lib": [
9-
"DOM",
10-
"ES2020",
11-
"WebWorker"
12-
] /* Specify library files to be included in the compilation. */,
8+
"lib": ["DOM", "ES2020", "WebWorker"] /* Specify library files to be included in the compilation. */,
139
"module": "es2020" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
1410
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
1511
"outDir": "./lib" /* Redirect output structure to the directory. */,
@@ -18,7 +14,10 @@
1814
"strict": true /* Enable all strict type-checking options. */,
1915
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
2016
},
21-
"include": [
22-
"src/**/*",
17+
"include": ["src/**/*"],
18+
"references": [
19+
{
20+
"path": "../powersync-sdk-common"
21+
}
2322
]
2423
}

packages/powersync-sdk-common/src/client/AbstractPowerSyncDatabase.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
114114
super();
115115
this.bucketStorageAdapter = this.generateBucketStorageAdapter();
116116
this.closed = true;
117-
this.currentStatus = null;
117+
this.currentStatus = undefined;
118118
this.options = { ...DEFAULT_POWERSYNC_DB_OPTIONS, ...options };
119119
this._schema = options.schema;
120120
this.ready = false;
@@ -201,7 +201,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
201201
try {
202202
schema.validate();
203203
} catch (ex) {
204-
this.options.logger.warn('Schema validation failed. Unexpected behaviour could occur', ex);
204+
this.options.logger?.warn('Schema validation failed. Unexpected behaviour could occur', ex);
205205
}
206206
this._schema = schema;
207207
await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
@@ -291,7 +291,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
291291
[tableGlob]
292292
);
293293

294-
if (!existingTableRows.rows.length) {
294+
if (!existingTableRows.rows?.length) {
295295
return;
296296
}
297297
for (const row of existingTableRows.rows._array) {
@@ -325,11 +325,11 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
325325
`SELECT SUM(cast(data as blob) + 20) as size, count(*) as count FROM ${PSInternalTable.CRUD}`
326326
);
327327

328-
const row = result.rows.item(0);
328+
const row = result.rows!.item(0);
329329
return new UploadQueueStats(row?.count ?? 0, row?.size ?? 0);
330330
} else {
331331
const result = await tx.execute(`SELECT count(*) as count FROM ${PSInternalTable.CRUD}`);
332-
const row = result.rows.item(0);
332+
const row = result.rows!.item(0);
333333
return new UploadQueueStats(row?.count ?? 0);
334334
}
335335
});
@@ -388,7 +388,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
388388
* Unlike {@link getCrudBatch}, this only returns data from a single transaction at a time.
389389
* All data for the transaction is loaded into memory.
390390
*/
391-
async getNextCrudTransaction(): Promise<CrudTransaction> {
391+
async getNextCrudTransaction(): Promise<CrudTransaction | null> {
392392
return await this.readTransaction(async (tx) => {
393393
const first = await tx.getOptional<CrudEntryJSON>(
394394
`SELECT id, tx_id, data FROM ${PSInternalTable.CRUD} ORDER BY id ASC LIMIT 1`
@@ -543,7 +543,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
543543

544544
const resolvedTables = options?.tables ?? [];
545545
if (!options?.tables) {
546-
const explained = await this.getAll(`EXPLAIN ${sql}`, parameters);
546+
const explained = await this.getAll<{ opcode: string; p3: number; p2: number }>(`EXPLAIN ${sql}`, parameters);
547547
const rootPages = _.chain(explained)
548548
.filter((row) => row['opcode'] == 'OpenRead' && row['p3'] == 0 && _.isNumber(row['p2']))
549549
.map((row) => row['p2'])
@@ -571,10 +571,11 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
571571
* Note, do not declare this as `async *onChange` as it will not work in React Native
572572
*/
573573
onChange(options?: SQLWatchOptions): AsyncIterable<WatchOnChangeEvent> {
574-
const watchedTables = options.tables ?? [];
574+
const resolvedOptions = options ?? {};
575+
const watchedTables = resolvedOptions.tables ?? [];
575576

576577
let throttledTableUpdates: string[] = [];
577-
const throttleMs = options.throttleMs ?? DEFAULT_WATCH_THROTTLE_MS;
578+
const throttleMs = resolvedOptions.throttleMs ?? DEFAULT_WATCH_THROTTLE_MS;
578579

579580
return new EventIterator<WatchOnChangeEvent>((eventOptions) => {
580581
const flushTableUpdates = _.throttle(
@@ -593,7 +594,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
593594

594595
const dispose = this.database.registerListener({
595596
tablesUpdated: async (update) => {
596-
const { rawTableNames } = options;
597+
const { rawTableNames } = resolvedOptions;
597598

598599
const tables = isBatchedUpdateNotification(update) ? update.tables : [update.table];
599600

@@ -613,7 +614,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
613614
}
614615
});
615616

616-
options.signal?.addEventListener('abort', () => {
617+
resolvedOptions.signal?.addEventListener('abort', () => {
617618
dispose();
618619
eventOptions.stop();
619620
// Maybe fail?
@@ -626,7 +627,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
626627
/**
627628
* @ignore
628629
*/
629-
private async executeReadOnly(sql: string, params: any[]) {
630+
private async executeReadOnly(sql: string, params?: any[]) {
630631
await this.waitForReady();
631632
return this.database.readLock((tx) => tx.execute(sql, params));
632633
}

packages/powersync-sdk-common/src/client/AbstractPowerSyncOpenFactory.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ export abstract class AbstractPowerSyncDatabaseOpenFactory {
3333
generateOptions(): PowerSyncDatabaseOptions {
3434
return {
3535
database: this.openDB(),
36-
schema: this.schema,
3736
...this.options
3837
};
3938
}

0 commit comments

Comments
 (0)