Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3990622
build(web): add @sqlite.org/sqlite-wasm and the vitest browser toolchain
kklem0 Aug 1, 2026
ab7752f
feat(web): replace the jeep-sqlite shim with an in-repo sqlite-wasm e…
kklem0 Aug 1, 2026
a3c9f8c
test(web): cover the core method set on both durability tiers
kklem0 Aug 1, 2026
267e773
fix(web): namespace the tier-2 image store by pool name
kklem0 Aug 1, 2026
eede810
build(web): add fflate for zip asset support
kklem0 Aug 1, 2026
f94a13f
feat(web): port the JSON import and export pipeline
kklem0 Aug 1, 2026
a479177
feat(web): port sync tables and the sql_deleted soft delete
kklem0 Aug 1, 2026
828ea93
feat(web): port copyFromAssets, getFromHTTPRequest and the local disk…
kklem0 Aug 1, 2026
9604f40
test(web): cover the JSON, sync, asset and local disk ports
kklem0 Aug 1, 2026
4008d3e
ci: install Playwright Chromium before the web verify step
kklem0 Aug 1, 2026
08c9128
feat(web): migrate databases out of the jeep-sqlite store on first boot
kklem0 Aug 1, 2026
6113810
test(web): cover the jeep-sqlite migration with a real jeep fixture
kklem0 Aug 1, 2026
4218f4b
build(web): drop the jeep-sqlite dependency and the stale bundler ext…
kklem0 Aug 1, 2026
a9edab8
docs(web): replace the jeep-sqlite setup with the worker and wasm story
kklem0 Aug 1, 2026
ddd071d
docs: add the 8.2.0 web engine release notes
kklem0 Aug 1, 2026
b78ef16
fix(web)!: enable foreign key enforcement on every connection
kklem0 Aug 1, 2026
44f810b
fix(web): repair the soft-delete rewrite for quotes, literals, OR and…
kklem0 Aug 1, 2026
d35fe8c
feat(web): propagate a soft delete along foreign keys
kklem0 Aug 1, 2026
5b551e2
feat(web): move IndexedDB databases into OPFS when the browser gains it
kklem0 Aug 1, 2026
d319e51
feat(web): follow the app in and out of the background, with a worker…
kklem0 Aug 1, 2026
45f4535
fix(web): name the real cause when the worker fails to load
kklem0 Aug 1, 2026
2504c1e
fix(web): keep the caller's connection open across importFromJson ove…
kklem0 Aug 1, 2026
8ab9831
docs(web): document the cascade, tier promotion and backgrounding
kklem0 Aug 1, 2026
59293be
docs(web): state the tier 2 conditions, market share, and why the tie…
kklem0 Aug 1, 2026
d60950c
fix(web): serialise pauseWebStore against resumeWebStore
kklem0 Aug 1, 2026
08141bb
fix(web): read a worker that answers with HTML as a bundler failure
kklem0 Aug 1, 2026
5149665
docs(web): correct the bundler story for serving the worker
kklem0 Aug 1, 2026
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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
run: npm run verify:ios
- name: Build Android
run: npm run verify:android
- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium
- name: Build Web
run: npm run verify:web
- name: Build Electron
Expand Down
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,46 @@

All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.

## [8.2.0](https://github.com/capacitor-community/sqlite/compare/v8.1.0...v8.2.0) (2026-08-01)


### ⚠ BREAKING CHANGES

* **web:** the web implementation no longer uses `jeep-sqlite`. Remove the `jeep-sqlite` dependency, the `defineCustomElements`/`applyPolyfills` import, the `<jeep-sqlite>` element and any React JSX augmentation for it from your app. `initWebStore()` is still mandatory and is still called the same way.
* **web:** the `sql-wasm.wasm` copy step is gone. The worker and `sqlite3.wasm` ship inside the package under `dist/`; delete any `copysqlwasm` script or assets entry.
* **web:** minimum browser versions are now Chrome/Android WebView 80, Safari 14 and Firefox 74. Below that the plugin fails to load rather than degrading, and no build target in your app changes that.
* **web:** the `<jeep-sqlite>` `autosave` attribute no longer exists. `saveToStore()` is a no-op when the store is on OPFS and the real image flush on the IndexedDB fallback.
* **web:** integer values above 2^53 are returned as `BigInt` instead of a silently truncated number. `JSON.stringify` throws on those; `exportToJson` encodes them as decimal strings.
* **web:** only one tab per origin may own the store. A second tab now gets an explicit error from `initWebStore()` instead of racing the first.
* **web:** foreign keys are now enforced. `PRAGMA foreign_keys` is set ON at open, as it already was on every other platform, so a schema that was quietly violating its own constraints will start reporting them.

