mindoodb 0.0.89 → 0.0.91
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/browser/index.mjs +15 -0
- package/dist/core/BaseMindooDB.d.ts +18 -0
- package/dist/core/BaseMindooDB.d.ts.map +1 -1
- package/dist/core/BaseMindooDB.js +57 -0
- package/dist/core/BaseMindooDB.js.map +1 -1
- package/dist/core/BaseMindooTenant.d.ts +3 -0
- package/dist/core/BaseMindooTenant.d.ts.map +1 -1
- package/dist/core/BaseMindooTenant.js +28 -0
- package/dist/core/BaseMindooTenant.js.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.d.ts +2 -1
- package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantDirectory.js +10 -2
- package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
- package/dist/core/expressions/builder.d.ts +30 -0
- package/dist/core/expressions/builder.d.ts.map +1 -1
- package/dist/core/expressions/builder.js +42 -0
- package/dist/core/expressions/builder.js.map +1 -1
- package/dist/core/expressions/evaluateExpression.d.ts +10 -0
- package/dist/core/expressions/evaluateExpression.d.ts.map +1 -1
- package/dist/core/expressions/evaluateExpression.js +66 -1
- package/dist/core/expressions/evaluateExpression.js.map +1 -1
- package/dist/core/expressions/formulaSource.d.ts.map +1 -1
- package/dist/core/expressions/formulaSource.js +5 -0
- package/dist/core/expressions/formulaSource.js.map +1 -1
- package/dist/core/expressions/index.d.ts +1 -1
- package/dist/core/expressions/index.d.ts.map +1 -1
- package/dist/core/expressions/index.js +2 -1
- package/dist/core/expressions/index.js.map +1 -1
- package/dist/core/expressions/metadata.d.ts +2 -2
- package/dist/core/expressions/metadata.d.ts.map +1 -1
- package/dist/core/expressions/metadata.js +14 -0
- package/dist/core/expressions/metadata.js.map +1 -1
- package/dist/core/expressions/types.d.ts +1 -1
- package/dist/core/expressions/types.d.ts.map +1 -1
- package/dist/core/filing/FilingIndex.d.ts +209 -0
- package/dist/core/filing/FilingIndex.d.ts.map +1 -0
- package/dist/core/filing/FilingIndex.js +689 -0
- package/dist/core/filing/FilingIndex.js.map +1 -0
- package/dist/core/filing/MindooFilingStore.d.ts +200 -0
- package/dist/core/filing/MindooFilingStore.d.ts.map +1 -0
- package/dist/core/filing/MindooFilingStore.js +515 -0
- package/dist/core/filing/MindooFilingStore.js.map +1 -0
- package/dist/core/filing/index.d.ts +4 -0
- package/dist/core/filing/index.d.ts.map +1 -0
- package/dist/core/filing/index.js +22 -0
- package/dist/core/filing/index.js.map +1 -0
- package/dist/core/filing/refs.d.ts +56 -0
- package/dist/core/filing/refs.d.ts.map +1 -0
- package/dist/core/filing/refs.js +109 -0
- package/dist/core/filing/refs.js.map +1 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/indexing/createViewDataProvider.d.ts +6 -0
- package/dist/core/indexing/createViewDataProvider.d.ts.map +1 -1
- package/dist/core/indexing/createViewDataProvider.js +26 -1
- package/dist/core/indexing/createViewDataProvider.js.map +1 -1
- package/dist/core/indexing/summary/DocumentSummaryStore.d.ts +43 -1
- package/dist/core/indexing/summary/DocumentSummaryStore.d.ts.map +1 -1
- package/dist/core/indexing/summary/DocumentSummaryStore.js +302 -2
- package/dist/core/indexing/summary/DocumentSummaryStore.js.map +1 -1
- package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.d.ts +33 -2
- package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.d.ts.map +1 -1
- package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.js +129 -26
- package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.js.map +1 -1
- package/dist/core/indexing/summary/types.d.ts +40 -0
- package/dist/core/indexing/summary/types.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts +27 -2
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js +106 -1
- package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualView.d.ts +70 -5
- package/dist/core/indexing/virtualviews/VirtualView.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualView.js +329 -28
- package/dist/core/indexing/virtualviews/VirtualView.js.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts +4 -22
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.js +175 -129
- package/dist/core/indexing/virtualviews/VirtualViewEntryData.js.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts +28 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewFactory.js +62 -0
- package/dist/core/indexing/virtualviews/VirtualViewFactory.js.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewFilingJoin.d.ts +75 -0
- package/dist/core/indexing/virtualviews/VirtualViewFilingJoin.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewFilingJoin.js +163 -0
- package/dist/core/indexing/virtualviews/VirtualViewFilingJoin.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewMergedNavigator.d.ts +112 -0
- package/dist/core/indexing/virtualviews/VirtualViewMergedNavigator.d.ts.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewMergedNavigator.js +296 -0
- package/dist/core/indexing/virtualviews/VirtualViewMergedNavigator.js.map +1 -0
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.d.ts +2 -0
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.js +10 -9
- package/dist/core/indexing/virtualviews/VirtualViewNavigator.js.map +1 -1
- package/dist/core/indexing/virtualviews/index.d.ts +2 -0
- package/dist/core/indexing/virtualviews/index.d.ts.map +1 -1
- package/dist/core/indexing/virtualviews/index.js +8 -1
- package/dist/core/indexing/virtualviews/index.js.map +1 -1
- package/dist/core/query/executeQuery.d.ts.map +1 -1
- package/dist/core/query/executeQuery.js +4 -0
- package/dist/core/query/executeQuery.js.map +1 -1
- package/dist/core/query/executeQueryInclude.d.ts.map +1 -1
- package/dist/core/query/executeQueryInclude.js +4 -0
- package/dist/core/query/executeQueryInclude.js.map +1 -1
- package/dist/core/query/queryView.d.ts.map +1 -1
- package/dist/core/query/queryView.js +4 -0
- package/dist/core/query/queryView.js.map +1 -1
- package/dist/core/types.d.ts +20 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { CategorizationStyle } from "./types";
|
|
|
6
6
|
import type { LocalCacheStore } from "../../cache/LocalCacheStore";
|
|
7
7
|
import type { CacheManager } from "../../cache/CacheManager";
|
|
8
8
|
import type { MindooDB } from "../../types";
|
|
9
|
+
import { VirtualViewFilingJoin } from "./VirtualViewFilingJoin";
|
|
9
10
|
/** Statistics passed to {@link VirtualView.onDidUpdate} listeners. */
|
|
10
11
|
export interface VirtualViewUpdateStats {
|
|
11
12
|
/** Entries added or replaced by the applied change batch. */
|
|
@@ -48,6 +49,11 @@ export declare class VirtualView {
|
|
|
48
49
|
private readonly viewHasTotalColumns;
|
|
49
50
|
/** Categorization style (categories before/after documents) */
|
|
50
51
|
private categorizationStyle;
|
|
52
|
+
/**
|
|
53
|
+
* Shared child comparators (immutable): one per categorization style and
|
|
54
|
+
* category direction instead of one per entry.
|
|
55
|
+
*/
|
|
56
|
+
private readonly comparators;
|
|
51
57
|
/** Whether the index has been built */
|
|
52
58
|
private indexBuilt;
|
|
53
59
|
/** Root entry of the view tree */
|
|
@@ -58,6 +64,8 @@ export declare class VirtualView {
|
|
|
58
64
|
private entriesByDocId;
|
|
59
65
|
/** Data providers */
|
|
60
66
|
private dataProviderByOrigin;
|
|
67
|
+
/** Filing stores joined into this view (`withFilingStore`), or null. */
|
|
68
|
+
private filingJoin;
|
|
61
69
|
/** Build-time data-source decision per origin (only for builder `withDB` sources) */
|
|
62
70
|
private dataSourceInfoByOrigin;
|
|
63
71
|
/** Entries pending sibling index recalculation */
|
|
@@ -80,10 +88,24 @@ export declare class VirtualView {
|
|
|
80
88
|
private lastCacheFlushAt;
|
|
81
89
|
/** Whether the most recent flush attempt was deferred by the min interval. */
|
|
82
90
|
private lastFlushDeferred;
|
|
91
|
+
private journalPending;
|
|
92
|
+
private journalEntriesSinceSnapshot;
|
|
93
|
+
/** Document entries in the last written/restored snapshot, `null` = none yet. */
|
|
94
|
+
private snapshotDocCount;
|
|
95
|
+
private nextJournalSegment;
|
|
96
|
+
/**
|
|
97
|
+
* Journal chain of this instance. Several VirtualView instances may
|
|
98
|
+
* cache under the same key (several navigators over one definition,
|
|
99
|
+
* several browser tabs); each writes its own chain and a snapshot names
|
|
100
|
+
* the chain that continues it, so chains never interleave.
|
|
101
|
+
*/
|
|
102
|
+
private readonly journalWriterId;
|
|
103
|
+
private replayingJournal;
|
|
83
104
|
private updateListeners;
|
|
84
105
|
private liveUnsubscribes;
|
|
85
106
|
private liveUpdateRunning;
|
|
86
107
|
private liveUpdatePending;
|
|
108
|
+
private liveBoundDbs;
|
|
87
109
|
constructor(columns: VirtualViewColumn[]);
|
|
88
110
|
/**
|
|
89
111
|
* Set the categorization style
|
|
@@ -132,6 +154,20 @@ export declare class VirtualView {
|
|
|
132
154
|
* forwarded to each data provider (see {@link VirtualViewUpdateOptions}).
|
|
133
155
|
*/
|
|
134
156
|
update(options?: VirtualViewUpdateOptions): Promise<void>;
|
|
157
|
+
/**
|
|
158
|
+
* Attach the filing stores this view joins (`withFilingStore`). Called by
|
|
159
|
+
* the view builder before the first update; not intended for user code.
|
|
160
|
+
*/
|
|
161
|
+
setFilingJoin(join: VirtualViewFilingJoin | null): this;
|
|
162
|
+
/** The filing stores joined into this view, or `null`. */
|
|
163
|
+
getFilingJoin(): VirtualViewFilingJoin | null;
|
|
164
|
+
/**
|
|
165
|
+
* Bring the joined filing indexes up to date FIRST (otherwise a fresh
|
|
166
|
+
* build would briefly show rows without their folders) and hand the
|
|
167
|
+
* membership delta to every provider; providers keep it until they have
|
|
168
|
+
* processed it, so an interrupted provider update loses nothing.
|
|
169
|
+
*/
|
|
170
|
+
private refreshFilingJoin;
|
|
135
171
|
/**
|
|
136
172
|
* Update a specific data provider by origin
|
|
137
173
|
*
|
|
@@ -139,6 +175,8 @@ export declare class VirtualView {
|
|
|
139
175
|
* forwarded to the data provider (see {@link VirtualViewUpdateOptions}).
|
|
140
176
|
*/
|
|
141
177
|
updateOrigin(origin: string, options?: VirtualViewUpdateOptions): Promise<void>;
|
|
178
|
+
/** The (shared) comparator for an entry's children. */
|
|
179
|
+
private comparatorFor;
|
|
142
180
|
/**
|
|
143
181
|
* Get the root entry of the view
|
|
144
182
|
*/
|
|
@@ -229,16 +267,34 @@ export declare class VirtualView {
|
|
|
229
267
|
hasDirtyState(): boolean;
|
|
230
268
|
clearDirty(): void;
|
|
231
269
|
/**
|
|
232
|
-
* Export the view state to the cache store
|
|
270
|
+
* Export the view state to the cache store — as snapshot + change
|
|
271
|
+
* journal:
|
|
272
|
+
*
|
|
273
|
+
* - Normally only the change batches applied since the last flush are
|
|
274
|
+
* appended as a small journal segment (`vvj` records), together with
|
|
275
|
+
* the data providers' cursors. Cost: O(changes), not O(view).
|
|
276
|
+
* - The full tree snapshot (`vv`) is rewritten when there is none yet,
|
|
277
|
+
* when the journal holds more than {@link JOURNAL_COMPACT_RATIO} of
|
|
278
|
+
* the snapshot's entries (compaction), and on forced flushes
|
|
279
|
+
* (shutdown/deregister — "on close"). Full snapshots are still
|
|
280
|
+
* throttled by {@link minCacheFlushIntervalMs}; a throttled compaction
|
|
281
|
+
* writes a journal segment instead.
|
|
233
282
|
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
* periodic flush; `force: true` (shutdown/deregister) bypasses the
|
|
237
|
-
* throttle.
|
|
283
|
+
* Restoring loads the snapshot and replays the journal segments written
|
|
284
|
+
* after it.
|
|
238
285
|
*/
|
|
239
286
|
flushToCache(store: LocalCacheStore, options?: {
|
|
240
287
|
force?: boolean;
|
|
241
288
|
}): Promise<number>;
|
|
289
|
+
/** Doc ids of the document entries an origin contributes to the view. */
|
|
290
|
+
private documentIdsOfOrigin;
|
|
291
|
+
private countDocumentEntries;
|
|
292
|
+
private exportProviderStates;
|
|
293
|
+
private journalSegmentId;
|
|
294
|
+
/** Journal segments of this view in the store (all writers). */
|
|
295
|
+
private listJournalSegments;
|
|
296
|
+
/** Drop every journal segment of this view (after a snapshot they are folded in or stale). */
|
|
297
|
+
private deleteJournalSegments;
|
|
242
298
|
/**
|
|
243
299
|
* Serialize the entire tree structure for caching (version 2).
|
|
244
300
|
* Each node stores its sort key, column values, counts, totals,
|
|
@@ -254,6 +310,15 @@ export declare class VirtualView {
|
|
|
254
310
|
* Attempt to restore the view from cache. Returns true on success.
|
|
255
311
|
*/
|
|
256
312
|
restoreFromCache(store: LocalCacheStore): Promise<boolean>;
|
|
313
|
+
/**
|
|
314
|
+
* Replay the journal segments written after the snapshot (numbers >=
|
|
315
|
+
* `journalBase`), in order; the last segment's provider cursors and
|
|
316
|
+
* filing positions win. A missing or corrupt segment ends the replay:
|
|
317
|
+
* later segments would build on a state that never existed, and the
|
|
318
|
+
* providers' cursors of the last good segment make the next update catch
|
|
319
|
+
* up from there.
|
|
320
|
+
*/
|
|
321
|
+
private replayJournal;
|
|
257
322
|
/**
|
|
258
323
|
* Restore the full tree structure directly from a version-2 snapshot.
|
|
259
324
|
* O(n) — no re-sorting, no category re-creation, no count recalculation.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VirtualView.d.ts","sourceRoot":"","sources":["../../../../src/core/indexing/virtualviews/VirtualView.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,KAAK,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACrG,OAAO,EACL,mBAAmB,EAOpB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAEnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,sEAAsE;AACtE,MAAM,WAAW,sBAAsB;IACrC,6DAA6D;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC;IAChC;;;OAGG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"VirtualView.d.ts","sourceRoot":"","sources":["../../../../src/core/indexing/virtualviews/VirtualView.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,12 @@ const VirtualViewDataChange_1 = require("./VirtualViewDataChange");
|
|
|
6
6
|
const ViewEntrySortKey_1 = require("./ViewEntrySortKey");
|
|
7
7
|
const ViewEntrySortKeyComparator_1 = require("./ViewEntrySortKeyComparator");
|
|
8
8
|
const types_1 = require("./types");
|
|
9
|
+
const idGeneration_1 = require("../../utils/idGeneration");
|
|
10
|
+
const VirtualViewFilingJoin_1 = require("./VirtualViewFilingJoin");
|
|
11
|
+
/** Rewrite the snapshot once the journal holds more than this share of its entries. */
|
|
12
|
+
const JOURNAL_COMPACT_RATIO = 0.1;
|
|
13
|
+
/** ...but never for fewer journal entries than this (tiny views). */
|
|
14
|
+
const JOURNAL_MIN_COMPACT_ENTRIES = 100;
|
|
9
15
|
/**
|
|
10
16
|
* VirtualView creates a hierarchical, sorted view of documents from one or more MindooDB instances.
|
|
11
17
|
* Documents are organized into categories (tree structure) with sorting and optional totals.
|
|
@@ -14,6 +20,11 @@ class VirtualView {
|
|
|
14
20
|
constructor(columns) {
|
|
15
21
|
/** Categorization style (categories before/after documents) */
|
|
16
22
|
this.categorizationStyle = types_1.CategorizationStyle.DOCUMENT_THEN_CATEGORY;
|
|
23
|
+
/**
|
|
24
|
+
* Shared child comparators (immutable): one per categorization style and
|
|
25
|
+
* category direction instead of one per entry.
|
|
26
|
+
*/
|
|
27
|
+
this.comparators = new Map();
|
|
17
28
|
/** Whether the index has been built */
|
|
18
29
|
this.indexBuilt = false;
|
|
19
30
|
/** Root entry of the view tree */
|
|
@@ -24,6 +35,8 @@ class VirtualView {
|
|
|
24
35
|
this.entriesByDocId = new Map();
|
|
25
36
|
/** Data providers */
|
|
26
37
|
this.dataProviderByOrigin = new Map();
|
|
38
|
+
/** Filing stores joined into this view (`withFilingStore`), or null. */
|
|
39
|
+
this.filingJoin = null;
|
|
27
40
|
/** Build-time data-source decision per origin (only for builder `withDB` sources) */
|
|
28
41
|
this.dataSourceInfoByOrigin = new Map();
|
|
29
42
|
/** Entries pending sibling index recalculation */
|
|
@@ -47,11 +60,29 @@ class VirtualView {
|
|
|
47
60
|
this.lastCacheFlushAt = 0;
|
|
48
61
|
/** Whether the most recent flush attempt was deferred by the min interval. */
|
|
49
62
|
this.lastFlushDeferred = false;
|
|
63
|
+
// Change journal (see flushToCache): applied change batches since the
|
|
64
|
+
// last flush, appended to the cache as small segments instead of
|
|
65
|
+
// rewriting the whole tree; the snapshot is rewritten only when the
|
|
66
|
+
// journal outgrows JOURNAL_COMPACT_RATIO of it, or on forced flushes.
|
|
67
|
+
this.journalPending = [];
|
|
68
|
+
this.journalEntriesSinceSnapshot = 0;
|
|
69
|
+
/** Document entries in the last written/restored snapshot, `null` = none yet. */
|
|
70
|
+
this.snapshotDocCount = null;
|
|
71
|
+
this.nextJournalSegment = 0;
|
|
72
|
+
/**
|
|
73
|
+
* Journal chain of this instance. Several VirtualView instances may
|
|
74
|
+
* cache under the same key (several navigators over one definition,
|
|
75
|
+
* several browser tabs); each writes its own chain and a snapshot names
|
|
76
|
+
* the chain that continues it, so chains never interleave.
|
|
77
|
+
*/
|
|
78
|
+
this.journalWriterId = (0, idGeneration_1.generateRandomUuid)().replace(/-/g, "").slice(0, 12);
|
|
79
|
+
this.replayingJournal = false;
|
|
50
80
|
// Live-view support (see bindTo/onDidUpdate)
|
|
51
81
|
this.updateListeners = new Set();
|
|
52
82
|
this.liveUnsubscribes = [];
|
|
53
83
|
this.liveUpdateRunning = false;
|
|
54
84
|
this.liveUpdatePending = false;
|
|
85
|
+
this.liveBoundDbs = new Set();
|
|
55
86
|
this.columns = [...columns];
|
|
56
87
|
this.categoryColumns = [];
|
|
57
88
|
this.sortColumns = [];
|
|
@@ -164,10 +195,44 @@ class VirtualView {
|
|
|
164
195
|
* forwarded to each data provider (see {@link VirtualViewUpdateOptions}).
|
|
165
196
|
*/
|
|
166
197
|
async update(options) {
|
|
198
|
+
await this.refreshFilingJoin();
|
|
167
199
|
for (const provider of this.dataProviderByOrigin.values()) {
|
|
168
200
|
await provider.update(options);
|
|
169
201
|
}
|
|
170
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Attach the filing stores this view joins (`withFilingStore`). Called by
|
|
205
|
+
* the view builder before the first update; not intended for user code.
|
|
206
|
+
*/
|
|
207
|
+
setFilingJoin(join) {
|
|
208
|
+
this.filingJoin = join;
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
/** The filing stores joined into this view, or `null`. */
|
|
212
|
+
getFilingJoin() {
|
|
213
|
+
return this.filingJoin;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Bring the joined filing indexes up to date FIRST (otherwise a fresh
|
|
217
|
+
* build would briefly show rows without their folders) and hand the
|
|
218
|
+
* membership delta to every provider; providers keep it until they have
|
|
219
|
+
* processed it, so an interrupted provider update loses nothing.
|
|
220
|
+
*/
|
|
221
|
+
async refreshFilingJoin() {
|
|
222
|
+
if (!this.filingJoin) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const changes = await this.filingJoin.refresh();
|
|
226
|
+
if (changes !== "all" && changes.size === 0) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
for (const provider of this.dataProviderByOrigin.values()) {
|
|
230
|
+
if ((0, VirtualViewFilingJoin_1.isFilingAwareDataProvider)(provider)) {
|
|
231
|
+
provider.onFilingChanges(changes);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
this.markViewDirty();
|
|
235
|
+
}
|
|
171
236
|
/**
|
|
172
237
|
* Update a specific data provider by origin
|
|
173
238
|
*
|
|
@@ -177,9 +242,26 @@ class VirtualView {
|
|
|
177
242
|
async updateOrigin(origin, options) {
|
|
178
243
|
const provider = this.dataProviderByOrigin.get(origin);
|
|
179
244
|
if (provider) {
|
|
245
|
+
await this.refreshFilingJoin();
|
|
180
246
|
await provider.update(options);
|
|
181
247
|
}
|
|
182
248
|
}
|
|
249
|
+
/** The (shared) comparator for an entry's children. */
|
|
250
|
+
comparatorFor(categoryOrderDescending, docOrderDescending = this.docOrderDescending) {
|
|
251
|
+
const shareable = docOrderDescending === this.docOrderDescending ||
|
|
252
|
+
(docOrderDescending.length === this.docOrderDescending.length &&
|
|
253
|
+
docOrderDescending.every((value, index) => value === this.docOrderDescending[index]));
|
|
254
|
+
if (!shareable) {
|
|
255
|
+
return new ViewEntrySortKeyComparator_1.ViewEntrySortKeyComparator(this.categorizationStyle, categoryOrderDescending, docOrderDescending);
|
|
256
|
+
}
|
|
257
|
+
const key = `${this.categorizationStyle}|${categoryOrderDescending ? 1 : 0}`;
|
|
258
|
+
let comparator = this.comparators.get(key);
|
|
259
|
+
if (!comparator) {
|
|
260
|
+
comparator = new ViewEntrySortKeyComparator_1.ViewEntrySortKeyComparator(this.categorizationStyle, categoryOrderDescending, this.docOrderDescending);
|
|
261
|
+
this.comparators.set(key, comparator);
|
|
262
|
+
}
|
|
263
|
+
return comparator;
|
|
264
|
+
}
|
|
183
265
|
/**
|
|
184
266
|
* Get the root entry of the view
|
|
185
267
|
*/
|
|
@@ -188,10 +270,10 @@ class VirtualView {
|
|
|
188
270
|
// Create root comparator
|
|
189
271
|
let rootChildComparator;
|
|
190
272
|
if (this.categoryColumns.length === 0) {
|
|
191
|
-
rootChildComparator =
|
|
273
|
+
rootChildComparator = this.comparatorFor(false);
|
|
192
274
|
}
|
|
193
275
|
else {
|
|
194
|
-
rootChildComparator =
|
|
276
|
+
rootChildComparator = this.comparatorFor(this.categoryColumns[0].sorting === types_1.ColumnSorting.DESCENDING);
|
|
195
277
|
}
|
|
196
278
|
const rootSortKey = ViewEntrySortKey_1.ViewEntrySortKey.createSortKey(true, [], types_1.ORIGIN_VIRTUALVIEW, "root");
|
|
197
279
|
const rootDocId = this.createNewCategoryId();
|
|
@@ -220,6 +302,15 @@ class VirtualView {
|
|
|
220
302
|
applyChanges(change) {
|
|
221
303
|
this.indexBuilt = true;
|
|
222
304
|
let indexChanged = false;
|
|
305
|
+
if (this.cacheManager && !this.replayingJournal && change.hasChanges()) {
|
|
306
|
+
this.journalPending.push({
|
|
307
|
+
o: change.origin,
|
|
308
|
+
r: Array.from(change.getRemovals()),
|
|
309
|
+
a: Array.from(change.getAdditions().values(), (entry) => entry.decryptionKeyId !== undefined
|
|
310
|
+
? [entry.docId, entry.values, entry.decryptionKeyId]
|
|
311
|
+
: [entry.docId, entry.values]),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
223
314
|
const origin = change.origin;
|
|
224
315
|
const categoryEntriesToCheck = [];
|
|
225
316
|
// Collect all doc IDs to remove (both explicit removals and additions that need re-adding)
|
|
@@ -346,9 +437,32 @@ class VirtualView {
|
|
|
346
437
|
if (!db.addChangeListener) {
|
|
347
438
|
throw new Error("This MindooDB instance does not support change listeners.");
|
|
348
439
|
}
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
440
|
+
const unsubscribes = [
|
|
441
|
+
db.addChangeListener(() => {
|
|
442
|
+
this.scheduleLiveUpdate();
|
|
443
|
+
}),
|
|
444
|
+
];
|
|
445
|
+
// Filing changes live in the filing databases: bind those too (once
|
|
446
|
+
// per view), so filing/moving documents updates the view live.
|
|
447
|
+
for (const filingDb of this.filingJoin?.getDatabases() ?? []) {
|
|
448
|
+
if (filingDb === db || this.liveBoundDbs.has(filingDb) || !filingDb.addChangeListener) {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
this.liveBoundDbs.add(filingDb);
|
|
452
|
+
const unsubscribeFiling = filingDb.addChangeListener(() => {
|
|
453
|
+
this.scheduleLiveUpdate();
|
|
454
|
+
});
|
|
455
|
+
unsubscribes.push(() => {
|
|
456
|
+
unsubscribeFiling();
|
|
457
|
+
this.liveBoundDbs.delete(filingDb);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
this.liveBoundDbs.add(db);
|
|
461
|
+
const unsubscribe = () => {
|
|
462
|
+
for (const fn of unsubscribes)
|
|
463
|
+
fn();
|
|
464
|
+
this.liveBoundDbs.delete(db);
|
|
465
|
+
};
|
|
352
466
|
this.liveUnsubscribes.push(unsubscribe);
|
|
353
467
|
this.scheduleLiveUpdate();
|
|
354
468
|
return () => {
|
|
@@ -435,7 +549,7 @@ class VirtualView {
|
|
|
435
549
|
docSortValues.push(this.getFirstListValue(value));
|
|
436
550
|
}
|
|
437
551
|
const sortKey = ViewEntrySortKey_1.ViewEntrySortKey.createSortKey(false, docSortValues, origin, docId);
|
|
438
|
-
const childComparator =
|
|
552
|
+
const childComparator = this.comparatorFor(false);
|
|
439
553
|
const newDocEntry = new VirtualViewEntryData_1.VirtualViewEntryData(this, targetParent, origin, docId, sortKey, childComparator, decryptionKeyId);
|
|
440
554
|
newDocEntry.setColumnValues(columnValues);
|
|
441
555
|
if (targetParent.addChildEntry(newDocEntry)) {
|
|
@@ -492,7 +606,7 @@ class VirtualView {
|
|
|
492
606
|
const nextCatColumn = remainingColumns.length > 0 ? remainingColumns[0] : null;
|
|
493
607
|
childCategoryDescending = nextCatColumn?.sorting === types_1.ColumnSorting.DESCENDING;
|
|
494
608
|
}
|
|
495
|
-
const childComparator =
|
|
609
|
+
const childComparator = this.comparatorFor(childCategoryDescending);
|
|
496
610
|
const newCategoryId = this.createNewCategoryId();
|
|
497
611
|
existingCategory = new VirtualViewEntryData_1.VirtualViewEntryData(this, currentParent, types_1.ORIGIN_VIRTUALVIEW, newCategoryId, categorySortKey, childComparator);
|
|
498
612
|
const catColValues = {};
|
|
@@ -526,7 +640,7 @@ class VirtualView {
|
|
|
526
640
|
if (!existingCategory) {
|
|
527
641
|
const nextCatColumn = remainingColumns.length > 0 ? remainingColumns[0] : null;
|
|
528
642
|
const childCategoryDescending = nextCatColumn?.sorting === types_1.ColumnSorting.DESCENDING;
|
|
529
|
-
const childComparator =
|
|
643
|
+
const childComparator = this.comparatorFor(childCategoryDescending);
|
|
530
644
|
const newCategoryId = this.createNewCategoryId();
|
|
531
645
|
existingCategory = new VirtualViewEntryData_1.VirtualViewEntryData(this, targetParent, types_1.ORIGIN_VIRTUALVIEW, newCategoryId, categorySortKey, childComparator);
|
|
532
646
|
const catColValues = {};
|
|
@@ -707,46 +821,151 @@ class VirtualView {
|
|
|
707
821
|
this.isDirty = false;
|
|
708
822
|
}
|
|
709
823
|
/**
|
|
710
|
-
* Export the view state to the cache store
|
|
824
|
+
* Export the view state to the cache store — as snapshot + change
|
|
825
|
+
* journal:
|
|
826
|
+
*
|
|
827
|
+
* - Normally only the change batches applied since the last flush are
|
|
828
|
+
* appended as a small journal segment (`vvj` records), together with
|
|
829
|
+
* the data providers' cursors. Cost: O(changes), not O(view).
|
|
830
|
+
* - The full tree snapshot (`vv`) is rewritten when there is none yet,
|
|
831
|
+
* when the journal holds more than {@link JOURNAL_COMPACT_RATIO} of
|
|
832
|
+
* the snapshot's entries (compaction), and on forced flushes
|
|
833
|
+
* (shutdown/deregister — "on close"). Full snapshots are still
|
|
834
|
+
* throttled by {@link minCacheFlushIntervalMs}; a throttled compaction
|
|
835
|
+
* writes a journal segment instead.
|
|
711
836
|
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
* periodic flush; `force: true` (shutdown/deregister) bypasses the
|
|
715
|
-
* throttle.
|
|
837
|
+
* Restoring loads the snapshot and replays the journal segments written
|
|
838
|
+
* after it.
|
|
716
839
|
*/
|
|
717
840
|
async flushToCache(store, options) {
|
|
718
841
|
const now = Date.now();
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
842
|
+
const pending = this.journalPending;
|
|
843
|
+
const pendingCount = pending.reduce((sum, change) => sum + change.r.length + change.a.length, 0);
|
|
844
|
+
const journalTooLarge = this.snapshotDocCount !== null &&
|
|
845
|
+
this.journalEntriesSinceSnapshot + pendingCount >
|
|
846
|
+
Math.max(JOURNAL_MIN_COMPACT_ENTRIES, this.snapshotDocCount * JOURNAL_COMPACT_RATIO);
|
|
847
|
+
const wantSnapshot = options?.force === true || this.snapshotDocCount === null || journalTooLarge;
|
|
848
|
+
const throttled = !options?.force &&
|
|
849
|
+
this.lastCacheFlushAt > 0 &&
|
|
850
|
+
now - this.lastCacheFlushAt < this.minCacheFlushIntervalMs;
|
|
851
|
+
if (wantSnapshot && throttled && this.snapshotDocCount === null) {
|
|
852
|
+
// No snapshot to append to yet: defer as before.
|
|
722
853
|
this.lastFlushDeferred = true;
|
|
723
854
|
return 0;
|
|
724
855
|
}
|
|
725
856
|
this.lastFlushDeferred = false;
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
857
|
+
if (wantSnapshot && !throttled) {
|
|
858
|
+
const journalBase = this.nextJournalSegment;
|
|
859
|
+
const state = this.exportCacheState(journalBase);
|
|
860
|
+
await store.put("vv", this.getCachePrefix(), state);
|
|
861
|
+
this.journalPending = [];
|
|
862
|
+
this.journalEntriesSinceSnapshot = 0;
|
|
863
|
+
this.snapshotDocCount = this.countDocumentEntries();
|
|
864
|
+
this.lastCacheFlushAt = now;
|
|
865
|
+
// Every existing segment is either folded into this snapshot or
|
|
866
|
+
// belongs to a chain this snapshot replaced (another instance, an
|
|
867
|
+
// older state).
|
|
868
|
+
await this.deleteJournalSegments(store);
|
|
869
|
+
return 1;
|
|
870
|
+
}
|
|
871
|
+
// Provider states without their `knownDocIds`: those equal the view's
|
|
872
|
+
// document entries of the provider's origin and are rebuilt from the
|
|
873
|
+
// tree on replay — writing them per segment would make every flush
|
|
874
|
+
// O(view) again.
|
|
875
|
+
const providerStates = this.exportProviderStates();
|
|
876
|
+
for (const [origin, state] of Object.entries(providerStates)) {
|
|
877
|
+
if (state && typeof state === "object" && Array.isArray(state.knownDocIds)) {
|
|
878
|
+
providerStates[origin] = { ...state, knownDocIds: undefined, knownDocIdsFromTree: true };
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const segment = {
|
|
882
|
+
changes: pending,
|
|
883
|
+
providerStates,
|
|
884
|
+
filing: this.filingJoin?.exportState(),
|
|
885
|
+
categoryIdCounter: this.categoryIdCounter,
|
|
886
|
+
};
|
|
887
|
+
const segmentNo = this.nextJournalSegment;
|
|
888
|
+
await store.put("vvj", this.journalSegmentId(this.journalWriterId, segmentNo), new TextEncoder().encode(JSON.stringify(segment)));
|
|
889
|
+
this.nextJournalSegment = segmentNo + 1;
|
|
890
|
+
this.journalPending = this.journalPending.slice(pending.length);
|
|
891
|
+
this.journalEntriesSinceSnapshot += pendingCount;
|
|
729
892
|
return 1;
|
|
730
893
|
}
|
|
731
|
-
/**
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
894
|
+
/** Doc ids of the document entries an origin contributes to the view. */
|
|
895
|
+
documentIdsOfOrigin(origin) {
|
|
896
|
+
const docIds = [];
|
|
897
|
+
for (const [scopedKey, entries] of this.entriesByDocId) {
|
|
898
|
+
const parsed = (0, types_1.parseScopedDocIdKey)(scopedKey);
|
|
899
|
+
if (parsed.origin === origin && entries.some((entry) => entry.isDocument())) {
|
|
900
|
+
docIds.push(parsed.docId);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return docIds;
|
|
904
|
+
}
|
|
905
|
+
countDocumentEntries() {
|
|
906
|
+
let count = 0;
|
|
907
|
+
for (const entries of this.entriesByDocId.values()) {
|
|
908
|
+
if (entries.some((entry) => entry.isDocument())) {
|
|
909
|
+
count++;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return count;
|
|
913
|
+
}
|
|
914
|
+
exportProviderStates() {
|
|
738
915
|
const providerStates = {};
|
|
739
916
|
for (const [origin, provider] of this.dataProviderByOrigin) {
|
|
740
917
|
if ("exportCacheState" in provider && typeof provider.exportCacheState === "function") {
|
|
741
918
|
providerStates[origin] = provider.exportCacheState();
|
|
742
919
|
}
|
|
743
920
|
}
|
|
921
|
+
return providerStates;
|
|
922
|
+
}
|
|
923
|
+
journalSegmentId(writer, segment) {
|
|
924
|
+
return `${this.getCachePrefix()}/${writer}/${String(segment).padStart(10, "0")}`;
|
|
925
|
+
}
|
|
926
|
+
/** Journal segments of this view in the store (all writers). */
|
|
927
|
+
async listJournalSegments(store) {
|
|
928
|
+
const prefix = `${this.getCachePrefix()}/`;
|
|
929
|
+
const segments = [];
|
|
930
|
+
for (const id of await store.list("vvj")) {
|
|
931
|
+
if (!id.startsWith(prefix))
|
|
932
|
+
continue;
|
|
933
|
+
const [writer, number, ...rest] = id.slice(prefix.length).split("/");
|
|
934
|
+
const segment = Number(number);
|
|
935
|
+
if (writer && rest.length === 0 && Number.isInteger(segment)) {
|
|
936
|
+
segments.push({ writer, segment });
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return segments.sort((a, b) => a.segment - b.segment);
|
|
940
|
+
}
|
|
941
|
+
/** Drop every journal segment of this view (after a snapshot they are folded in or stale). */
|
|
942
|
+
async deleteJournalSegments(store) {
|
|
943
|
+
try {
|
|
944
|
+
for (const { writer, segment } of await this.listJournalSegments(store)) {
|
|
945
|
+
await store.delete("vvj", this.journalSegmentId(writer, segment));
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
catch {
|
|
949
|
+
// Stale segments are ignored on restore (other chain or before the base).
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Serialize the entire tree structure for caching (version 2).
|
|
954
|
+
* Each node stores its sort key, column values, counts, totals,
|
|
955
|
+
* and children — so restoration is O(n) without re-sorting.
|
|
956
|
+
*/
|
|
957
|
+
exportCacheState(journalBase) {
|
|
958
|
+
const root = this.getRoot();
|
|
959
|
+
const providerStates = this.exportProviderStates();
|
|
744
960
|
const snapshot = {
|
|
745
961
|
version: 2,
|
|
962
|
+
journalWriter: this.journalWriterId,
|
|
963
|
+
journalBase,
|
|
746
964
|
categoryIdCounter: this.categoryIdCounter,
|
|
747
965
|
categorizationStyle: this.categorizationStyle,
|
|
748
966
|
docOrderDescending: [...this.docOrderDescending],
|
|
749
967
|
providerStates,
|
|
968
|
+
filing: this.filingJoin?.exportState(),
|
|
750
969
|
tree: this.serializeNode(root),
|
|
751
970
|
};
|
|
752
971
|
return new TextEncoder().encode(JSON.stringify(snapshot));
|
|
@@ -812,7 +1031,11 @@ class VirtualView {
|
|
|
812
1031
|
return false;
|
|
813
1032
|
const snapshot = JSON.parse(new TextDecoder().decode(bytes));
|
|
814
1033
|
if (snapshot.version === 2) {
|
|
815
|
-
|
|
1034
|
+
if (!this.restoreFromTreeSnapshot(snapshot)) {
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
await this.replayJournal(store, typeof snapshot.journalWriter === "string" ? snapshot.journalWriter : null, typeof snapshot.journalBase === "number" ? snapshot.journalBase : 0);
|
|
1038
|
+
return true;
|
|
816
1039
|
}
|
|
817
1040
|
return false;
|
|
818
1041
|
}
|
|
@@ -820,6 +1043,83 @@ class VirtualView {
|
|
|
820
1043
|
return false;
|
|
821
1044
|
}
|
|
822
1045
|
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Replay the journal segments written after the snapshot (numbers >=
|
|
1048
|
+
* `journalBase`), in order; the last segment's provider cursors and
|
|
1049
|
+
* filing positions win. A missing or corrupt segment ends the replay:
|
|
1050
|
+
* later segments would build on a state that never existed, and the
|
|
1051
|
+
* providers' cursors of the last good segment make the next update catch
|
|
1052
|
+
* up from there.
|
|
1053
|
+
*/
|
|
1054
|
+
async replayJournal(store, writer, journalBase) {
|
|
1055
|
+
// This instance continues with its OWN chain, so its first flush writes
|
|
1056
|
+
// a fresh snapshot (another instance may have restored the same state
|
|
1057
|
+
// and continue the restored chain concurrently otherwise).
|
|
1058
|
+
this.snapshotDocCount = null;
|
|
1059
|
+
this.journalEntriesSinceSnapshot = 0;
|
|
1060
|
+
if (writer === null) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
let segments;
|
|
1064
|
+
try {
|
|
1065
|
+
segments = (await this.listJournalSegments(store))
|
|
1066
|
+
.filter((entry) => entry.writer === writer && entry.segment >= journalBase)
|
|
1067
|
+
.map((entry) => entry.segment);
|
|
1068
|
+
}
|
|
1069
|
+
catch {
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
let expected = journalBase;
|
|
1073
|
+
this.replayingJournal = true;
|
|
1074
|
+
// Only the last replayed segment's provider cursors matter.
|
|
1075
|
+
const lastProviderStates = new Map();
|
|
1076
|
+
try {
|
|
1077
|
+
for (const segmentNo of segments) {
|
|
1078
|
+
if (segmentNo !== expected) {
|
|
1079
|
+
break; // gap
|
|
1080
|
+
}
|
|
1081
|
+
const bytes = await store.get("vvj", this.journalSegmentId(writer, segmentNo));
|
|
1082
|
+
if (!bytes)
|
|
1083
|
+
break;
|
|
1084
|
+
const segment = JSON.parse(new TextDecoder().decode(bytes));
|
|
1085
|
+
for (const serialized of segment.changes ?? []) {
|
|
1086
|
+
const change = new VirtualViewDataChange_1.VirtualViewDataChange(serialized.o);
|
|
1087
|
+
for (const docId of serialized.r)
|
|
1088
|
+
change.removeEntry(docId);
|
|
1089
|
+
for (const [docId, values, decryptionKeyId] of serialized.a) {
|
|
1090
|
+
change.addEntry(docId, values, decryptionKeyId);
|
|
1091
|
+
}
|
|
1092
|
+
this.applyChanges(change);
|
|
1093
|
+
this.journalEntriesSinceSnapshot += serialized.r.length + serialized.a.length;
|
|
1094
|
+
}
|
|
1095
|
+
for (const [origin, rawState] of Object.entries(segment.providerStates ?? {})) {
|
|
1096
|
+
lastProviderStates.set(origin, rawState);
|
|
1097
|
+
}
|
|
1098
|
+
this.filingJoin?.importState(segment.filing);
|
|
1099
|
+
if (typeof segment.categoryIdCounter === "number") {
|
|
1100
|
+
this.categoryIdCounter = Math.max(this.categoryIdCounter, segment.categoryIdCounter);
|
|
1101
|
+
}
|
|
1102
|
+
expected = segmentNo + 1;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
catch {
|
|
1106
|
+
// Keep what was replayed; see above.
|
|
1107
|
+
}
|
|
1108
|
+
finally {
|
|
1109
|
+
for (const [origin, rawState] of lastProviderStates) {
|
|
1110
|
+
const provider = this.dataProviderByOrigin.get(origin);
|
|
1111
|
+
let state = rawState;
|
|
1112
|
+
if (state && typeof state === "object" && state.knownDocIdsFromTree) {
|
|
1113
|
+
state = { ...state, knownDocIds: this.documentIdsOfOrigin(origin) };
|
|
1114
|
+
}
|
|
1115
|
+
if (provider && "importCacheState" in provider && typeof provider.importCacheState === "function") {
|
|
1116
|
+
provider.importCacheState(state);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
this.replayingJournal = false;
|
|
1120
|
+
this.isDirty = false;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
823
1123
|
/**
|
|
824
1124
|
* Restore the full tree structure directly from a version-2 snapshot.
|
|
825
1125
|
* O(n) — no re-sorting, no category re-creation, no count recalculation.
|
|
@@ -841,6 +1141,7 @@ class VirtualView {
|
|
|
841
1141
|
const treeData = snapshot.tree;
|
|
842
1142
|
if (!treeData)
|
|
843
1143
|
return false;
|
|
1144
|
+
this.filingJoin?.importState(snapshot.filing);
|
|
844
1145
|
this.rootEntry = this.deserializeNode(treeData, null, docOrderDesc);
|
|
845
1146
|
this.indexBuilt = true;
|
|
846
1147
|
return true;
|
|
@@ -852,7 +1153,7 @@ class VirtualView {
|
|
|
852
1153
|
deserializeNode(data, parent, docOrderDesc) {
|
|
853
1154
|
const buildEntry = (nodeData, nodeParent) => {
|
|
854
1155
|
const sk = ViewEntrySortKey_1.ViewEntrySortKey.createSortKey(nodeData.sk.c, nodeData.sk.v, nodeData.sk.o, nodeData.sk.d);
|
|
855
|
-
const comparator =
|
|
1156
|
+
const comparator = this.comparatorFor(nodeData.cod === true, docOrderDesc);
|
|
856
1157
|
const entry = new VirtualViewEntryData_1.VirtualViewEntryData(this, nodeParent, nodeData.o, nodeData.d, sk, comparator, nodeData.dk);
|
|
857
1158
|
entry.setColumnValues(nodeData.cv ?? {});
|
|
858
1159
|
entry.setIndentLevels(nodeData.il ?? 0);
|