Skip to content

Commit 7d04f74

Browse files
[Feature] Added PowerSyncDatabase options validation (#274)
1 parent 4fc1de3 commit 7d04f74

File tree

4 files changed

+106
-4
lines changed

4 files changed

+106
-4
lines changed

.changeset/dull-dancers-judge.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/common': patch
3+
---
4+
5+
Added basic validations for required options in `PowerSyncDatabase` constructor.

packages/common/src/client/AbstractPowerSyncDatabase.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -182,21 +182,28 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
182182
constructor(options: PowerSyncDatabaseOptions); // Note this is important for extending this class and maintaining API compatibility
183183
constructor(protected options: PowerSyncDatabaseOptions) {
184184
super();
185-
const { database } = options;
185+
186+
const { database, schema } = options;
187+
188+
if (typeof schema?.toJSON != 'function') {
189+
throw new Error('The `schema` option should be provided and should be an instance of `Schema`.');
190+
}
186191

187192
if (isDBAdapter(database)) {
188193
this._database = database;
189194
} else if (isSQLOpenFactory(database)) {
190195
this._database = database.openDB();
191196
} else if (isPowerSyncDatabaseOptionsWithSettings(options)) {
192197
this._database = this.openDBAdapter(options);
198+
} else {
199+
throw new Error('The provided `database` option is invalid.');
193200
}
194201

195202
this.bucketStorageAdapter = this.generateBucketStorageAdapter();
196203
this.closed = false;
197204
this.currentStatus = new SyncStatus({});
198205
this.options = { ...DEFAULT_POWERSYNC_DB_OPTIONS, ...options };
199-
this._schema = options.schema;
206+
this._schema = schema;
200207
this.ready = false;
201208
this.sdkVersion = '';
202209
// Start async init

packages/common/src/client/SQLOpenFactory.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ export interface SQLOpenFactory {
3333
* Tests if the input is a {@link SQLOpenOptions}
3434
*/
3535
export const isSQLOpenOptions = (test: any): test is SQLOpenOptions => {
36-
return typeof test == 'object' && 'dbFilename' in test;
36+
// typeof null is `object`, but you cannot use the `in` operator on `null.
37+
return test && typeof test == 'object' && 'dbFilename' in test;
3738
};
3839

3940
/**

packages/web/tests/open.test.ts

+90-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AbstractPowerSyncDatabase } from '@powersync/common';
1+
import { AbstractPowerSyncDatabase, Schema } from '@powersync/common';
22
import {
33
PowerSyncDatabase,
44
WASQLiteDBAdapter,
@@ -127,4 +127,93 @@ describe('Open Methods', () => {
127127
expect(sharedSpy).toBeCalledTimes(0);
128128
expect(dedicatedSpy).toBeCalledTimes(0);
129129
});
130+
131+
/**
132+
* TypeScript should prevent this kind of error. This scenario could happen
133+
* in pure JavaScript with no language server checking types.
134+
*/
135+
it('Should throw if schema setting is not valid', async () => {
136+
const schemaError = 'The `schema` option should be provided';
137+
138+
expect(
139+
() =>
140+
new PowerSyncDatabase({
141+
database: { dbFilename: 'test.sqlite' },
142+
// @ts-expect-error
143+
schema: null
144+
})
145+
).throws(schemaError);
146+
147+
expect(
148+
() =>
149+
new PowerSyncDatabase({
150+
database: { dbFilename: 'test.sqlite' },
151+
// @ts-expect-error
152+
schema: {}
153+
})
154+
).throws(schemaError);
155+
156+
expect(
157+
() =>
158+
new PowerSyncDatabase({
159+
database: { dbFilename: 'test.sqlite' },
160+
// @ts-expect-error
161+
schema: 'schema'
162+
})
163+
).throws(schemaError);
164+
165+
expect(
166+
() =>
167+
new PowerSyncDatabase({
168+
database: { dbFilename: 'test.sqlite' },
169+
// @ts-expect-error
170+
schema: undefined
171+
})
172+
).throws(schemaError);
173+
174+
// An Extended class should be fine
175+
class ExtendedSchema extends Schema {}
176+
177+
const extendedClient = new PowerSyncDatabase({
178+
database: { dbFilename: 'test.sqlite' },
179+
schema: new ExtendedSchema([])
180+
});
181+
182+
await extendedClient.close();
183+
});
184+
185+
/**
186+
* TypeScript should prevent this kind of error. This scenario could happen
187+
* in pure JavaScript with no language server checking types.
188+
*/
189+
it('Should throw if database setting is not valid', async () => {
190+
const dbError = 'The provided `database` option is invalid.';
191+
192+
expect(
193+
() =>
194+
new PowerSyncDatabase({
195+
// @ts-expect-error
196+
database: null,
197+
schema: new Schema([])
198+
})
199+
).throws(dbError);
200+
201+
expect(
202+
() =>
203+
new PowerSyncDatabase({
204+
// @ts-expect-error
205+
database: {},
206+
schema: new Schema([])
207+
})
208+
).throws(dbError);
209+
210+
expect(
211+
() =>
212+
new PowerSyncDatabase({
213+
// @ts-expect-error
214+
database: 'db.sqlite',
215+
schema: new Schema([])
216+
})
217+
).throws(dbError);
218+
});
130219
});

0 commit comments

Comments
 (0)