Skip to content

Commit

Permalink
re-structure idb, run only on mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
nofurtherinformation committed Feb 4, 2025
1 parent aadf783 commit bba0215
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 70 deletions.
15 changes: 1 addition & 14 deletions app/src/app/store/mapEditSubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,6 @@ export const getMapEditSubs = (useMapStore: typeof _useMapStore) => {
{equalityFn: shallowCompareArray}
);

const cacheAssignmentsSub = useMapStore.subscribe(
state => state.lastUpdatedHash,
lastUpdatedHash => {
const {zoneAssignments, shatterIds, shatterMappings, mapDocument} = useMapStore.getState();
if (!mapDocument) return;
districtrIdbCache.cacheAssignments(mapDocument.document_id, lastUpdatedHash, {
zoneAssignments,
shatterIds,
shatterMappings,
});
}
);

const fetchAssignmentsSub = useMapStore.subscribe(
state => state.mapDocument,
mapDocument => mapDocument && updateAssignments(mapDocument)
Expand Down Expand Up @@ -130,5 +117,5 @@ export const getMapEditSubs = (useMapStore: typeof _useMapStore) => {
}
);

return [sendZoneUpdatesOnUpdate, fetchAssignmentsSub, healAfterEdits, lockMapOnShatterIdChange, cacheAssignmentsSub];
return [sendZoneUpdatesOnUpdate, fetchAssignmentsSub, healAfterEdits, lockMapOnShatterIdChange];
};
33 changes: 18 additions & 15 deletions app/src/app/utils/api/apiHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const FormatAssignments = () => {
const {allPainted, shatterIds} = useMapStore.getState();
const assignmentsVisited = new Set([...allPainted]);
const assignments: Assignment[] = [];
const subZoneAssignments = new Map();

Array.from(useMapStore.getState().zoneAssignments.entries()).forEach(
// @ts-ignore
Expand All @@ -23,6 +24,7 @@ export const FormatAssignments = () => {
assignmentsVisited.delete(geo_id);
if (lastSentAssignments.get(geo_id) !== zone) {
lastSentAssignments.set(geo_id, zone);
subZoneAssignments.set(geo_id, zone);
assignments.push({
document_id: useMapStore.getState().mapDocument?.document_id || '',
geo_id,
Expand All @@ -42,6 +44,7 @@ export const FormatAssignments = () => {
// @ts-ignore assignment wants to be number
zone: null,
});
subZoneAssignments.set(geo_id, null);
}
});
return assignments;
Expand Down Expand Up @@ -164,21 +167,21 @@ export const getAssignments: (
return null;
}
if (mapDocument) {
const localCached = await districtrIdbCache.getCachedAssignments(mapDocument.document_id);
const lastUpdatedServer = mapDocument.updated_at;
if (
localCached &&
lastUpdatedServer &&
new Date(lastUpdatedServer).toISOString() === new Date(localCached?.updated_at).toISOString()
) {
try {
return {
type: 'local',
documentId: mapDocument.document_id,
data: hydrateStateObjFromObj(localCached.state)
};
} catch (e) {
console.error(e);
if (mapDocument.updated_at) {
const localCached = await districtrIdbCache.getCachedAssignments(
mapDocument.document_id,
mapDocument.updated_at
);
if (localCached) {
try {
return {
type: 'local',
documentId: mapDocument.document_id,
data: localCached
};
} catch (e) {
console.error(e);
}
}
}
return await axios
Expand Down
13 changes: 12 additions & 1 deletion app/src/app/utils/api/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import {useMapStore} from '@/app/store/mapStore';
import {mapMetrics} from './queries';
import {useChartStore} from '@/app/store/chartStore';
import {districtrIdbCache} from '../cache';

export const patchShatter = new MutationObserver(queryClient, {
mutationFn: patchShatterParents,
Expand Down Expand Up @@ -58,6 +59,15 @@ export const patchUpdates = new MutationObserver(queryClient, {
onMutate: () => {
console.log('Updating assignments');
populationAbortController?.abort();

const {zoneAssignments, shatterIds, shatterMappings, mapDocument, lastUpdatedHash} =
useMapStore.getState();
if (!mapDocument) return;
districtrIdbCache.cacheAssignments(mapDocument.document_id, lastUpdatedHash, {
zoneAssignments,
shatterIds,
shatterMappings,
});
},
onError: error => {
console.log('Error updating assignments: ', error);
Expand Down Expand Up @@ -103,7 +113,8 @@ export const document = new MutationObserver(queryClient, {
console.error('Error creating map document: ', error);
},
onSuccess: data => {
const {setMapDocument, setLoadedMapId, setAssignmentsHash, setAppLoadingState} = useMapStore.getState();
const {setMapDocument, setLoadedMapId, setAssignmentsHash, setAppLoadingState} =
useMapStore.getState();
setMapDocument(data);
setLoadedMapId(data.document_id);
setAssignmentsHash(Date.now().toString());
Expand Down
147 changes: 107 additions & 40 deletions app/src/app/utils/cache.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,51 @@
import {DistrictrMap, Assignment} from '@utils/api/apiHandlers';
import {openDB, IDBPDatabase} from 'idb';
import { MapStore } from '@store/mapStore';
import {openDB, IDBPDatabase, DBSchema} from 'idb';
import {MapStore} from '@store/mapStore';
import {NullableZone} from '../constants/types';

type MapStoreCache = {
zoneAssignments: MapStore['zoneAssignments'],
shatterIds: MapStore['shatterIds'],
shatterMappings: MapStore['shatterMappings'],
type DocumentID = string;

interface DistrictrIDB extends DBSchema {
lastUpdated: {
key: string;
value: string;
};
shatterIds: {
key: string;
value: Record<'children' | 'parents', Set<string>>;
};
shatterMappings: {
key: string;
value: Record<string, Set<string>>;
};
assignments: {
key: string;
value: Map<string, NullableZone>;
};
mapViews: {
value: DistrictrMap[];
key: 'views';
};
}

type MapStoreCache = {
zoneAssignments: MapStore['zoneAssignments'];
shatterIds: MapStore['shatterIds'];
shatterMappings: MapStore['shatterMappings'];
};
export const convertStateObjToObj = (state: MapStoreCache) => {
return {
zoneAssignments: Array.from(state.zoneAssignments.entries()),
shatterIds: {
children: Array.from(state.shatterIds.children),
parents: Array.from(state.shatterIds.parents),
},
shatterMappings: Object.entries(state.shatterMappings).map(([key, value]) => [key, Array.from(value)]),
}
}
shatterMappings: Object.entries(state.shatterMappings).map(([key, value]) => [
key,
Array.from(value),
]),
};
};

export const hydrateStateObjFromObj = (obj: any) => {
return {
Expand All @@ -25,64 +54,101 @@ export const hydrateStateObjFromObj = (obj: any) => {
children: new Set(obj.shatterIds.children),
parents: new Set(obj.shatterIds.parents),
},
shatterMappings: Object.fromEntries(obj.shatterMappings.map(([key, value]: [string, string[]]) => [key, new Set(value)])),
shatterMappings: Object.fromEntries(
obj.shatterMappings.map(([key, value]: [string, string[]]) => [key, new Set(value)])
),
} as MapStoreCache;
}
};

class DistrictrIdbCache {
db: IDBPDatabase | undefined = undefined;
db: IDBPDatabase<DistrictrIDB> | undefined = undefined;
constructor() {
this.init();
}
async init() {
if (!this.db) {
this.db = await openDB('districtr', 1.2, {
this.db = await openDB<DistrictrIDB>('districtr', 1.41, {
upgrade(db) {
db.createObjectStore('mapViews');
db.createObjectStore('map_states');
db.createObjectStore('shatterIds');
db.createObjectStore('shatterMappings');
db.createObjectStore('lastUpdated');
db.createObjectStore('assignments');
},
});
}
return this.db!;
}
async cacheAssignments(document_id: string, updated_at: string, state: {
zoneAssignments: MapStore['zoneAssignments'],
shatterIds: MapStore['shatterIds'],
shatterMappings: MapStore['shatterMappings'],
}) {
async cacheAssignments(
document_id: string,
updated_at: string,
state: {
zoneAssignments: MapStore['zoneAssignments'];
shatterIds: MapStore['shatterIds'];
shatterMappings: MapStore['shatterMappings'];
}
) {
const t0 = performance.now();
const db = await this.init();
const tx = db.transaction('map_states', 'readwrite');
const db = await this.init();

const lastUpdatedTx = db.transaction('lastUpdated', 'readwrite');
const lastUpdatedStore = lastUpdatedTx.objectStore('lastUpdated');
const shatterIdsTx = db.transaction('shatterIds', 'readwrite');
const shatterIdsStore = shatterIdsTx.objectStore('shatterIds');
const shatterMappingsTx = db.transaction('shatterMappings', 'readwrite');
const shatterMappingsStore = shatterMappingsTx.objectStore('shatterMappings');
const assignmentTx = db.transaction('assignments', 'readwrite');
const assignmentStore = assignmentTx.objectStore(`assignments`);

await Promise.all([
tx.store.put(updated_at, `${document_id}_updated_at`),
tx.store.put(convertStateObjToObj(state), `${document_id}_state`),
tx.done,
lastUpdatedStore.put(updated_at, document_id),
shatterIdsStore.put(state.shatterIds, document_id),
shatterMappingsStore.put(state.shatterMappings, document_id),
assignmentStore.put(state.zoneAssignments, document_id),
lastUpdatedTx.done,
shatterIdsTx.done,
shatterMappingsTx.done,
assignmentTx.done,
]);
console.log("!!!cached assignments in", performance.now() - t0, "ms");
console.log('cached assignments in', performance.now() - t0, 'ms');
}

async getCachedAssignments(document_id: string) {
async getCachedAssignments(document_id: string, lastUpdatedServer: string) {
const t0 = performance.now();
const db = await this.init();
const [updated_at, state] = await Promise.all([
db.get('map_states', `${document_id}_updated_at`),
db.get('map_states', `${document_id}_state`),
]);
if (updated_at && state) {
console.log("fetched assignments in", performance.now() - t0, "ms");
const db = await this.init();
const localLastUpdated = await db.get('lastUpdated', document_id);
console.log('localLastUpdated', localLastUpdated, 'lastUpdatedServer', lastUpdatedServer, document_id);
if (
!localLastUpdated ||
!lastUpdatedServer ||
new Date(lastUpdatedServer).toISOString() > new Date(localLastUpdated).toISOString()
)
return false;

const [
zoneAssignments,
shatterIds,
shatterMappings,
] = await Promise.all([
db.get('assignments', document_id),
db.get('shatterIds', document_id),
db.get('shatterMappings', document_id),
]);
if (!zoneAssignments || !shatterIds || !shatterMappings) return false;
console.log("Got cached assignments in", performance.now() - t0, 'ms');
return {
updated_at,
state
};
}
zoneAssignments,
shatterIds,
shatterMappings,
}
}

cacheViews = async (views: DistrictrMap[]) => {
const db = await this.init();
const db = await this.init();
await db.put('mapViews', views, 'views');
};
getCachedViews = async () => {
const db = await this.init();
const db = await this.init();
const views = await db.get('mapViews', 'views');
if (views) {
return views as DistrictrMap[];
Expand Down Expand Up @@ -120,4 +186,5 @@ class DistrictrLocalStorageCache {
};
}

export const districtrLocalStorageCache = typeof window === 'undefined' ? {} : new DistrictrLocalStorageCache();
export const districtrLocalStorageCache =
typeof window === 'undefined' ? {} : new DistrictrLocalStorageCache();

0 comments on commit bba0215

Please sign in to comment.