mindoodb 0.0.88 → 0.0.90

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.
Files changed (132) hide show
  1. package/README.md +1 -0
  2. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +4 -1
  3. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
  4. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +8 -0
  5. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
  6. package/dist/browser/index.mjs +17 -0
  7. package/dist/core/BaseMindooDB.d.ts +14 -0
  8. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  9. package/dist/core/BaseMindooDB.js +42 -0
  10. package/dist/core/BaseMindooDB.js.map +1 -1
  11. package/dist/core/BaseMindooTenant.d.ts +3 -0
  12. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  13. package/dist/core/BaseMindooTenant.js +28 -0
  14. package/dist/core/BaseMindooTenant.js.map +1 -1
  15. package/dist/core/BaseMindooTenantDirectory.d.ts +5 -0
  16. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  17. package/dist/core/BaseMindooTenantDirectory.js +29 -1
  18. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  19. package/dist/core/accesscontrol/directoryProjection.d.ts.map +1 -1
  20. package/dist/core/accesscontrol/directoryProjection.js +4 -0
  21. package/dist/core/accesscontrol/directoryProjection.js.map +1 -1
  22. package/dist/core/accesscontrol/types.d.ts +25 -3
  23. package/dist/core/accesscontrol/types.d.ts.map +1 -1
  24. package/dist/core/accesscontrol/types.js +27 -6
  25. package/dist/core/accesscontrol/types.js.map +1 -1
  26. package/dist/core/builtinDbInvariants.d.ts +5 -1
  27. package/dist/core/builtinDbInvariants.d.ts.map +1 -1
  28. package/dist/core/builtinDbInvariants.js +5 -1
  29. package/dist/core/builtinDbInvariants.js.map +1 -1
  30. package/dist/core/expressions/builder.d.ts +30 -0
  31. package/dist/core/expressions/builder.d.ts.map +1 -1
  32. package/dist/core/expressions/builder.js +42 -0
  33. package/dist/core/expressions/builder.js.map +1 -1
  34. package/dist/core/expressions/evaluateExpression.d.ts +10 -0
  35. package/dist/core/expressions/evaluateExpression.d.ts.map +1 -1
  36. package/dist/core/expressions/evaluateExpression.js +66 -1
  37. package/dist/core/expressions/evaluateExpression.js.map +1 -1
  38. package/dist/core/expressions/formulaSource.d.ts.map +1 -1
  39. package/dist/core/expressions/formulaSource.js +5 -0
  40. package/dist/core/expressions/formulaSource.js.map +1 -1
  41. package/dist/core/expressions/index.d.ts +1 -1
  42. package/dist/core/expressions/index.d.ts.map +1 -1
  43. package/dist/core/expressions/index.js +2 -1
  44. package/dist/core/expressions/index.js.map +1 -1
  45. package/dist/core/expressions/metadata.d.ts +2 -2
  46. package/dist/core/expressions/metadata.d.ts.map +1 -1
  47. package/dist/core/expressions/metadata.js +14 -0
  48. package/dist/core/expressions/metadata.js.map +1 -1
  49. package/dist/core/expressions/types.d.ts +1 -1
  50. package/dist/core/expressions/types.d.ts.map +1 -1
  51. package/dist/core/filing/FilingIndex.d.ts +209 -0
  52. package/dist/core/filing/FilingIndex.d.ts.map +1 -0
  53. package/dist/core/filing/FilingIndex.js +689 -0
  54. package/dist/core/filing/FilingIndex.js.map +1 -0
  55. package/dist/core/filing/MindooFilingStore.d.ts +200 -0
  56. package/dist/core/filing/MindooFilingStore.d.ts.map +1 -0
  57. package/dist/core/filing/MindooFilingStore.js +515 -0
  58. package/dist/core/filing/MindooFilingStore.js.map +1 -0
  59. package/dist/core/filing/index.d.ts +4 -0
  60. package/dist/core/filing/index.d.ts.map +1 -0
  61. package/dist/core/filing/index.js +22 -0
  62. package/dist/core/filing/index.js.map +1 -0
  63. package/dist/core/filing/refs.d.ts +56 -0
  64. package/dist/core/filing/refs.d.ts.map +1 -0
  65. package/dist/core/filing/refs.js +109 -0
  66. package/dist/core/filing/refs.js.map +1 -0
  67. package/dist/core/index.d.ts +3 -2
  68. package/dist/core/index.d.ts.map +1 -1
  69. package/dist/core/index.js +10 -5
  70. package/dist/core/index.js.map +1 -1
  71. package/dist/core/indexing/createViewDataProvider.d.ts +6 -0
  72. package/dist/core/indexing/createViewDataProvider.d.ts.map +1 -1
  73. package/dist/core/indexing/createViewDataProvider.js +26 -1
  74. package/dist/core/indexing/createViewDataProvider.js.map +1 -1
  75. package/dist/core/indexing/summary/DocumentSummaryStore.d.ts +43 -1
  76. package/dist/core/indexing/summary/DocumentSummaryStore.d.ts.map +1 -1
  77. package/dist/core/indexing/summary/DocumentSummaryStore.js +302 -2
  78. package/dist/core/indexing/summary/DocumentSummaryStore.js.map +1 -1
  79. package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.d.ts +33 -2
  80. package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.d.ts.map +1 -1
  81. package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.js +129 -26
  82. package/dist/core/indexing/summary/SummaryVirtualViewDataProvider.js.map +1 -1
  83. package/dist/core/indexing/summary/types.d.ts +40 -0
  84. package/dist/core/indexing/summary/types.d.ts.map +1 -1
  85. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts +27 -2
  86. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts.map +1 -1
  87. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js +106 -1
  88. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js.map +1 -1
  89. package/dist/core/indexing/virtualviews/VirtualView.d.ts +70 -5
  90. package/dist/core/indexing/virtualviews/VirtualView.d.ts.map +1 -1
  91. package/dist/core/indexing/virtualviews/VirtualView.js +329 -28
  92. package/dist/core/indexing/virtualviews/VirtualView.js.map +1 -1
  93. package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts +4 -22
  94. package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts.map +1 -1
  95. package/dist/core/indexing/virtualviews/VirtualViewEntryData.js +175 -129
  96. package/dist/core/indexing/virtualviews/VirtualViewEntryData.js.map +1 -1
  97. package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts +28 -0
  98. package/dist/core/indexing/virtualviews/VirtualViewFactory.d.ts.map +1 -1
  99. package/dist/core/indexing/virtualviews/VirtualViewFactory.js +62 -0
  100. package/dist/core/indexing/virtualviews/VirtualViewFactory.js.map +1 -1
  101. package/dist/core/indexing/virtualviews/VirtualViewFilingJoin.d.ts +75 -0
  102. package/dist/core/indexing/virtualviews/VirtualViewFilingJoin.d.ts.map +1 -0
  103. package/dist/core/indexing/virtualviews/VirtualViewFilingJoin.js +163 -0
  104. package/dist/core/indexing/virtualviews/VirtualViewFilingJoin.js.map +1 -0
  105. package/dist/core/indexing/virtualviews/VirtualViewMergedNavigator.d.ts +112 -0
  106. package/dist/core/indexing/virtualviews/VirtualViewMergedNavigator.d.ts.map +1 -0
  107. package/dist/core/indexing/virtualviews/VirtualViewMergedNavigator.js +296 -0
  108. package/dist/core/indexing/virtualviews/VirtualViewMergedNavigator.js.map +1 -0
  109. package/dist/core/indexing/virtualviews/VirtualViewNavigator.d.ts +2 -0
  110. package/dist/core/indexing/virtualviews/VirtualViewNavigator.d.ts.map +1 -1
  111. package/dist/core/indexing/virtualviews/VirtualViewNavigator.js +10 -9
  112. package/dist/core/indexing/virtualviews/VirtualViewNavigator.js.map +1 -1
  113. package/dist/core/indexing/virtualviews/index.d.ts +2 -0
  114. package/dist/core/indexing/virtualviews/index.d.ts.map +1 -1
  115. package/dist/core/indexing/virtualviews/index.js +8 -1
  116. package/dist/core/indexing/virtualviews/index.js.map +1 -1
  117. package/dist/core/query/executeQuery.d.ts.map +1 -1
  118. package/dist/core/query/executeQuery.js +4 -0
  119. package/dist/core/query/executeQuery.js.map +1 -1
  120. package/dist/core/query/executeQueryInclude.d.ts.map +1 -1
  121. package/dist/core/query/executeQueryInclude.js +4 -0
  122. package/dist/core/query/executeQueryInclude.js.map +1 -1
  123. package/dist/core/query/queryView.d.ts.map +1 -1
  124. package/dist/core/query/queryView.js +4 -0
  125. package/dist/core/query/queryView.js.map +1 -1
  126. package/dist/core/types.d.ts +20 -0
  127. package/dist/core/types.d.ts.map +1 -1
  128. package/dist/core/types.js.map +1 -1
  129. package/dist/server/TenantManager.d.ts.map +1 -1
  130. package/dist/server/TenantManager.js +1 -1
  131. package/dist/server/TenantManager.js.map +1 -1
  132. 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