### Features

* **web:** replace the jeep-sqlite pass-through with an in-repo engine on `@sqlite.org/sqlite-wasm`, running in a dedicated worker, with databases stored as real files in OPFS through the `opfs-sahpool` VFS and no COOP/COEP headers required
* **web:** add an automatic `:memory:` + IndexedDB fallback tier for browsers without OPFS sync access handles, so the two tiers differ only in where the bytes rest
* **web:** import databases from the previous `jeep-sqlite` IndexedDB store once, on the first `initWebStore()`, verifying each one with `PRAGMA integrity_check` before retiring the legacy store
* **web:** support read-only connections, which were previously unsupported on this platform
* **web:** add a real browser test suite (vitest browser mode, Playwright Chromium) covering both durability tiers
* **web:** export `setSqliteWorkerFactory`, `setSqliteWebOptions` and `setSqliteLocalDiskAdapter` for bundlers and file pickers that need to override the defaults
* **web:** propagate a soft delete along foreign keys, applying each constraint's `ON DELETE` action to the `sql_deleted` marking, recursively and for every referencing table
* **web:** move databases out of the IndexedDB fallback store and into OPFS automatically when a browser gains OPFS support, so an engine update no longer strands them
* **web:** close and pause the store when a Capacitor app is backgrounded and restore it on return, with a worker restart as the fallback when the gentle path cannot run

### Bug Fixes

* **web:** a failed database upgrade now restores the pre-upgrade image and rejects, instead of returning a working connection at the old version with no signal that the migration failed
* **web:** errors keep their message instead of being re-thrown as the string `Error: Error: ...`
* **web:** a worker that fails to load now says which of the two causes it was, an unsupported browser or an asset the bundler did not serve, and quotes what the browser reported
* **web:** a `DELETE` inside an `execute()` batch is now recorded on a sync-tracked database instead of physically removing the rows, matching `run()` and every other platform
* **web:** a soft delete whose `WHERE` clause contained a string literal produced SQL that did not parse; the clause now keeps its literals
* **web:** `DELETE ... RETURNING` on a sync-tracked database produced SQL that did not parse; the clause is now carried to the rewritten statement and the rows come back
* **web:** a `DELETE` against a table without a `sql_deleted` column is no longer rewritten just because some other table in the database has one
* **web:** a `DELETE` against a double-quoted table name on a sync-tracked database performed a real delete instead of recording one
* **web:** a `DELETE` whose `WHERE` clause contained a top-level `OR` re-marked already-deleted rows, because `AND` binds tighter than `OR` in the rewritten statement
* **web:** a table whose name is a reserved word, such as `order`, switched soft deletes off for the whole database
* **web:** `CREATE TABLE` issued through `run()` rather than `execute()` left the cached schema stale, so a foreign key added that way was invisible to the next delete
* **web:** `importFromJson` with `overwrite` left the caller's open connection closed behind its back

## [8.1.0](https://github.com/capacitor-community/sqlite/compare/v8.0.1...v8.1.0) (2026-03-30)


Expand Down
91 changes: 67 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
<br>

<p align="center">
Capacitor community plugin for Native and Electron SQLite Databases.
Capacitor community plugin for Native, Electron and Web SQLite Databases.
- In Native, databases could be encrypted with `SQLCipher`
- In Electron, databases could be encrypted with `better-sqlite3-multiple-ciphers`
- In Web, databases run on `@sqlite.org/sqlite-wasm` in a Worker and persist in OPFS; encryption is not available
</p>
<br>
<p align="center">
Expand Down Expand Up @@ -45,8 +46,6 @@ npx cap sync

