Skip to content

Commit a1abb15

Browse files
feat: @powersync/adapter-sql-js added to support SQL.js (#647)
Co-authored-by: Christiaan Landman <chriz.ek@gmail.com>
1 parent c7d2b53 commit a1abb15

File tree

15 files changed

+1189
-71
lines changed

15 files changed

+1189
-71
lines changed

.changeset/orange-spies-cry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/adapter-sql-js': patch
3+
---
4+
5+
Introduced adapter-sql-js package.

.changeset/rude-pears-fail.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/common': minor
3+
---
4+
5+
Added ControlledExecutor utility to exports.

packages/adapter-sql-js/README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# PowerSync SQL-JS Adapter
2+
3+
A development package for PowerSync which uses [SQL.js](https://sql.js.org/#/) to provide a pure JavaScript SQLite implementation.
4+
This eliminates the need for native dependencies and enables seamless development with Expo Go and other JavaScript-only environments.
5+
6+
This adapter is specifically intended to streamline the development workflow and will be much slower than DB adapters that use native dependencies.
7+
Every write operation triggers a complete rewrite of the entire database file to persistent storage, not just the changed data.
8+
In addition to the perfomance overheads, this adapter doesn't provide any of the SQLite consistency guarantees - you may end up with missing data or a corrupted database file if the app is killed while writing to the database file.
9+
10+
For production use, when building React Native apps we recommend switching to our [react-native-quick-sqlite](https://www.npmjs.com/package/@journeyapps/react-native-quick-sqlite) or [OP-SQLite](https://www.npmjs.com/package/@powersync/op-sqlite) adapters when making production builds as they give substantially better performance.
11+
12+
## Note: Alpha Release
13+
14+
This package is currently in an alpha release.
15+
16+
## Usage
17+
18+
By default the SQLJS adapter will be in-memory. Read further for persister examples.
19+
20+
```tsx
21+
import { SQLJSOpenFactory } from '@powersync/adapter-sql-js';
22+
23+
const powersync = new PowerSyncDatabase({
24+
schema: AppSchema,
25+
database: new SQLJSOpenFactory({
26+
dbFilename: 'powersync.db'
27+
})
28+
});
29+
```
30+
31+
## Persister examples
32+
33+
### Expo
34+
35+
We can use the [Expo File System](https://docs.expo.dev/versions/latest/sdk/filesystem/) to persist the database in an Expo app.
36+
37+
```tsx
38+
import { PowerSyncDatabase, SQLJSOpenFactory, SQLJSPersister } from '@powersync/react-native';
39+
import * as FileSystem from 'expo-file-system';
40+
41+
const powersync = new PowerSyncDatabase({
42+
schema: AppSchema,
43+
database: new SQLJSOpenFactory({
44+
dbFilename: 'powersync.db',
45+
persister: createSQLJSPersister('powersync.db')
46+
})
47+
});
48+
49+
const createSQLJSPersister = (dbFilename: string): SQLJSPersister => {
50+
const dbPath = `${FileSystem.documentDirectory}${dbFilename}`;
51+
52+
return {
53+
readFile: async (): Promise<ArrayLike<number> | Buffer | null> => {
54+
try {
55+
const fileInfo = await FileSystem.getInfoAsync(dbPath);
56+
if (!fileInfo.exists) {
57+
return null;
58+
}
59+
60+
const result = await FileSystem.readAsStringAsync(dbPath, {
61+
encoding: FileSystem.EncodingType.Base64
62+
});
63+
64+
const binary = atob(result);
65+
const bytes = new Uint8Array(binary.length);
66+
for (let i = 0; i < binary.length; i++) {
67+
bytes[i] = binary.charCodeAt(i);
68+
}
69+
return bytes;
70+
} catch (error) {
71+
console.error('Error reading database file:', error);
72+
return null;
73+
}
74+
},
75+
76+
writeFile: async (data: ArrayLike<number> | Buffer): Promise<void> => {
77+
try {
78+
const uint8Array = new Uint8Array(data);
79+
const binary = Array.from(uint8Array, (byte) => String.fromCharCode(byte)).join('');
80+
const base64 = btoa(binary);
81+
82+
await FileSystem.writeAsStringAsync(dbPath, base64, {
83+
encoding: FileSystem.EncodingType.Base64
84+
});
85+
} catch (error) {
86+
console.error('Error writing database file:', error);
87+
throw error;
88+
}
89+
}
90+
};
91+
};
92+
```
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "@powersync/adapter-sql-js",
3+
"version": "0.0.0",
4+
"publishConfig": {
5+
"registry": "https://registry.npmjs.org/",
6+
"access": "public"
7+
},
8+
"description": "A development db adapter based on SQL.js for JourneyApps PowerSync",
9+
"type": "module",
10+
"main": "dist/bundle.mjs",
11+
"module": "dist/bundle.mjs",
12+
"types": "lib/index.d.ts",
13+
"author": "JOURNEYAPPS",
14+
"license": "Apache-2.0",
15+
"files": [
16+
"lib",
17+
"dist"
18+
],
19+
"repository": {
20+
"type": "git",
21+
"url": "git+https://github.com/powersync-ja/powersync-js.git"
22+
},
23+
"bugs": {
24+
"url": "https://github.com/powersync-ja/powersync-js/issues"
25+
},
26+
"homepage": "https://docs.powersync.com",
27+
"scripts": {
28+
"build": "tsc -b && rollup -c rollup.config.mjs",
29+
"build:prod": "tsc -b --sourceMap false && rollup -c rollup.config.mjs --sourceMap false",
30+
"clean": "rm -rf lib dist tsconfig.tsbuildinfo",
31+
"test": "vitest"
32+
},
33+
"dependencies": {
34+
"@powersync/common": "workspace:^",
35+
"async-mutex": "^0.4.0"
36+
},
37+
"devDependencies": {
38+
"@powersync/sql-js": "0.0.1",
39+
"@powersync/web": "workspace:*",
40+
"@rollup/plugin-alias": "^5.1.0",
41+
"@types/sql.js": "^1.4.9",
42+
"chance": "^1.1.9",
43+
"rollup": "4.14.3",
44+
"uuid": "^11.1.0"
45+
}
46+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import alias from '@rollup/plugin-alias';
2+
import commonjs from '@rollup/plugin-commonjs';
3+
import nodeResolve from '@rollup/plugin-node-resolve';
4+
import path from 'path';
5+
import { fileURLToPath } from 'url';
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
10+
/**
11+
* @returns {import('rollup').RollupOptions}
12+
*/
13+
export default (commandLineArgs) => {
14+
const sourceMap = (commandLineArgs.sourceMap || 'true') == 'true';
15+
16+
// Clears rollup CLI warning https://github.com/rollup/rollup/issues/2694
17+
delete commandLineArgs.sourceMap;
18+
19+
return {
20+
input: 'lib/index.js',
21+
output: {
22+
file: 'dist/bundle.mjs',
23+
format: 'esm',
24+
sourcemap: sourceMap
25+
},
26+
plugins: [
27+
nodeResolve({ preferBuiltins: false, browser: true }),
28+
commonjs({}),
29+
alias({
30+
entries: [
31+
// The default Emscripten output contains code like `require("fs")`. This seems
32+
// to be unreachable, but Metro complains when it detects it.
33+
{ find: 'fs', replacement: path.resolve(__dirname, 'vendored/empty.js') },
34+
{ find: 'path', replacement: path.resolve(__dirname, 'vendored/empty.js') },
35+
{ find: 'crypto', replacement: path.resolve(__dirname, 'vendored/empty.js') }
36+
]
37+
})
38+
],
39+
external: ['@powersync/common']
40+
};
41+
};

0 commit comments

Comments
 (0)