-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add import/export dexie db workflow to web client (#740)
- Loading branch information
1 parent
46be8a3
commit 91cbdb4
Showing
14 changed files
with
291 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
use wasm_bindgen::prelude::*; | ||
use wasm_bindgen_futures::{js_sys, wasm_bindgen}; | ||
|
||
#[wasm_bindgen(module = "/src/store/web_store/js/export.js")] | ||
extern "C" { | ||
#[wasm_bindgen(js_name = exportStore)] | ||
pub fn idxdb_export_store() -> js_sys::Promise; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use super::WebStore; | ||
use crate::store::StoreError; | ||
|
||
mod js_bindings; | ||
use js_bindings::idxdb_export_store; | ||
use wasm_bindgen::JsValue; | ||
use wasm_bindgen_futures::JsFuture; | ||
|
||
impl WebStore { | ||
pub async fn export_store(&self) -> Result<JsValue, StoreError> { | ||
let promise = idxdb_export_store(); | ||
let js_value = JsFuture::from(promise) | ||
.await | ||
.map_err(|err| StoreError::DatabaseError(format!("Failed to export store: {err:?}")))?; | ||
Ok(js_value) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
use wasm_bindgen::prelude::*; | ||
use wasm_bindgen_futures::{js_sys, wasm_bindgen}; | ||
|
||
#[wasm_bindgen(module = "/src/store/web_store/js/import.js")] | ||
extern "C" { | ||
#[wasm_bindgen(js_name = forceImportStore)] | ||
pub fn idxdb_force_import_store(store_dump: JsValue) -> js_sys::Promise; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use super::WebStore; | ||
use crate::store::StoreError; | ||
|
||
mod js_bindings; | ||
use js_bindings::idxdb_force_import_store; | ||
use wasm_bindgen::JsValue; | ||
use wasm_bindgen_futures::JsFuture; | ||
|
||
impl WebStore { | ||
pub async fn force_import_store(&self, store_dump: JsValue) -> Result<(), StoreError> { | ||
let promise = idxdb_force_import_store(store_dump); | ||
JsFuture::from(promise) | ||
.await | ||
.map_err(|err| StoreError::DatabaseError(format!("Failed to import store: {err:?}")))?; | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { db } from "./schema.js"; | ||
|
||
async function recursivelyTransformForExport(obj) { | ||
if (obj instanceof Blob) { | ||
const blobBuffer = await obj.arrayBuffer(); | ||
return { | ||
__type: "Blob", | ||
data: uint8ArrayToBase64(new Uint8Array(blobBuffer)), | ||
}; | ||
} | ||
|
||
if (Array.isArray(obj)) { | ||
return await Promise.all(obj.map(recursivelyTransformForExport)); | ||
} | ||
|
||
if (obj && typeof obj === "object") { | ||
const entries = await Promise.all( | ||
Object.entries(obj).map(async ([key, value]) => [ | ||
key, | ||
await recursivelyTransformForExport(value), | ||
]) | ||
); | ||
return Object.fromEntries(entries); | ||
} | ||
|
||
return obj; | ||
} | ||
|
||
export async function exportStore() { | ||
const db_json = {}; | ||
for (const table of db.tables) { | ||
const records = await table.toArray(); | ||
|
||
db_json[table.name] = await Promise.all( | ||
records.map(recursivelyTransformForExport) | ||
); | ||
} | ||
|
||
const stringified = JSON.stringify(db_json); | ||
return stringified; | ||
} | ||
|
||
function uint8ArrayToBase64(uint8Array) { | ||
return btoa(String.fromCharCode(...uint8Array)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { db, openDatabase } from "./schema.js"; | ||
|
||
async function recursivelyTransformForImport(obj) { | ||
if (obj && typeof obj === "object") { | ||
if (obj.__type === "Blob") { | ||
return new Blob([base64ToUint8Array(obj.data)]); | ||
} | ||
|
||
if (Array.isArray(obj)) { | ||
return await Promise.all(obj.map(recursivelyTransformForImport)); | ||
} | ||
|
||
const entries = await Promise.all( | ||
Object.entries(obj).map(async ([key, value]) => [ | ||
key, | ||
await recursivelyTransformForImport(value), | ||
]) | ||
); | ||
return Object.fromEntries(entries); | ||
} | ||
|
||
return obj; // Return unchanged if it's neither Blob, Array, nor Object | ||
} | ||
|
||
export async function forceImportStore(jsonStr) { | ||
try { | ||
if (!db.isOpen) { | ||
await openDatabase(); | ||
} | ||
|
||
let db_json = JSON.parse(jsonStr); | ||
if (typeof db_json === "string") { | ||
db_json = JSON.parse(db_json); | ||
} | ||
|
||
const jsonTableNames = Object.keys(db_json); | ||
const dbTableNames = db.tables.map((t) => t.name); | ||
|
||
if (jsonTableNames.length === 0) { | ||
throw new Error("No tables found in the provided JSON."); | ||
} | ||
|
||
// Wrap everything in a transaction | ||
await db.transaction( | ||
"rw", | ||
...dbTableNames.map((name) => db.table(name)), | ||
async () => { | ||
// Clear all tables in the database | ||
await Promise.all(db.tables.map((t) => t.clear())); | ||
|
||
// Import data from JSON into matching tables | ||
for (const tableName of jsonTableNames) { | ||
const table = db.table(tableName); | ||
|
||
if (!dbTableNames.includes(tableName)) { | ||
console.warn( | ||
`Table "${tableName}" does not exist in the database schema. Skipping.` | ||
); | ||
continue; // Skip tables not in the Dexie schema | ||
} | ||
|
||
const records = db_json[tableName]; | ||
|
||
const transformedRecords = await Promise.all( | ||
records.map(recursivelyTransformForImport) | ||
); | ||
|
||
await table.bulkPut(transformedRecords); | ||
} | ||
} | ||
); | ||
|
||
console.log("Store imported successfully."); | ||
} catch (err) { | ||
console.error("Failed to import store: ", err); | ||
throw err; | ||
} | ||
} | ||
|
||
function base64ToUint8Array(base64) { | ||
const binaryString = atob(base64); | ||
const len = binaryString.length; | ||
const bytes = new Uint8Array(len); | ||
for (let i = 0; i < len; i++) { | ||
bytes[i] = binaryString.charCodeAt(i); | ||
} | ||
return bytes; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// TODO: Rename this / figure out rebasing with the other featuer which has import tests | ||
|
||
import { expect } from "chai"; | ||
import { testingPage } from "./mocha.global.setup.mjs"; | ||
import { clearStore, setupWalletAndFaucet } from "./webClientTestUtils"; | ||
|
||
const exportDb = async () => { | ||
return await testingPage.evaluate(async () => { | ||
const client = window.client; | ||
const db = await client.export_store(); | ||
const serialized = JSON.stringify(db); | ||
return serialized; | ||
}); | ||
}; | ||
|
||
const importDb = async (db: any) => { | ||
return await testingPage.evaluate(async (_db) => { | ||
const client = window.client; | ||
await client.force_import_store(_db); | ||
}, db); | ||
}; | ||
|
||
const getAccount = async (accountId: string) => { | ||
return await testingPage.evaluate(async (_accountId) => { | ||
const client = window.client; | ||
const accountId = window.AccountId.from_hex(_accountId); | ||
const account = await client.get_account(accountId); | ||
return { | ||
accountId: account?.id().to_string(), | ||
accountHash: account?.hash().to_hex(), | ||
}; | ||
}, accountId); | ||
}; | ||
|
||
describe("export and import the db", () => { | ||
it("export db with an account, find the account when re-importing", async () => { | ||
const { accountHash: initialAccountHash, accountId } = | ||
await setupWalletAndFaucet(); | ||
const dbDump = await exportDb(); | ||
|
||
await clearStore(); | ||
|
||
await importDb(dbDump); | ||
|
||
const { accountHash } = await getAccount(accountId); | ||
|
||
expect(accountHash).to.equal(initialAccountHash); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.