```
pnpm install --save @capacitor-community/sqlite
pnpm install --save jeep-sqlite
pnpm install --save sql.js
npx cap sync
```

Expand Down Expand Up @@ -101,35 +100,74 @@ export default config;

## Tutorials Blog

- [JeepQ Capacitor Plugin Tutorials](https://jepiqueau.github.io/)
- [JeepQ Capacitor Plugin Tutorials](https://jepiqueau.github.io/) (the Web tutorials there describe the previous `jeep-sqlite` setup, which no longer applies)


## Web Quirks

The plugin follows the guidelines from the `Capacitor Team`,
On the Web platform the plugin runs [`@sqlite.org/sqlite-wasm`](https://github.com/sqlite/sqlite-wasm), the official SQLite build, inside a dedicated Worker that it creates itself. There is nothing to install alongside it and nothing to copy into your assets folder: the worker (`dist/web-worker.js`) and the SQLite binary (`dist/sqlite3.wasm`) ship inside the package. Full setup is in [Web Usage](https://github.com/capacitor-community/sqlite/blob/master/docs/Web-Usage.md), which is required reading for the Web platform.

- [Capacitor Browser Support](https://capacitorjs.com/docs/v3/web#browser-support)
`initWebStore()` is still mandatory and is still called exactly as before, once, before the first connection.

Meaning that it will not work in IE11 without additional JavaScript transformations, e.g. with [Babel](https://babeljs.io/).
You'll need the usual capacitor/android/react npm script to build and copy the assets folder.
#### Where the data lives

#### For Angular framework
`initWebStore()` picks one of two durability tiers:

- Copy manually the file `sql-wasm.wasm` from `node_modules/sql.js/dist/sql-wasm.wasm` to the `src/assets` folder of YOUR_APP
| Tier | Storage | When it is used |
| ---- | ------- | --------------- |
| 1 | Real database files in the `Origin Private File System`, through the `opfs-sahpool` VFS. Needs no COOP/COEP headers and no `SharedArrayBuffer`, so it works inside Capacitor WebViews and on ordinary hosting. | Whenever the browser has OPFS sync access handles. |
| 2 | `:memory:` databases whose whole-file image is written to IndexedDB, the model the previous implementation used. | Automatic fallback when it does not. |

#### For Vue & React frameworks
`saveToStore()` is a no-op on tier 1, where every committed write is already durable, and performs the real image flush on tier 2 (as do `close` and `closeConnection`). Calling it unconditionally is the portable pattern. The old `<jeep-sqlite>` `autosave` attribute is gone and has no replacement.

- Copy manually the file `sql-wasm.wasm` from `node_modules/sql.js/dist/sql-wasm.wasm` to the `public/assets` folder of YOUR_APP
#### Browser support

## Web Debugging Tools
There are two separate floors, and they mean different things:

| Floor | Chromium / Android WebView | Safari / iOS WebKit | Firefox | What happens below it |
| ----- | -------------------------- | ------------------- | ------- | --------------------- |
| Engine (`BigInt`, optional chaining, nullish coalescing) | 80 | 14 | 74 | **The plugin does not load at all.** The failure is a syntax error inside the SQLite build, not a fallback, and no transpiler setting in your app can change it. |
| Durability (OPFS sync access handles) | 108 | 16.4 | 111 | Tier 2 above: everything works, the database is an image in IndexedDB rather than a file. Android WebView reached this in M132, January 2025. |

#### Who actually lands on tier 2

Tier 2 is chosen at `initWebStore()` when the browser lacks OPFS sync access handles:
concretely, `navigator.storage.getDirectory` or
`FileSystemFileHandle.prototype.createSyncAccessHandle` is missing, or the SQLite build's own
API version check rejects. In browser versions that means Safari / iOS WebKit 14 to 16.3,
Chromium 80 to 107, Firefox 74 to 110, and Android System WebView before M132 (January 2025).
Two version-independent cases: Safari private browsing always runs tier 2 (OPFS is unavailable
there on every Safari version), and a store already owned by another tab is an explicit error,
never a silent drop to tier 2.

How much traffic that is: measured against caniuse-lite 1.0.30001806 (StatCounter data,
checked 2026-08), the whole tier 2 version band is about **1.4% of global browser usage**:
Safari / iOS 14-16.3 at ~0.5%, Chromium 80-107 remnants at ~0.9%, Firefox 74-110 at ~0.01%.
Put differently, of the usage that can load this plugin at all, **roughly 98% runs tier 1 and
under 2% lands on tier 2**, and the band shrinks as evergreen browsers update. In-app WebView
versions are not visible to StatCounter, so treat WebViews older than M132 (devices without
Google Play services, or with WebView updates disabled) as a small qualitative extra on top.

When using the Web platform, SQLite databases are backed by `jeep-sqlite` and stored in IndexedDB, which can be difficult to inspect during development.
Why the tier stays despite the small share: Capacitor 8 itself supports iOS 15.0+ and Android
API 24+ with WebViews as old as Chrome 60, so the platform's official support envelope reaches
below the tier 1 floor, and a Capacitor plugin should not fail where Capacitor does not; and
Safari private browsing is tier 2 forever regardless of version, so the tier is the difference
between an app that works ephemerally in a private tab and one that is simply broken there.

For easier debugging, there is a Chrome DevTools extension that allows browsing, querying, and exporting `jeep-sqlite` databases directly from IndexedDB:
#### Other things worth knowing

- **Jeep SQLite Browser (Chrome DevTools Extension)**
- Chrome Web Store: https://chromewebstore.google.com/detail/jeep-sqlite-browser/ocgeealadeabmhponndjebghfkbfbnch
- GitHub: https://github.com/pinguluk/jeep-sqlite-browser
- **One tab at a time.** OPFS access handles are single-owner by design, so `initWebStore()` fails with an explicit error if another tab of the same origin already owns the store.
- **Migration is automatic.** The first `initWebStore()` after upgrading imports every database left behind by the previous `jeep-sqlite`/IndexedDB implementation, verifies each one with `PRAGMA integrity_check`, and only then retires the old store. A failure leaves the old data untouched and warns on the console.
- **Tier 2 databases follow you up to tier 1.** When a browser that lacked OPFS gains it, which is the normal upgrade path for the devices that run on tier 2, `initWebStore()` moves the stored images into OPFS and only then removes them.
- **Foreign keys are enforced**, as on every other platform. On a database that participates in sync, a soft delete also applies each constraint's `ON DELETE` action to the children, and to their children.
- **Backgrounding is handled** under Capacitor native: the store is closed and paused before the OS suspends the app, and reopened on return. `pauseWebStore()`, `resumeWebStore()` and `restartWebStore()` are there if you would rather drive it from `App.appStateChange`.
- **Integers above 2^53 are returned as `BigInt`.** The previous web engine silently lost precision on those. `JSON.stringify` refuses to serialise a `BigInt`, so code that stringifies query results may need `exportToJson`, which handles this, or a replacer.
- **No encryption.** There is no SQLCipher build for wasm, so encrypted connections and every secret-related method still reject on Web.
- **Read-only connections work on Web**, on both tiers.

## Web Debugging Tools

Where to look depends on the tier. On tier 1, open DevTools > Application > Storage and browse the Origin Private File System: the databases sit in the `.capacitor-sqlite` directory, though `opfs-sahpool` names the files opaquely, so exporting through the plugin is usually easier than reading them in place. On tier 2, they appear in IndexedDB under `capacitor-sqlite-store` > `databases`, one whole-file image per key.

## Android Quirks

Expand Down Expand Up @@ -218,11 +256,11 @@ npm install --save-dev electron-builder@24.6.4
| Name | Android | iOS | Electron | Web |
| :--------------------------- | :------ | :--- | :------- | :--- |
| createConnection (ReadWrite) | ✅ | ✅ | ✅ | ✅ |
| createConnection (ReadOnly) | ✅ | ✅ | ✅ | | since 4.1.0-7 |
| createConnection (ReadOnly) | ✅ | ✅ | ✅ | | since 4.1.0-7, Web since 8.2.0 |
| closeConnection (ReadWrite) | ✅ | ✅ | ✅ | ✅ |
| closeConnection (ReadOnly) | ✅ | ✅ | ✅ | | since 4.1.0-7 |
| closeConnection (ReadOnly) | ✅ | ✅ | ✅ | | since 4.1.0-7, Web since 8.2.0 |
| isConnection (ReadWrite) | ✅ | ✅ | ✅ | ✅ |
| isConnection (ReadOnly) | ✅ | ✅ | ✅ | | since 4.1.0-7 |
| isConnection (ReadOnly) | ✅ | ✅ | ✅ | | since 4.1.0-7, Web since 8.2.0 |
| open (non-encrypted DB) | ✅ | ✅ | ✅ | ✅ |
| open (encrypted DB) | ✅ | ✅ | ✅ | ❌ |
| close | ✅ | ✅ | ✅ | ✅ |
Expand Down Expand Up @@ -259,7 +297,7 @@ npm install --save-dev electron-builder@24.6.4
| clearEncryptionSecret | ✅ | ✅ | ✅ | ❌ |
| checkEncryptionSecret | ✅ | ✅ | ✅ | ❌ |
| initWebStore | ❌ | ❌ | ❌ | ✅ |
| saveToStore | ❌ | ❌ | ❌ | ✅ |
| saveToStore | ❌ | ❌ | ❌ | ✅ | Web: no-op on OPFS, flushes the image on the IndexedDB tier |
| getNCDatabasePath | ✅ | ✅ | ❌ | ❌ |
| createNCConnection | ✅ | ✅ | ❌ | ❌ |
| closeNCConnection | ✅ | ✅ | ❌ | ❌ |
Expand Down Expand Up @@ -295,7 +333,7 @@ npm install --save-dev electron-builder@24.6.4

- [TypeORM-From-5.6.0](https://github.com/capacitor-community/sqlite/blob/master/docs/TypeORM-Usage-From-5.6.0.md)

- [Web Usage](https://github.com/capacitor-community/sqlite/blob/master/docs/Web-Usage.md)
- [Web Usage](https://github.com/capacitor-community/sqlite/blob/master/docs/Web-Usage.md) (required reading for the Web platform)

- [Non Conformed Databases](https://github.com/capacitor-community/sqlite/blob/master/docs/NonConformedDatabases.md)

Expand All @@ -310,6 +348,11 @@ npm install --save-dev electron-builder@24.6.4

## Applications demonstrating the use of the plugin and related documentation

> The sample apps below predate the current Web engine. Their native code is unaffected, but every
> Web setup step they show (installing `jeep-sqlite`, defining the custom element, copying
> `sql-wasm.wasm`) has been removed from the plugin. Follow
> [Web Usage](https://github.com/capacitor-community/sqlite/blob/master/docs/Web-Usage.md) instead.

### Ionic/Angular

- [Web ionic7-angular-sqlite-app](https://github.com/jepiqueau/blog-tutorials-apps/tree/main/SQLite/Part-1/ionic7-angular-sqlite-app) Ionic 7 Angular 16 Capacitor 5 SQLite CRUD operations for Web.
Expand Down Expand Up @@ -370,7 +413,7 @@ npm install --save-dev electron-builder@24.6.4
The iOS and Android codes are using `SQLCipher` allowing for database encryption.
The iOS code is using `ZIPFoundation` for unzipping assets files
The Electron code is using `better-sqlite3-multiple-ciphers` , `electron-json-storage` and `node-fetch` from 5.0.4.
The Web code is using the Stencil component `jeep-sqlite` based on `sql.js`, `localforage`. and `jszip`
The Web code is using `@sqlite.org/sqlite-wasm`, the official SQLite wasm build, in a dedicated Worker, with `fflate` for unzipping assets files.

## Contributors ✨

Expand Down
Loading
Loading