Skip to content

Commit 377d528

Browse files
committed
Support web indexDB cache for larger model storage
1 parent fbfa926 commit 377d528

File tree

2 files changed

+164
-1
lines changed

2 files changed

+164
-1
lines changed

web/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export {
2222
PackedFunc, Module, NDArray,
2323
TVMArray, TVMObject, VirtualMachine,
2424
InitProgressCallback, InitProgressReport,
25-
ArtifactCache, Instance, instantiate, hasNDArrayInCache, deleteNDArrayCache
25+
ArtifactCache, ArtifactIndexDBCache, Instance, instantiate, hasNDArrayInCache, deleteNDArrayCache
2626
} from "./runtime";
2727
export { Disposable, LibraryProvider } from "./types";
2828
export { RPCServer } from "./rpc_server";

web/src/runtime.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,169 @@ export class ArtifactCache implements ArtifactCacheTemplate {
10521052
}
10531053
}
10541054

1055+
/**
1056+
* Cache by IndexDB to support caching model data
1057+
*/
1058+
export class ArtifactIndexDBCache implements ArtifactCacheTemplate {
1059+
private dbName?: string;
1060+
private dbVersion = 1;
1061+
private db: IDBDatabase | undefined;
1062+
1063+
private async initDB() {
1064+
if (this.db != null){
1065+
return; // the db is already inialized
1066+
}
1067+
return new Promise<void>((resolve, reject) => {
1068+
const request = indexedDB.open(this.dbName, this.dbVersion);
1069+
request.onupgradeneeded = (event) => {
1070+
this.db = (event.target as IDBOpenDBRequest).result;
1071+
if (!this.db.objectStoreNames.contains('urls')) {
1072+
this.db.createObjectStore('urls', { keyPath: 'url' });
1073+
}
1074+
};
1075+
request.onsuccess = (event) => {
1076+
this.db = (event.target as IDBOpenDBRequest).result;
1077+
resolve();
1078+
};
1079+
request.onerror = (event) => {
1080+
console.error("Database error: ", (event.target as IDBOpenDBRequest).error);
1081+
reject((event.target as IDBOpenDBRequest).error);
1082+
};
1083+
});
1084+
}
1085+
1086+
/* Check if the URL is in DB or not */
1087+
private async isUrlInDB(url: string): Promise<boolean> {
1088+
return new Promise<boolean>((resolve, reject) => {
1089+
const transaction = this.db?.transaction(['urls'], 'readonly');
1090+
if (transaction === undefined){
1091+
return false;
1092+
}
1093+
const store = transaction.objectStore('urls');
1094+
const request = store.get(url);
1095+
request.onsuccess = () => {
1096+
resolve(request.result !== undefined);
1097+
};
1098+
request.onerror = (event) => {
1099+
reject((event.target as IDBRequest).error);
1100+
};
1101+
});
1102+
}
1103+
1104+
constructor(dbName: string){
1105+
this.dbName = dbName;
1106+
}
1107+
1108+
async asyncGetHelper(url: string){
1109+
return new Promise((resolve, reject) => {
1110+
let result: any;
1111+
const transaction = this.db?.transaction(['urls'], 'readonly');
1112+
if (transaction === undefined){
1113+
return false;
1114+
}
1115+
transaction.oncomplete = _ => resolve(result);
1116+
transaction.onerror = e => reject(transaction.error);
1117+
const objectStore = transaction.objectStore('urls');
1118+
const getRequest = objectStore.get(url);
1119+
getRequest.onsuccess = () => {
1120+
result = getRequest.result;
1121+
}
1122+
})
1123+
}
1124+
1125+
async fetchWithCache(url: string) {
1126+
await this.initDB(); // await the initDB process
1127+
const isInDB = await this.isUrlInDB(url);
1128+
if (!isInDB) {
1129+
const response = await this.addToCache(url);
1130+
return response;
1131+
} else {
1132+
const result = await this.asyncGetHelper(url);
1133+
if (result != null && typeof result === "object" && "data" in result){
1134+
return result.data;
1135+
}
1136+
return null;
1137+
}
1138+
}
1139+
1140+
async addToIndexDB(url: string, response: any){
1141+
await this.initDB();
1142+
const data = await response.json();
1143+
return new Promise<void>((resolve, reject) => {
1144+
const transaction = this.db?.transaction(['urls'], 'readwrite');
1145+
if (transaction === undefined){
1146+
return;
1147+
}
1148+
const store = transaction.objectStore('urls');
1149+
const request = store.add({data, url}); // Index DB follows a {value, key} format, instead of {key, value} format!
1150+
request.onsuccess = () => resolve();
1151+
request.onerror = (event) => reject((event.target as IDBRequest).error);
1152+
});
1153+
}
1154+
1155+
async addToCache(url: string) :Promise<any>{
1156+
let response: Response;
1157+
try {
1158+
response = await fetch(url);
1159+
if (!response.ok) {
1160+
throw new Error('Network response was not ok');
1161+
}
1162+
const response_copy = response.clone();
1163+
await this.addToIndexDB(url, response_copy);
1164+
return response;
1165+
} catch (error) {
1166+
console.error("There was a problem fetching the data:", error);
1167+
}
1168+
}
1169+
1170+
async hasAllKeys(keys: string[]) {
1171+
await this.initDB(); // Ensure the DB is initialized
1172+
return new Promise<boolean> ((resolve, reject) => {
1173+
const transaction = this.db?.transaction(['urls'], 'readonly');
1174+
if (transaction === undefined){
1175+
return;
1176+
}
1177+
const store = transaction.objectStore('urls');
1178+
let allExist = true;
1179+
for (const key of keys) {
1180+
const request = store.get(key);
1181+
// Wait for each request to complete before moving to the next to ensure accurate allExist status
1182+
new Promise<void>((resolve) => {
1183+
request.onsuccess = () => {
1184+
if (request.result === undefined) {
1185+
allExist = false;
1186+
}
1187+
resolve();
1188+
};
1189+
request.onerror = () => {
1190+
allExist = false;
1191+
resolve(); // Resolve to continue checking other keys even if one fails
1192+
};
1193+
});
1194+
if (!allExist){
1195+
break;
1196+
}
1197+
}
1198+
resolve(allExist);
1199+
});
1200+
}
1201+
1202+
async deleteInCache(url: string) {
1203+
await this.initDB(); // Make sure the DB is initialized
1204+
const transaction = this.db?.transaction(['urls'], 'readwrite');
1205+
if (transaction === undefined){
1206+
return;
1207+
}
1208+
const store = transaction.objectStore('urls');
1209+
const request = store.delete(url);
1210+
await new Promise<void>((resolve, reject) => {
1211+
request.onsuccess = () => resolve();
1212+
request.onerror = () => reject(request.error);
1213+
});
1214+
return;
1215+
}
1216+
}
1217+
10551218
/**
10561219
* TVM runtime instance.
10571220
*

0 commit comments

Comments
 (0)