@capacitor-community/sqlite
This is the description on how to use the Web part of the @capacitor-community/sqlite for development purpose.
When your developement is fully tested, it will be a good idea to minimize the package size of your native app to remove the jeep-sqlite
Stencil component and the sql-wasm.wasm
file from the assets folder.
npm i --save @capacitor-community/sqlite@latest
npm i --save jeep-sqlite@latest
jeep-sqlite
is a Stencil Component which is using sql.js
for sql in-memory queries and store the database in the Browser on a localforage
IndexedDB store named jeepSqliteStore
and inside a table named databases
.
🚨 The database is stored from in-memory to localforage
IndexedDB store when one requires
- a
saveToStore
, - a
close
, - a
closeConnection
. 🚨
-
sql-wasm.wasm
-
Either copy manually the file
sql-wasm.wasm
fromnode_modules/sql.js/dist/sql-wasm.wasm
to thesrc/assets
folder of YOUR_APP -
or
npm i --save-dev copyfiles
and modify the scripts in thepackage.json
file as follows:"scripts": { "ng": "ng", "start": "npm run copysqlwasm && ng serve", "build": "npm run copysqlwasm && ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", "copysqlwasm": "copyfiles -u 3 node_modules/sql.js/dist/sql-wasm.wasm src/assets" },
-
-
For databases in the
src/assets/databases
folder if any, you have to create adatabases.json
file which includes only the non-encrypted database's names
{
"databaseList" : [
"YOUR_DB1.db",
"YOUR_DB2.db",
...
]
}
- open the
main.ts
file and add the following
...
import { defineCustomElements as jeepSqlite} from 'jeep-sqlite/loader';
...
jeepSqlite(window);
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));
- open the
app.module.ts
file and add
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
...
import { SQLiteService } from './services/sqlite.service';
import { DetailService } from './services/detail.service';
...
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
providers: [
SQLiteService,
DetailService,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
- open the
app-component.html
file and add
<ion-app>
<ion-router-outlet></ion-router-outlet>
<jeep-sqlite *ngIf="isWeb"></jeep-sqlite>
</ion-app>
- open the
app-component.ts
file and add
import { Component } from '@angular/core';
import { Platform } from '@ionic/angular';
import { SQLiteService } from './services/sqlite.service';
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss']
})
export class AppComponent {
public isWeb: boolean = false;
private initPlugin: boolean;
constructor(
private platform: Platform,
private sqlite: SQLiteService,
) {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(async () => {
this.sqlite.initializePlugin().then(async (ret) => {
this.initPlugin = ret;
if( this.sqlite.platform === "web") {
this.isWeb = true;
await customElements.whenDefined('jeep-sqlite');
const jeepSqliteEl = document.querySelector('jeep-sqlite');
if(jeepSqliteEl != null) {
await this.sqlite.initWebStore();
console.log(`>>>> isStoreOpen ${await jeepSqliteEl.isStoreOpen()}`);
} else {
console.log('>>>> jeepSqliteEl is null');
}
}
console.log(`>>>> in App this.initPlugin ${this.initPlugin}`);
});
});
}
}
- open or create a
sqlite.service.ts
under theservices
folder
import { Injectable } from '@angular/core';
import { Capacitor } from '@capacitor/core';
import { CapacitorSQLite, SQLiteDBConnection, SQLiteConnection, capSQLiteSet,
capSQLiteChanges, capSQLiteValues, capEchoResult, capSQLiteResult,
capNCDatabasePathResult } from '@capacitor-community/sqlite';
@Injectable()
export class SQLiteService {
sqlite: SQLiteConnection;
isService: boolean = false;
platform: string;
sqlitePlugin: any;
native: boolean = false;
constructor() {
}
/**
* Plugin Initialization
*/
initializePlugin(): Promise<boolean> {
return new Promise (resolve => {
this.platform = Capacitor.getPlatform();
if(this.platform === 'ios' || this.platform === 'android') this.native = true;
this.sqlitePlugin = CapacitorSQLite;
this.sqlite = new SQLiteConnection(this.sqlitePlugin);
this.isService = true;
resolve(true);
});
}
/**
* Echo a value
* @param value
*/
async echo(value: string): Promise<capEchoResult> {
if(this.sqlite != null) {
try {
const ret = await this.sqlite.echo(value);
return Promise.resolve(ret);
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error("no connection open"));
}
}
async isSecretStored(): Promise<capSQLiteResult> {
if(!this.native) {
return Promise.reject(new Error(`Not implemented for ${this.platform} platform`));
}
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.isSecretStored());
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
async setEncryptionSecret(passphrase: string): Promise<void> {
if(!this.native) {
return Promise.reject(new Error(`Not implemented for ${this.platform} platform`));
}
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.setEncryptionSecret(passphrase));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
async changeEncryptionSecret(passphrase: string, oldpassphrase: string): Promise<void> {
if(!this.native) {
return Promise.reject(new Error(`Not implemented for ${this.platform} platform`));
}
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.changeEncryptionSecret(passphrase, oldpassphrase));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* addUpgradeStatement
* @param database
* @param toVersion
* @param statements
*/
async addUpgradeStatement(database:string, toVersion: number, statements: string)
: Promise<void> {
if(this.sqlite != null) {
try {
await this.sqlite.addUpgradeStatement(database, toVersion, statement);
return Promise.resolve();
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open for ${database}`));
}
}
/**
* get a non-conformed database path
* @param path
* @param database
* @returns Promise<capNCDatabasePathResult>
* @since 3.3.3-1
*/
async getNCDatabasePath(folderPath: string, database: string): Promise<capNCDatabasePathResult> {
if(this.sqlite != null) {
try {
const res: capNCDatabasePathResult = await this.sqlite.getNCDatabasePath(
folderPath, database);
return Promise.resolve(res);
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open for ${database}`));
}
}
/**
* Create a non-conformed database connection
* @param databasePath
* @param version
* @returns Promise<SQLiteDBConnection>
* @since 3.3.3-1
*/
async createNCConnection(databasePath: string, version: number): Promise<SQLiteDBConnection> {
if(this.sqlite != null) {
try {
const db: SQLiteDBConnection = await this.sqlite.createNCConnection(
databasePath, version);
if (db != null) {
return Promise.resolve(db);
} else {
return Promise.reject(new Error(`no db returned is null`));
}
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open for ${databasePath}`));
}
}
/**
* Close a non-conformed database connection
* @param databasePath
* @returns Promise<void>
* @since 3.3.3-1
*/
async closeNCConnection(databasePath: string): Promise<void> {
if(this.sqlite != null) {
try {
await this.sqlite.closeNCConnection(databasePath);
return Promise.resolve();
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open for ${databasePath}`));
}
}
/**
* Check if a non-conformed databaseconnection exists
* @param databasePath
* @returns Promise<capSQLiteResult>
* @since 3.3.3-1
*/
async isNCConnection(databasePath: string): Promise<capSQLiteResult> {
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.isNCConnection(databasePath));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Retrieve a non-conformed database connection
* @param databasePath
* @returns Promise<SQLiteDBConnection>
* @since 3.3.3-1
*/
async retrieveNCConnection(databasePath: string): Promise<SQLiteDBConnection> {
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.retrieveNCConnection(databasePath));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open for ${databasePath}`));
}
}
/**
* Check if a non conformed database exists
* @param databasePath
* @returns Promise<capSQLiteResult>
* @since 3.3.3-1
*/
async isNCDatabase(databasePath: string): Promise<capSQLiteResult> {
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.isNCDatabase(databasePath));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Create a connection to a database
* @param database
* @param encrypted
* @param mode
* @param version
*/
async createConnection(database:string, encrypted: boolean,
mode: string, version: number
): Promise<SQLiteDBConnection> {
if(this.sqlite != null) {
try {
/* if(encrypted) {
if(this.native) {
const isSet = await this.sqlite.isSecretStored()
if(!isSet.result) {
return Promise.reject(new Error(`no secret phrase registered`));
}
}
}
*/
const db: SQLiteDBConnection = await this.sqlite.createConnection(
database, encrypted, mode, version);
if (db != null) {
return Promise.resolve(db);
} else {
return Promise.reject(new Error(`no db returned is null`));
}
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open for ${database}`));
}
}
/**
* Close a connection to a database
* @param database
*/
async closeConnection(database:string): Promise<void> {
if(this.sqlite != null) {
try {
await this.sqlite.closeConnection(database);
return Promise.resolve();
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open for ${database}`));
}
}
/**
* Retrieve an existing connection to a database
* @param database
*/
async retrieveConnection(database:string):
Promise<SQLiteDBConnection> {
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.retrieveConnection(database));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open for ${database}`));
}
}
/**
* Retrieve all existing connections
*/
async retrieveAllConnections():
Promise<Map<string, SQLiteDBConnection>> {
if(this.sqlite != null) {
try {
const myConns = await this.sqlite.retrieveAllConnections();
/* let keys = [...myConns.keys()];
keys.forEach( (value) => {
console.log("Connection: " + value);
});
*/
return Promise.resolve(myConns);
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Close all existing connections
*/
async closeAllConnections(): Promise<void> {
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.closeAllConnections());
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Check if connection exists
* @param database
*/
async isConnection(database: string): Promise<capSQLiteResult> {
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.isConnection(database));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Check Connections Consistency
* @returns
*/
async checkConnectionsConsistency(): Promise<capSQLiteResult> {
if(this.sqlite != null) {
try {
const res = await this.sqlite.checkConnectionsConsistency();
return Promise.resolve(res);
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Check if database exists
* @param database
*/
async isDatabase(database: string): Promise<capSQLiteResult> {
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.isDatabase(database));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Get the list of databases
*/
async getDatabaseList() : Promise<capSQLiteValues> {
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.getDatabaseList());
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Get Migratable databases List
*/
async getMigratableDbList(folderPath?: string): Promise<capSQLiteValues>{
if(!this.native) {
return Promise.reject(new Error(`Not implemented for ${this.platform} platform`));
}
if(this.sqlite != null) {
try {
if(!folderPath || folderPath.length === 0) {
return Promise.reject(new Error(`You must provide a folder path`));
}
return Promise.resolve(await this.sqlite.getMigratableDbList(folderPath));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Add "SQLite" suffix to old database's names
*/
async addSQLiteSuffix(folderPath?: string, dbNameList?: string[]): Promise<void>{
if(!this.native) {
return Promise.reject(new Error(`Not implemented for ${this.platform} platform`));
}
if(this.sqlite != null) {
try {
const path: string = folderPath ? folderPath : "default";
const dbList: string[] = dbNameList ? dbNameList : [];
return Promise.resolve(await this.sqlite.addSQLiteSuffix(path, dbList));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Delete old databases
*/
async deleteOldDatabases(folderPath?: string, dbNameList?: string[]): Promise<void>{
if(!this.native) {
return Promise.reject(new Error(`Not implemented for ${this.platform} platform`));
}
if(this.sqlite != null) {
try {
const path: string = folderPath ? folderPath : "default";
const dbList: string[] = dbNameList ? dbNameList : [];
return Promise.resolve(await this.sqlite.deleteOldDatabases(path, dbList));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Import from a Json Object
* @param jsonstring
*/
async importFromJson(jsonstring:string): Promise<capSQLiteChanges> {
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.importFromJson(jsonstring));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Is Json Object Valid
* @param jsonstring Check the validity of a given Json Object
*/
async isJsonValid(jsonstring:string): Promise<capSQLiteResult> {
if(this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.isJsonValid(jsonstring));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Copy databases from public/assets/databases folder to application databases folder
*/
async copyFromAssets(overwrite?: boolean): Promise<void> {
const mOverwrite: boolean = overwrite != null ? overwrite : true;
console.log(`&&&& mOverwrite ${mOverwrite}`);
if (this.sqlite != null) {
try {
return Promise.resolve(await this.sqlite.copyFromAssets(mOverwrite));
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Initialize the Web store
* @param database
*/
async initWebStore(): Promise<void> {
if(this.platform !== 'web') {
return Promise.reject(new Error(`not implemented for this platform: ${this.platform}`));
}
if(this.sqlite != null) {
try {
await this.sqlite.initWebStore();
return Promise.resolve();
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open`));
}
}
/**
* Save a database to store
* @param database
*/
async saveToStore(database:string): Promise<void> {
if(this.platform !== 'web') {
return Promise.reject(new Error(`not implemented for this platform: ${this.platform}`));
}
if(this.sqlite != null) {
try {
await this.sqlite.saveToStore(database);
return Promise.resolve();
} catch (err) {
return Promise.reject(new Error(err));
}
} else {
return Promise.reject(new Error(`no connection open for ${database}`));
}
}
}
- follow the capacitor build process
npx cap sync
npm run build
npx cap copy web
ionic serve
that is it.
-
copy manually the file
sql-wasm.wasm
fromnode_modules/sql.js/dist/sql-wasm.wasm
to thepublic/assets
folder of YOUR_APP -
For databases in the
public/assets/databases
folder if any, you have to create adatabases.json
file which includes only the non-encrypted database's names
{
"databaseList" : [
"YOUR_DB1.db",
"YOUR_DB2.db",
...
]
}
- open the
main.ts
file and add the following
...
import { defineCustomElements as jeepSqlite, applyPolyfills } from "jeep-sqlite/loader";
import { Capacitor } from '@capacitor/core';
import { CapacitorSQLite, SQLiteConnection, SQLiteDBConnection } from '@capacitor-community/sqlite';
import { useState } from '@/composables/state';
...
applyPolyfills().then(() => {
jeepSqlite(window);
});
window.addEventListener('DOMContentLoaded', async () => {
const platform = Capacitor.getPlatform();
const sqlite: SQLiteConnection = new SQLiteConnection(CapacitorSQLite)
const app = createApp(App)
.use(IonicVue)
.use(router);
/* SQLite Global Variables*/
// Only if you want to use the onProgressImport/Export events
const [jsonListeners, setJsonListeners] = useState(false);
const [isModal, setIsModal] = useState(false);
const [message, setMessage] = useState("");
app.config.globalProperties.$isModalOpen = {isModal: isModal, setIsModal: setIsModal};
app.config.globalProperties.$isJsonListeners = {jsonListeners: jsonListeners, setJsonListeners: setJsonListeners};
app.config.globalProperties.$messageContent = {message: message, setMessage: setMessage};
// Existing Connections Store
const [existConn, setExistConn] = useState(false);
app.config.globalProperties.$existingConn = {existConn: existConn, setExistConn: setExistConn};
try {
if(platform === "web") {
// Create the 'jeep-sqlite' Stencil component
const jeepSqlite = document.createElement('jeep-sqlite');
document.body.appendChild(jeepSqlite);
await customElements.whenDefined('jeep-sqlite');
// Initialize the Web store
await sqlite.initWebStore();
}
// here you can initialize some database schema if required
// example: database creation with standard SQLite statements
const ret = await sqlite.checkConnectionsConsistency();
const isConn = (await sqlite.isConnection("db_tab3")).result;
let db: SQLiteDBConnection
if (ret.result && isConn) {
db = await sqlite.retrieveConnection("db_tab3");
} else {
db = await sqlite.createConnection("db_tab3", false, "no-encryption", 1);
}
await db.open();
const query = `
CREATE TABLE IF NOT EXISTS test (
id INTEGER PRIMARY KEY NOT NULL,
name TEXT NOT NULL
);
`
const res = await db.execute(query);
if(res.changes && res.changes.changes && res.changes.changes < 0) {
throw new Error(`Error: execute failed`);
}
await sqlite.closeConnection("db_tab3");
// example: database creation from importFromJson
const schemaToImport179 = {
database: 'db-issue179',
version: 1,
encrypted: false,
mode: 'full',
tables: [
{
name: 'album',
schema: [
{ column: 'albumartist', value: 'TEXT NOT NULL' },
{ column: 'albumname', value: 'TEXT NOT NULL' },
{ column: 'albumcover', value: 'BINARY' },
{ column: 'last_modified', value: 'INTEGER' },
{ constraint: 'PK_albumartist_albumname', value: 'PRIMARY KEY (albumartist,albumname)'},
],
indexes: [
{ name: 'index_album_on_albumartist_albumname', value: 'albumartist,albumname' },
{ name: 'index_album_on_last_modified', value: 'last_modified DESC' },
],
},
{
name: 'song',
schema: [
{ column: 'songid', value: 'INTEGER PRIMARY KEY NOT NULL' },
{ column: 'songartist', value: 'TEXT NOT NULL' },
{ column: 'songalbum', value: 'TEXT NOT NULL' },
{ column: 'songname', value: 'TEXT NOT NULL' },
{ column: 'last_modified', value: 'INTEGER' },
{
foreignkey: 'songartist,songalbum',
value: 'REFERENCES album(albumartist,albumname)',
},
],
indexes: [
{ name: 'index_song_on_songartist_songalbum', value: 'songartist,songalbum' },
{
name: 'index_song_on_last_modified',
value: 'last_modified DESC',
},
],
},
],
};
const result = await sqlite.isJsonValid(JSON.stringify(schemaToImport179));
if(!result.result) {
throw new Error(`isJsonValid: "schemaToImport179" is not valid`);
}
// full import
const resJson = await sqlite.importFromJson(JSON.stringify(schemaToImport179));
if(resJson.changes && resJson.changes.changes && resJson.changes.changes < 0) {
throw new Error(`importFromJson: "full" failed`);
}
...
router.isReady().then(() => {
app.mount('#app');
});
} catch (err) {
console.log(`Error: ${err}`);
throw new Error(`Error: ${err}`)
}
});
- open the
App.vue
file
<template>
<ion-app>
<ion-router-outlet />
</ion-app>
</template>
<script lang="ts">
import { IonApp, IonRouterOutlet } from '@ionic/vue';
import { defineComponent, getCurrentInstance} from 'vue';
import { useSQLite} from 'vue-sqlite-hook/dist';
export default defineComponent({
name: 'App',
components: {
IonApp,
IonRouterOutlet,
},
setup() {
const app = getCurrentInstance();
const isModalOpen = app?.appContext.config.globalProperties.$isModalOpen;
const contentMessage = app?.appContext.config.globalProperties.$messageContent;
const jsonListeners = app?.appContext.config.globalProperties.$isJsonListeners;
const onProgressImport = async (progress: string) => {
if(jsonListeners.jsonListeners.value) {
if(!isModalOpen.isModal.value) isModalOpen.setIsModal(true);
contentMessage.setMessage(
contentMessage.message.value.concat(`${progress}\n`));
}
}
const onProgressExport = async (progress: string) => {
if(jsonListeners.jsonListeners.value) {
if(!isModalOpen.isModal.value) isModalOpen.setIsModal(true);
contentMessage.setMessage(
contentMessage.message.value.concat(`${progress}\n`));
}
}
if( app != null) {
// !!!!! if you do not want to use the progress events !!!!!
// since vue-sqlite-hook 2.1.1
// app.appContext.config.globalProperties.$sqlite = useSQLite()
// before
// app.appContext.config.globalProperties.$sqlite = useSQLite({})
// !!!!! !!!!!
app.appContext.config.globalProperties.$sqlite = useSQLite({
onProgressImport,
onProgressExport
});
}
return;
}
});
</script>
- open a component
YOUR_COMPONENT.vue
file
<template>
<div id="no-encryption-container">
<div v-if="showSpinner">
<br>
<LoadingSpinner />
<div>
<span class="spinner">Running tests ...</span>
</div>
</div>
<div v-else id="log">
<pre>
<p>{{log}}</p>
</pre>
<div v-if="errMess.length > 0">
<p>{{errMess}}</p>}
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, getCurrentInstance } from 'vue';
import { createTablesNoEncryption, importTwoUsers,
dropTablesTablesNoEncryption } from '@/utils/utils-db-no-encryption';
import { useState } from '@/composables/state';
import LoadingSpinner from '@/components/LoadingSpinner.vue'
import { SQLiteDBConnection, SQLiteHook } from 'vue-sqlite-hook/dist';
import { deleteDatabase } from '@/utils/utils-delete-db';
import { Dialog } from '@capacitor/dialog';
export default defineComponent({
name: 'NoEncryptionTest',
components: {
LoadingSpinner
},
setup() {
console.log('$$$ Start NoEncryptionTest setup $$$')
const [showSpinner, setShowSpinner] = useState(true);
const [log, setLog] = useState("");
const app = getCurrentInstance()
const sqlite: SQLiteHook = app?.appContext.config.globalProperties.$sqlite;
let errMess = "";
const showAlert = async (message: string) => {
await Dialog.alert({
title: 'Error Dialog',
message: message,
});
};
const noEncryptionTest = async (): Promise<boolean> => {
try {
console.log(' Starting testDatabaseNoEncryption')
setLog(log.value
.concat("* Starting testDatabaseNoEncryption *\n"));
// test the plugin with echo
let res: any = await sqlite.echo("Hello from echo");
if(res.value !== "Hello from echo"){
errMess = `Echo not returning "Hello from echo"`;
return false;
}
console.log(`after echo ${JSON.stringify(res)}`);
setLog(log.value.concat("> Echo successful\n"));
// create a connection for NoEncryption
const db: SQLiteDBConnection = await sqlite.createConnection("NoEncryption");
setLog(log.value.concat("> createConnection " +
" 'NoEncryption' successful\n"));
console.log("after createConnection")
// check if the databases exist
// and delete it for multiple successive tests
await deleteDatabase(db);
// open NoEncryption database
await db.open();
setLog(log.value.concat("> open 'NoEncryption' successful\n"));
// Drop tables if exists
res = await db.execute(dropTablesTablesNoEncryption);
if(res.changes.changes !== 0 &&
res.changes.changes !== 1){
errMess = `Execute dropTablesTablesNoEncryption changes < 0`;
return false;
}
setLog(log.value.concat(" Execute1 successful\n"));
// Create tables
res = await db.execute(createTablesNoEncryption);
if (res.changes.changes < 0) {
errMess = `Execute createTablesNoEncryption changes < 0`;
return false;
}
setLog(log.value.concat(" Execute2 successful\n"));
// Insert two users with execute method
res = await db.execute(importTwoUsers);
if (res.changes.changes !== 2) {
errMess = `Execute importTwoUsers changes != 2`;
return false;
}
setLog(log.value.concat(" Execute3 successful\n"));
// Select all Users
res = await db.query("SELECT * FROM users");
if(res.values.length !== 2 ||
res.values[0].name !== "Whiteley" ||
res.values[1].name !== "Jones") {
errMess = `Query not returning 2 values`;
return false;
}
setLog(log.value.concat(" Select1 successful\n"));
// add one user with statement and values
let sqlcmd = "INSERT INTO users (name,email,age) VALUES (?,?,?)";
let values: Array<any> = ["Simpson","Simpson@example.com",69];
res = await db.run(sqlcmd,values);
if(res.changes.changes !== 1 ||
res.changes.lastId !== 3) {
errMess = `Run lastId != 3`;
return false;
}
setLog(log.value.concat(" Run1 successful\n"));
// add one user with statement
sqlcmd = `INSERT INTO users (name,email,age) VALUES `+
`("Brown","Brown@example.com",15)`;
res = await db.run(sqlcmd);
if(res.changes.changes !== 1 ||
res.changes.lastId !== 4) {
errMess = `Run lastId != 4`;
return false;
}
setLog(log.value.concat(" Run2 successful\n"));
// Select all Users
res = await db.query("SELECT * FROM users");
if(res.values.length !== 4) {
errMess = `Query not returning 4 values`;
return false;
}
setLog(log.value.concat(" Select2 successful\n"));
// Select Users with age > 35
sqlcmd = "SELECT name,email,age FROM users WHERE age > ?";
values = ["35"];
res = await db.query(sqlcmd,values);
if(res.values.length !== 2) {
errMess = `Query > 35 not returning 2 values`;
return false;
}
setLog(log.value
.concat(" Select with filter on age successful\n"));
// Close Connection NoEncryption
await sqlite.closeConnection("NoEncryption");
setLog(log.value
.concat("* Ending testDatabaseNoEncryption *\n"));
return true;
} catch (err) {
errMess = `${err.message}`;
return false;
}
};
onMounted(async () => {
// Running the test
console.log('$$$ Start NoEncryptionTest on Mounted $$$')
const retNoEncryption: boolean = await noEncryptionTest();
console.log(`retNoEncryption ${retNoEncryption}`);
setShowSpinner(false);
if(!retNoEncryption) {
setLog(log.value
.concat("* testDatabaseNoEncryption failed *\n"));
setLog(log.value
.concat("\n* The set of tests failed *\n"));
await showAlert(errMess);
} else {
setLog(log.value
.concat("\n* The set of tests was successful *\n"));
}
console.log('$$$ End NoEncryptionTest on Mounted $$$')
});
console.log('$$$ End NoEncryptionTest setup $$$')
return { log, showSpinner, errMess };
},
});
</script>
- follow the capacitor build process
npx cap sync
npm run build
npx cap copy web
npm run serve
that is it.
-
copy manually the file
sql-wasm.wasm
fromnode_modules/sql.js/dist/sql-wasm.wasm
to thepublic/assets
folder of YOUR_APP -
For databases in the
public/assets/databases
folder if any, you have to create adatabases.json
file which includes only the non-encrypted database's names
{
"databaseList" : [
"YOUR_DB1.db",
"YOUR_DB2.db",
...
]
}
- open the
index.tsx
file and add the following
...
import { defineCustomElements as jeepSqlite, applyPolyfills, JSX as LocalJSX } from "jeep-sqlite/loader";
import { HTMLAttributes } from 'react';
import { Capacitor } from '@capacitor/core';
import { CapacitorSQLite, SQLiteConnection, SQLiteDBConnection } from '@capacitor-community/sqlite';
type StencilToReact<T> = {
[P in keyof T]?: T[P] & Omit<HTMLAttributes<Element>, 'className'> & {
class?: string;
};
} ;
declare global {
export namespace JSX {
interface IntrinsicElements extends StencilToReact<LocalJSX.IntrinsicElements> {
}
}
}
applyPolyfills().then(() => {
jeepSqlite(window);
});
window.addEventListener('DOMContentLoaded', async () => {
const platform = Capacitor.getPlatform();
const sqlite: SQLiteConnection = new SQLiteConnection(CapacitorSQLite)
try {
if(platform === "web") {
// add 'jeep-sqlite' Stencil component to the DOM
const jeepEl = document.createElement("jeep-sqlite");
document.body.appendChild(jeepEl);
await customElements.whenDefined('jeep-sqlite');
// initialize the web store
await sqlite.initWebStore();
}
// initialize some database schema if needed
const ret = await sqlite.checkConnectionsConsistency();
const isConn = (await sqlite.isConnection("db_issue10")).result;
var db: SQLiteDBConnection
if (ret.result && isConn) {
db = await sqlite.retrieveConnection("db_issue10");
} else {
db = await sqlite.createConnection("db_issue10", false, "no-encryption", 1);
}
await db.open();
let query = `
CREATE TABLE IF NOT EXISTS test (
id INTEGER PRIMARY KEY NOT NULL,
name TEXT NOT NULL
);
`
const res: any = await db.execute(query);
await db.close();
await sqlite.closeConnection("db_issue10");
// launch the React App
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
} catch (err) {
console.log(`Error: ${err}`);
throw new Error(`Error: ${err}`)
}
});
- open the
App.tsx
file
import React, { useState, useRef } from 'react';
import { Redirect, Route } from 'react-router-dom';
import {
IonApp,
IonIcon,
IonLabel,
IonRouterOutlet,
IonTabBar,
IonTabButton,
IonTabs
} from '@ionic/react';
import { IonReactRouter } from '@ionic/react-router';
import { ellipse, square, triangle } from 'ionicons/icons';
import Tab1 from './pages/Tab1';
import Tab2 from './pages/Tab2';
import Tab3 from './pages/Tab3';
import { useSQLite } from 'react-sqlite-hook/dist';
import ModalJsonMessages from './components/ModalJsonMessages';
/* Core CSS required for Ionic components to work properly */
import '@ionic/react/css/core.css';
/* Basic CSS for apps built with Ionic */
import '@ionic/react/css/normalize.css';
import '@ionic/react/css/structure.css';
import '@ionic/react/css/typography.css';
/* Optional CSS utils that can be commented out */
import '@ionic/react/css/padding.css';
import '@ionic/react/css/float-elements.css';
import '@ionic/react/css/text-alignment.css';
import '@ionic/react/css/text-transformation.css';
import '@ionic/react/css/flex-utils.css';
import '@ionic/react/css/display.css';
/* Theme variables */
import './theme/variables.css';
// Singleton SQLite Hook
export let sqlite: any;
// Existing Connections Store
export let existingConn: any;
// Is Json Listeners used
export let isJsonListeners: any;
const App: React.FC = () => {
const [existConn, setExistConn] = useState(false);
existingConn = {existConn: existConn, setExistConn: setExistConn};
const [jsonListeners, setJsonListeners] = useState(false);
isJsonListeners = {jsonListeners: jsonListeners, setJsonListeners: setJsonListeners};
const [isModal,setIsModal] = useState(false);
const message = useRef("");
const onProgressImport = async (progress: string) => {
if(isJsonListeners.jsonListeners) {
if(!isModal) setIsModal(true);
message.current = message.current.concat(`${progress}\n`);
}
}
const onProgressExport = async (progress: string) => {
if(isJsonListeners.jsonListeners) {
if(!isModal) setIsModal(true);
message.current = message.current.concat(`${progress}\n`);
}
}
// !!!!! if you do not want to use the progress events !!!!!
// since react-sqlite-hook 2.1.0
// sqlite = useSQLite()
// before
// sqlite = useSQLite({})
// !!!!! !!!!!
sqlite = useSQLite({
onProgressImport,
onProgressExport
});
const handleClose = () => {
setIsModal(false);
message.current = "";
}
return (
<IonApp>
<IonReactRouter>
<IonTabs>
<IonRouterOutlet>
<Route path="/tab1" component={Tab1} exact={true} />
<Route path="/tab2" component={Tab2} exact={true} />
<Route path="/tab3" component={Tab3} />
<Route path="/" render={() => <Redirect to="/tab1" />} exact={true} />
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="tab1" href="/tab1">
<IonIcon icon={triangle} />
<IonLabel>Tab 1</IonLabel>
</IonTabButton>
<IonTabButton tab="tab2" href="/tab2">
<IonIcon icon={ellipse} />
<IonLabel>Tab 2</IonLabel>
</IonTabButton>
<IonTabButton tab="tab3" href="/tab3">
<IonIcon icon={square} />
<IonLabel>Tab 3</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
</IonReactRouter>
{ isModal
? <ModalJsonMessages close={handleClose} message={message.current}></ModalJsonMessages>
: null
}
</IonApp>
)
};
export default App;
- open a component
YOUR_COMPONENT.tsx
file
import React, { useState, useEffect, useRef } from 'react';
import './NoEncryption.css';
import { IonCard,IonCardContent } from '@ionic/react';
import { createTablesNoEncryption, importTwoUsers,
dropTablesTablesNoEncryption } from '../Utils/noEncryptionUtils';
import { sqlite } from '../App';
import { SQLiteDBConnection} from 'react-sqlite-hook/dist';
import { deleteDatabase } from '../Utils/deleteDBUtil';
import { Dialog } from '@capacitor/dialog';
const NoEncryption: React.FC = () => {
const [log, setLog] = useState<string[]>([]);
const errMess = useRef("");
const showAlert = async (message: string) => {
await Dialog.alert({
title: 'Error Dialog',
message: message,
});
};
useEffect( () => {
const testDatabaseNoEncryption = async (): Promise<Boolean> => {
setLog((log) => log.concat("* Starting testDatabaseNoEncryption *\n"));
try {
// test the plugin with echo
let res: any = await sqlite.echo("Hello from echo");
if(res.value !== "Hello from echo"){
errMess.current = `Echo not returning "Hello from echo"`;
return false;
}
setLog((log) => log.concat("> Echo successful\n"));
// create a connection for NoEncryption
let db: SQLiteDBConnection = await sqlite.createConnection("NoEncryption");
// check if the databases exist
// and delete it for multiple successive tests
await deleteDatabase(db);
// open NoEncryption
await db.open();
setLog((log) => log.concat("> open 'NoEncryption' successful\n"));
// Drop tables if exists
res = await db.execute(dropTablesTablesNoEncryption);
if(res.changes.changes !== 0 &&
res.changes.changes !== 1){
errMess.current = `Execute dropTablesTablesNoEncryption changes < 0`;
return false;
}
setLog((log) => log.concat(" Execute1 successful\n"));
// Create tables
res = await db.execute(createTablesNoEncryption);
if (res.changes.changes < 0) {
errMess.current = `Execute createTablesNoEncryption changes < 0`;
return false;
}
setLog((log) => log.concat(" Execute2 successful\n"));
// Insert two users with execute method
res = await db.execute(importTwoUsers);
if (res.changes.changes !== 2) {
errMess.current = `Execute importTwoUsers changes != 2`;
return false;
}
setLog((log) => log.concat(" Execute3 successful\n"));
// Select all Users
res = await db.query("SELECT * FROM users");
if(res.values.length !== 2 ||
res.values[0].name !== "Whiteley" ||
res.values[1].name !== "Jones") {
errMess.current = `Query not returning 2 values`;
return false;
}
setLog((log) => log.concat(" Select1 successful\n"));
// add one user with statement and values
let sqlcmd = "INSERT INTO users (name,email,age) VALUES (?,?,?)";
let values: Array<any> = ["Simpson","Simpson@example.com",69];
res = await db.run(sqlcmd,values);
if(res.changes.changes !== 1 ||
res.changes.lastId !== 3) {
errMess.current = `Run lastId != 3`;
return false;
}
setLog((log) => log.concat(" Run1 successful\n"));
// add one user with statement
sqlcmd = `INSERT INTO users (name,email,age) VALUES `+
`("Brown","Brown@example.com",15)`;
res = await db.run(sqlcmd);
if(res.changes.changes !== 1 ||
res.changes.lastId !== 4) {
errMess.current = `Run lastId != 4`;
return false;
}
setLog((log) => log.concat(" Run2 successful\n"));
// Select all Users
res = await db.query("SELECT * FROM users");
if(res.values.length !== 4) {
errMess.current = `Query not returning 4 values`;
return false;
}
setLog((log) => log.concat(" Select2 successful\n"));
// Select Users with age > 35
sqlcmd = "SELECT name,email,age FROM users WHERE age > ?";
values = ["35"];
res = await db.query(sqlcmd,values);
if(res.values.length !== 2) {
errMess.current = `Query > 35 not returning 2 values`;
return false;
}
setLog((log) => log
.concat(" Select with filter on age successful\n"));
// Close Connection NoEncryption
await sqlite.closeConnection("NoEncryption");
return true;
} catch (err) {
errMess.current = `${err.message}`;
return false;
}
}
if(sqlite.isAvailable) {
testDatabaseNoEncryption().then(async res => {
if(res) {
setLog((log) => log
.concat("\n* The set of tests was successful *\n"));
} else {
setLog((log) => log
.concat("\n* The set of tests failed *\n"));
await showAlert(errMess.current);
}
});
} else {
sqlite.getPlatform().then((ret: { platform: string; }) => {
setLog((log) => log.concat("\n* Not available for " +
ret.platform + " platform *\n"));
});
}
}, [errMess]);
return (
<IonCard className="container-noencryption">
<IonCardContent>
<pre>
<p>{log}</p>
</pre>
{errMess.current.length > 0 && <p>{errMess.current}</p>}
</IonCardContent>
</IonCard>
);
};
export default NoEncryption;
- follow the capacitor build process
npx cap sync
npm run build
npx cap copy web
npm run start
that is it.
- The web-implementation uses IndexedDB to store the data. IndexedDB-support is checked via userAgent. Pay attention not to modify your userAgent (e.g. selecting an iPhone in the Chrome device simulator in your devtools), as this may break the user-agent check and result in an uninitialized DB (causing an error like this:
No available storage method found
).