Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3620769
poc: configurable vfs
stevensJourney Nov 25, 2024
a265ca5
fix tests
stevensJourney Nov 25, 2024
18dbd7f
wip: share db worker ports
stevensJourney Nov 27, 2024
e937099
wip: split db classes/interfaces
stevensJourney Nov 28, 2024
e06f3a9
wip: split worker db interfaces and instantiation
stevensJourney Nov 28, 2024
2ae92ba
share worker for OPFS
stevensJourney Nov 29, 2024
87b1f25
wip: test
stevensJourney Dec 2, 2024
a1d6d64
better proxy
stevensJourney Dec 2, 2024
ff93d0d
Add BroadcastChannel for sharing table change notifications
stevensJourney Dec 2, 2024
1f87766
cleanup
stevensJourney Dec 3, 2024
2b3b44d
imrove worker connections
stevensJourney Dec 3, 2024
446a93c
fix worker proxies
stevensJourney Dec 3, 2024
e2978e2
fix all tests
stevensJourney Dec 3, 2024
d197e12
Merge remote-tracking branch 'origin/main' into configurable-vfs
stevensJourney Dec 3, 2024
28ebe48
cleanup config sharing
stevensJourney Dec 3, 2024
db3e0e2
fix tests with config shared
stevensJourney Dec 3, 2024
a2b02df
added changeset
stevensJourney Dec 3, 2024
c2d0679
Added warning if OPFS config is invalid. Moved viewport meta tag to i…
Chriztiaan Dec 13, 2024
77543e3
Web database encryption (#439)
mugikhan Dec 17, 2024
319e403
Merge branch 'main' into configurable-vfs
Chriztiaan Jan 13, 2025
997505d
Updated Angular demo to use OPFS.
Chriztiaan Jan 13, 2025
611ce1e
Reverted demo changes used for testing.
Chriztiaan Jan 14, 2025
428686a
Merge branch 'main' into configurable-vfs
Chriztiaan Jan 14, 2025
61a460f
Fix broadcast notification infinite loop.
Chriztiaan Jan 27, 2025
c84a8e0
Ensuring dedicated worker is closed on client close call.
Chriztiaan Jan 27, 2025
f8cad95
Merge branch 'main' into configurable-vfs
Chriztiaan Jan 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/kind-suns-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@powersync/web': minor
---

Added support for OPFS virtual filesystem.
6 changes: 4 additions & 2 deletions demos/angular-supabase-todolist/src/app/powersync.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
PowerSyncDatabase,
Schema,
Table,
WASQLiteOpenFactory
WASQLiteOpenFactory,
WASQLiteVFS
} from '@powersync/web';