- * Full-tree serialization is throttled by {@link minCacheFlushIntervalMs}
235
- * so active editing sessions don't serialize the entire view on every
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":"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"}
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 = new ViewEntrySortKeyComparator_1.ViewEntrySortKeyComparator(this.categorizationStyle, false, this.docOrderDescending);
273
+ rootChildComparator = this.comparatorFor(false);
192
274
  }
193
275
  else {
194
- rootChildComparator = new ViewEntrySortKeyComparator_1.ViewEntrySortKeyComparator(this.categorizationStyle, this.categoryColumns[0].sorting === types_1.ColumnSorting.DESCENDING, this.docOrderDescending);
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 unsubscribe = db.addChangeListener(() => {
350
- this.scheduleLiveUpdate();
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 = new ViewEntrySortKeyComparator_1.ViewEntrySortKeyComparator(this.categorizationStyle, false, this.docOrderDescending);
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 = new ViewEntrySortKeyComparator_1.ViewEntrySortKeyComparator(this.categorizationStyle, childCategoryDescending, this.docOrderDescending);
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 = new ViewEntrySortKeyComparator_1.ViewEntrySortKeyComparator(this.categorizationStyle, childCategoryDescending, this.docOrderDescending);
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
- * Full-tree serialization is throttled by {@link minCacheFlushIntervalMs}
713
- * so active editing sessions don't serialize the entire view on every
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
- if (!options?.force
720
- && this.lastCacheFlushAt > 0
721
- && now - this.lastCacheFlushAt < this.minCacheFlushIntervalMs) {
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
- const state = this.exportCacheState();
727
- await store.put("vv", this.getCachePrefix(), state);
728
- this.lastCacheFlushAt = now;
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
- * Serialize the entire tree structure for caching (version 2).
733
- * Each node stores its sort key, column values, counts, totals,
734
- * and children — so restoration is O(n) without re-sorting.
735
- */
736
- exportCacheState() {
737
- const root = this.getRoot();
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
- return this.restoreFromTreeSnapshot(snapshot);
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 = new ViewEntrySortKeyComparator_1.ViewEntrySortKeyComparator(this.categorizationStyle, nodeData.cod, docOrderDesc);
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);