Skip to content

Commit 2c22bd9

Browse files
committed
perf: cache dataview lookups to speed up queries
1 parent 71797a0 commit 2c22bd9

File tree

3 files changed

+61
-18
lines changed

3 files changed

+61
-18
lines changed

src/main.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ export default class DayPlanner extends Plugin {
7777
private store!: AppStore;
7878

7979
async onload() {
80-
this.dataviewFacade = new DataviewFacade(() => getAPI(this.app));
80+
this.dataviewFacade = new DataviewFacade(
81+
() => getAPI(this.app),
82+
this.app.vault,
83+
);
8184
const initialPluginData: PluginData = {
8285
...defaultSettings,
8386
...(await this.loadData()),

src/service/dataview-facade.ts

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,78 @@
1+
import { partition } from "lodash/fp";
2+
import type { Vault } from "obsidian";
13
import { STask, type DataviewApi } from "obsidian-dataview";
24

35
import {
46
type Scheduler,
57
createBackgroundBatchScheduler,
68
} from "../util/scheduler";
9+
import { isNotVoid } from "typed-assert";
10+
11+
interface STaskCacheEntry {
12+
mtime: number;
13+
tasks: STask[];
14+
}
715

816
export class DataviewFacade {
917
private readonly scheduler: Scheduler<STask[]> =
1018
createBackgroundBatchScheduler({
1119
timeRemainingLowerLimit: 5,
1220
});
21+
private readonly taskCache = new Map<string, STaskCacheEntry>();
22+
23+
constructor(
24+
private readonly getDataview: () => DataviewApi | undefined,
25+
private readonly vault: Vault,
26+
) {}
27+
28+
private isCached = (path: string) => {
29+
const cacheForPath = this.taskCache.get(path);
30+
31+
if (!cacheForPath) {
32+
return false;
33+
}
1334

14-
constructor(private readonly getDataview: () => DataviewApi | undefined) {}
35+
const fileInVault = this.vault.getFileByPath(path);
36+
37+
isNotVoid(fileInVault);
38+
39+
return cacheForPath.mtime === fileInVault.stat.mtime;
40+
};
1541

1642
getAllTasksFrom = async (source: string) => {
17-
return new Promise<STask[]>((resolve) => {
18-
const paths: string[] = this.getDataview()?.pagePaths(source).array();
43+
const pathsForSource: string[] = this.getDataview()
44+
?.pagePaths(source)
45+
.array();
1946

20-
const pageQueries: Array<() => STask[]> = paths.map(
21-
(path) => () =>
22-
this.getDataview()?.page(path)?.file.tasks.array() || [],
23-
);
47+
const [pathsWithValidCache, pathsToBeQueried] = partition(
48+
this.isCached,
49+
pathsForSource,
50+
);
2451

52+
const cachedTasks = pathsWithValidCache.map(
53+
(it) => this.taskCache.get(it)?.tasks || [],
54+
);
55+
56+
const pageQueries: Array<() => STask[]> = pathsToBeQueried.map(
57+
(path) => () => {
58+
const tasks = this.getDataview()?.page(path)?.file.tasks.array() || [];
59+
const mtime = this.vault.getFileByPath(path)?.stat.mtime;
60+
61+
if (mtime && tasks.length > 0) {
62+
this.taskCache.set(path, { mtime, tasks });
63+
}
64+
65+
return tasks;
66+
},
67+
);
68+
69+
return new Promise<STask[]>((resolve) => {
2570
this.scheduler.enqueueTasks(pageQueries, (results) => {
26-
resolve(results.flat());
71+
resolve(results.concat(cachedTasks).flat());
2772
});
2873
});
2974
};
3075

31-
getPathsFrom = (source: string) => {
32-
return this.getDataview()?.pages(source).file.path.array() || [];
33-
};
34-
3576
getAllListsFrom = (source: string) => {
3677
return this.getDataview()?.pages(source).file.lists.array() || [];
3778
};

tests/store/redux.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,23 @@ import {
1010
import { makeStore } from "../../src/redux/store";
1111
import { DataviewFacade } from "../../src/service/dataview-facade";
1212
import { createSTask } from "../../src/util/dataview";
13+
import type { Vault } from "obsidian";
1314

1415
export async function getIcalFixture(file: string) {
1516
return readFile(`fixtures/${file}.txt`, {
1617
encoding: "utf8",
1718
});
1819
}
1920

21+
export class FakeVault {}
22+
2023
export class FakeDataviewFacade extends DataviewFacade {
2124
private readonly fixtures: Record<string, Array<STask>>;
2225

2326
constructor() {
2427
super(() => {
2528
throw new Error("Incorrect usage");
26-
});
29+
}, new FakeVault() as Vault);
2730

2831
// this.fixtures = readFileSync(`${fixturesPath}/dataview-fixtures.json`);
2932
this.fixtures = {};
@@ -32,10 +35,6 @@ export class FakeDataviewFacade extends DataviewFacade {
3235
getTasksFromPath = (path: string) => {
3336
return this.fixtures[path];
3437
};
35-
36-
getPathsFrom = (source: string) => {
37-
return Object.keys(this.fixtures);
38-
};
3938
}
4039

4140
describe("Search", () => {

0 commit comments

Comments
 (0)