export interface ListRecord {
Expand Down Expand Up @@ -66,14 +67,15 @@ export class PowerSyncService {
constructor() {
const factory = new WASQLiteOpenFactory({
dbFilename: 'test.db',

vfs: WASQLiteVFS.OPFSCoopSyncVFS,
// Specify the path to the worker script
worker: 'assets/@powersync/worker/WASQLiteDB.umd.js'
});

this.db = new PowerSyncDatabase({
schema: AppSchema,
database: factory,

sync: {
// Specify the path to the worker script
worker: 'assets/@powersync/worker/SharedSyncImplementation.umd.js'
Expand Down
1 change: 1 addition & 0 deletions demos/react-supabase-todolist/src/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<html lang="en">
<head>
<meta name="theme-color" content="#c44eff" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="apple-touch-icon" href="/icons/icon.png" />
<link rel="stylesheet" href="./app/globals.css" />
<script type="module" src="./app/index.tsx"></script>
Expand Down
2 changes: 1 addition & 1 deletion demos/react-supabase-todolist/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default defineConfig({
// Don't optimize these packages as they contain web workers and WASM files.
// https://github.com/vitejs/vite/issues/11672#issuecomment-1415820673
exclude: ['@journeyapps/wa-sqlite', '@powersync/web'],
include: [],
include: []
// include: ['@powersync/web > js-logger'], // <-- Include `js-logger` when it isn't installed and imported.
},
plugins: [
Expand Down
33 changes: 33 additions & 0 deletions packages/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,39 @@ Install it in your app with:
npm install @journeyapps/wa-sqlite
```

### Encryption with Multiple Ciphers

To enable encryption you need to specify an encryption key when instantiating the PowerSync database.

> The PowerSync Web SDK uses the ChaCha20 cipher algorithm by [default](https://utelle.github.io/SQLite3MultipleCiphers/docs/ciphers/cipher_chacha20/).

```typescript
export const db = new PowerSyncDatabase({
// The schema you defined
schema: AppSchema,
database: {
// Filename for the SQLite database — it's important to only instantiate one instance per file.
dbFilename: 'example.db'
// Optional. Directory where the database file is located.'
// dbLocation: 'path/to/directory'
},
// Encryption key for the database.
encryptionKey: 'your-encryption-key'
});

// If you are using a custom WASQLiteOpenFactory or WASQLiteDBAdapter, you need specify the encryption key inside the construtor
export const db = new PowerSyncDatabase({
schema: AppSchema,
database: new WASQLiteOpenFactory({
//new WASQLiteDBAdapter
dbFilename: 'example.db',
vfs: WASQLiteVFS.OPFSCoopSyncVFS,
// Encryption key for the database.
encryptionKey: 'your-encryption-key'
})
});
```

## Webpack

See the [example Webpack config](https://github.com/powersync-ja/powersync-js/blob/main/demos/example-webpack/webpack.config.js) for details on polyfills and requirements.
Expand Down
2 changes: 1 addition & 1 deletion packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@powersync/common": "workspace:*",
"async-mutex": "^0.4.0",
"bson": "^6.6.0",
"comlink": "^4.4.1",
"comlink": "^4.4.2",
"commander": "^12.1.0",
"js-logger": "^1.6.1"
},
Expand Down
43 changes: 38 additions & 5 deletions packages/web/src/db/PowerSyncDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
AbstractPowerSyncDatabase,
DBAdapter,
DEFAULT_POWERSYNC_CLOSE_OPTIONS,
isDBAdapter,
isSQLOpenFactory,
PowerSyncDatabaseOptions,
PowerSyncDatabaseOptionsWithDBAdapter,
PowerSyncDatabaseOptionsWithOpenFactory,
Expand All @@ -14,21 +16,22 @@ import {
StreamingSyncImplementation
} from '@powersync/common';
import { Mutex } from 'async-mutex';
import { getNavigatorLocks } from '../shared/navigator';
import { WASQLiteOpenFactory } from './adapters/wa-sqlite/WASQLiteOpenFactory';
import {
DEFAULT_WEB_SQL_FLAGS,
ResolvedWebSQLOpenOptions,
resolveWebSQLFlags,
WebSQLFlags
} from './adapters/web-sql-flags';
import { WebDBAdapter } from './adapters/WebDBAdapter';
import { SharedWebStreamingSyncImplementation } from './sync/SharedWebStreamingSyncImplementation';
import { SSRStreamingSyncImplementation } from './sync/SSRWebStreamingSyncImplementation';
import { WebRemote } from './sync/WebRemote';
import {
WebStreamingSyncImplementation,
WebStreamingSyncImplementationOptions
} from './sync/WebStreamingSyncImplementation';
import { getNavigatorLocks } from '../shared/navigator';

export interface WebPowerSyncFlags extends WebSQLFlags {
/**
Expand All @@ -55,14 +58,24 @@ type WithWebSyncOptions<Base> = Base & {
sync?: WebSyncOptions;
};

export interface WebEncryptionOptions {
/**
* Encryption key for the database.
* If set, the database will be encrypted using Multiple Ciphers.
*/
encryptionKey?: string;
}

type WithWebEncryptionOptions<Base> = Base & WebEncryptionOptions;

export type WebPowerSyncDatabaseOptionsWithAdapter = WithWebSyncOptions<
WithWebFlags<PowerSyncDatabaseOptionsWithDBAdapter>
>;
export type WebPowerSyncDatabaseOptionsWithOpenFactory = WithWebSyncOptions<
WithWebFlags<PowerSyncDatabaseOptionsWithOpenFactory>
>;
export type WebPowerSyncDatabaseOptionsWithSettings = WithWebSyncOptions<
WithWebFlags<PowerSyncDatabaseOptionsWithSettings>
WithWebFlags<WithWebEncryptionOptions<PowerSyncDatabaseOptionsWithSettings>>
>;

export type WebPowerSyncDatabaseOptions = WithWebSyncOptions<WithWebFlags<PowerSyncDatabaseOptions>>;
Expand All @@ -72,14 +85,28 @@ export const DEFAULT_POWERSYNC_FLAGS: Required<WebPowerSyncFlags> = {
externallyUnload: false
};

export const resolveWebPowerSyncFlags = (flags?: WebPowerSyncFlags): WebPowerSyncFlags => {
export const resolveWebPowerSyncFlags = (flags?: WebPowerSyncFlags): Required<WebPowerSyncFlags> => {
return {
...DEFAULT_POWERSYNC_FLAGS,
...flags,
...resolveWebSQLFlags(flags)
};
};

/**
* Asserts that the database options are valid for custom database constructors.
*/
function assertValidDatabaseOptions(options: WebPowerSyncDatabaseOptions): void {
if ('database' in options && 'encryptionKey' in options) {
const { database } = options;
if (isSQLOpenFactory(database) || isDBAdapter(database)) {
throw new Error(
`Invalid configuration: 'encryptionKey' should only be included inside the database object when using a custom ${isSQLOpenFactory(database) ? 'WASQLiteOpenFactory' : 'WASQLiteDBAdapter'} constructor.`
);
}
}
}

/**
* A PowerSync database which provides SQLite functionality
* which is automatically synced.
Expand Down Expand Up @@ -107,6 +134,8 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
constructor(protected options: WebPowerSyncDatabaseOptions) {
super(options);

assertValidDatabaseOptions(options);

this.resolvedFlags = resolveWebPowerSyncFlags(options.flags);

if (this.resolvedFlags.enableMultiTabs && !this.resolvedFlags.externallyUnload) {
Expand All @@ -120,7 +149,8 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
protected openDBAdapter(options: WebPowerSyncDatabaseOptionsWithSettings): DBAdapter {
const defaultFactory = new WASQLiteOpenFactory({
...options.database,
flags: resolveWebPowerSyncFlags(options.flags)
flags: resolveWebPowerSyncFlags(options.flags),
encryptionKey: options.encryptionKey
});
return defaultFactory.openDB();
}
Expand Down Expand Up @@ -191,7 +221,10 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
const logger = this.options.logger;
logger ? logger.warn(warning) : console.warn(warning);
}
return new SharedWebStreamingSyncImplementation(syncOptions);
return new SharedWebStreamingSyncImplementation({
...syncOptions,
db: this.database as WebDBAdapter // This should always be the case
});
default:
return new WebStreamingSyncImplementation(syncOptions);
}
Expand Down
3 changes: 3 additions & 0 deletions packages/web/src/db/adapters/AbstractWebSQLOpenFactory.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { DBAdapter, SQLOpenFactory } from '@powersync/common';
import Logger, { ILogger } from 'js-logger';
import { SSRDBAdapter } from './SSRDBAdapter';
import { ResolvedWebSQLFlags, WebSQLOpenFactoryOptions, isServerSide, resolveWebSQLFlags } from './web-sql-flags';

export abstract class AbstractWebSQLOpenFactory implements SQLOpenFactory {
protected resolvedFlags: ResolvedWebSQLFlags;
protected logger: ILogger;

constructor(protected options: WebSQLOpenFactoryOptions) {
this.resolvedFlags = resolveWebSQLFlags(options.flags);
this.logger = options.logger ?? Logger.get(`AbstractWebSQLOpenFactory - ${this.options.dbFilename}`);
}

/**
Expand Down
31 changes: 31 additions & 0 deletions packages/web/src/db/adapters/AsyncDatabaseConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { BatchedUpdateNotification, QueryResult } from '@powersync/common';
import { ResolvedWebSQLOpenOptions } from './web-sql-flags';

/**
* Proxied query result does not contain a function for accessing row values
*/
export type ProxiedQueryResult = Omit<QueryResult, 'rows'> & {
rows: {
_array: any[];
length: number;
};
};
export type OnTableChangeCallback = (event: BatchedUpdateNotification) => void;

/**
* @internal
* An async Database connection which provides basic async SQL methods.
* This is usually a proxied through a web worker.
*/
export interface AsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> {
init(): Promise<void>;
close(): Promise<void>;
execute(sql: string, params?: any[]): Promise<ProxiedQueryResult>;
executeBatch(sql: string, params?: any[]): Promise<ProxiedQueryResult>;
registerOnTableChange(callback: OnTableChangeCallback): Promise<() => void>;
getConfig(): Promise<Config>;
}

export type OpenAsyncDatabaseConnection<Config extends ResolvedWebSQLOpenOptions = ResolvedWebSQLOpenOptions> = (
config: Config
) => AsyncDatabaseConnection;
Loading
Loading