datafolder 1.0.6 → 1.0.7

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 (57) hide show
  1. package/README.md +3 -1
  2. package/internal.md +119 -0
  3. package/package.json +2 -2
  4. package/scripts/convertBinFiles.js +49 -0
  5. package/scripts/fastify.js +2 -2
  6. package/src/QyCD.js +19 -10
  7. package/src/QyCache.js +6 -0
  8. package/src/QyFetch.js +1 -1
  9. package/src/QyKVData.js +21 -12
  10. package/src/QyKVDataSaver_Worker.js +7 -7
  11. package/src/QySnapshotCompactor_Worker.js +19 -18
  12. package/src/QySnapshotSaver_Worker.js +54 -52
  13. package/src/QySnapshots.js +72 -64
  14. package/src/rpc/getTriggerFiles.js +1 -0
  15. package/src/soc_pokec.js +7 -7
  16. package/static/datafolderui/QyStyledElement.js +6 -15
  17. package/static/datafolderui/QyUIFolderTree.js +71 -62
  18. package/static/datafolderui/QyUIFolderViewer.js +210 -0
  19. package/static/datafolderui/QyUIUtils.js +1 -1
  20. package/static/datafolderui/display-tree.js +18 -57
  21. package/static/datafolderui/explore-tree.js +10 -10
  22. package/static/datafolderui/folder-tree.js +71 -2
  23. package/static/datafolderui/json-view.js +7 -3
  24. package/static/datafolderui/main-page.js +22 -19
  25. package/static/datafolderui/query-editor.js +1 -1
  26. package/static/datafolderui/trigger-editor.js +30 -0
  27. package/data/sampleDataFolder/qykv/history/acl/1_acl.txt +0 -1
  28. package/data/sampleDataFolder/qykv/history/acl/2_acl.txt +0 -1
  29. package/data/sampleDataFolder/qykv/history/acl/3_acl.txt +0 -1
  30. package/data/sampleDataFolder/qykv/history/acl/4_acl.txt +0 -1
  31. package/data/sampleDataFolder/qykv/history/acl/5_acl.txt +0 -1
  32. package/data/sampleDataFolder/qykv/snapshot/1/1_pM.txt +0 -0
  33. package/data/sampleDataFolder/qykv/snapshot/1/1_prefixPM.txt +0 -0
  34. package/data/sampleDataFolder/qykv/snapshot/1/1_vL.txt +0 -10
  35. package/data/sampleDataFolder/qykv/snapshot/2/1_pM.txt +0 -0
  36. package/data/sampleDataFolder/qykv/snapshot/2/1_prefixPM.txt +0 -0
  37. package/data/sampleDataFolder/qykv/snapshot/2/1_vL.txt +0 -1
  38. package/data/sampleDataFolder/qykv/snapshot/3/1_pM.txt +0 -0
  39. package/data/sampleDataFolder/qykv/snapshot/3/1_prefixPM.txt +0 -0
  40. package/data/sampleDataFolder/qykv/snapshot/3/1_vL.txt +0 -1
  41. package/data/sampleDataFolder/qykv/snapshot/4/1_pM.txt +0 -0
  42. package/data/sampleDataFolder/qykv/snapshot/4/1_prefixPM.txt +0 -0
  43. package/data/sampleDataFolder/qykv/snapshot/4/1_vL.txt +0 -1
  44. package/data/sampleDataFolder/qykv/snapshot/4/2_pM.txt +0 -0
  45. package/data/sampleDataFolder/qykv/snapshot/4/2_prefixPM.txt +0 -0
  46. package/data/sampleDataFolder/qykv/snapshot/4/2_vL.txt +0 -9
  47. package/data/sampleDataFolder/qykv/snapshot/5/1_pM.txt +0 -0
  48. package/data/sampleDataFolder/qykv/snapshot/5/1_prefixPM.txt +0 -0
  49. package/data/sampleDataFolder/qykv/snapshot/5/1_vL.txt +0 -55
  50. package/data/sampleDataFolder/qykv/snapshot/info/1.json +0 -1
  51. package/data/sampleDataFolder/qykv/snapshot/info/2.json +0 -1
  52. package/data/sampleDataFolder/qykv/snapshot/info/3.json +0 -1
  53. package/data/sampleDataFolder/qykv/snapshot/info/4.json +0 -1
  54. package/data/sampleDataFolder/qykv/snapshot/info/5.json +0 -1
  55. package/data/sampleDataFolder/qylog/2026/02/15/08/change.txt +0 -58
  56. package/data/sampleDataFolder/qylog/2026/02/15/08/stderr.txt +0 -11
  57. package/data/sampleDataFolder/qylog/2026/02/15/08/stdout.txt +0 -36
package/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  datafolder is a lightweight JSON database, featuring an AI-friendly API for simple and human maintainable code generation, millisecond data response, with full index and trigger features supported, and the unique ability to browse and manage data as a folder tree directly in your browser. It works perfectly for a wide range of scenarios, from game/application backends to embedded systems to IoT to edge computing.
4
4
 
5
5
  ### Changelog
6
+
7
+ 1.0.7 Added trigger editor and [internal.md](https://github.com/datafolderdev/datafolder/wiki/Internals-of-datafolder)
6
8
  1.0.6 Added code mirror as the query editor.
7
9
 
8
10
  ## Installation
@@ -189,4 +191,4 @@ Next time to bring up the UI:
189
191
  npm run ui
190
192
  ```
191
193
 
192
- Complex query examples can be found in the scripts folder source code.
194
+ Complex query examples can be found in the scripts folder source code.
package/internal.md ADDED
@@ -0,0 +1,119 @@
1
+ ## Internals of datafolder
2
+
3
+ The best mechanism to index and partition data is **by the nature of data**. Instead of forcing data to transform, it should seamlessly embrace and fully cater to the **uniqueness** of each and every data to accomplish nature-level efficiency, **mentally and mechanically**. Whatever data is, it follows like fluid.
4
+
5
+ This is the **design philosophy** behind datafolder, a lightweight schemaless JSON database, featuring an AI-friendly API for simple and human maintainable code generation, millisecond data response, with full index and trigger features supported, and the unique ability to browse and manage data as a folder tree directly in your browser. It works perfectly for a wide range of scenarios, from game/application backends to embedded systems to IoT to edge computing.
6
+
7
+ ### User Level Data Layout
8
+
9
+ Data can be organized naturally into folders, sub folders and files. For example, Orders can be stored in
10
+
11
+ ```
12
+ orders/{year}/{month}/{day}/{order_id}
13
+ ```
14
+
15
+ Flights can be stored as
16
+
17
+ ```
18
+ flights/{from}/{to}/{flight_id}
19
+ ```
20
+
21
+ Airports can be stored as
22
+
23
+ ```
24
+ airports/{country}/{state}/{airport_id}
25
+ ```
26
+
27
+ When you conceive the data model, you don't need to think about tables, schemas, indexes etc. All you need to know is the nature of the data and then goes with the most comfortable one for your mental mind.
28
+
29
+ ### In-memory Tree Structure
30
+
31
+ datafolder uses a tree structure to store the folder/file relationship in memory. Each node has a subdir map and a file map. subdir map stores subdir nodes, which in turn has its subdir map. file map stores the files that are inside this folder. JSON data is stored as file content within each file node. They are all normal Javascript Objects themselves. Thanks to V8 engine's highly optimized object [property](https://v8.dev/blog/fast-properties) [access](https://v8.dev/docs/hidden-classes), getting the dir/file node by path and getting properties from JSON data are extremely fast. These dir/file node and file content can be loaded beforehand, or loaded on demand. And will be released according to visiting time/count to reduce runtime memory usage.
32
+
33
+ ### Lock Free, Always Sync, Immediate Programming Mode
34
+
35
+ When dealing with datafolder, you write your Node.js code in a lock free, always sync, immediate programming mode.
36
+
37
+ - The data are just normal Javascript objects and live within the same memory space of your code. No network traffic, no serialization/deserialization, no overhead. You code directly manipulates the data.
38
+ - No need to worry about whether data is modified by others during your changes. No need to guard data corruption with a transaction.
39
+ - You get the needed data, exam them, make your decisions and changes, and store them back. That is it.
40
+ - datafolder's underlying mechanism guarantees the correctness of running your code in this mode.
41
+ - start() and stop() are the only two APIs that need be called with await. All other APIs are called synchronously.
42
+
43
+ ### Accumulated Change List
44
+
45
+ datafolder make changes immediately in memory. It calculates deltas of such changes and store these deltas in a log file called acl file. Since only storing deltas, these changes are compact. datafolder utilize Node.js's processes and offload the actual acl storing work to separate process. In fact, this change stream is also sent out to other processes to create snapshots, read-only replicas and to compress snapshots etc.
46
+ These deltas can be of the following types:
47
+
48
+ 1. Adding a subdir or file to a dir.
49
+ 2. Deleting a subdir or file from a dir.
50
+ 3. Delete a dir or a file itself.
51
+ 4. Merging JSON changes.
52
+
53
+ JSON changes are further separated into the following:
54
+
55
+ 1. Deleting a property
56
+ 2. Adding a property with value.
57
+ 3. Changing a property to new value.
58
+
59
+ These deltas are appended to the acl log file in sequence. Once the log file reaches a certain size a new log file will be created and appended to. Storing these acl1, acl2, ...,acln files keep the change history of the whole database. The whole data can be reconstructed by replying these acl files in sequence.
60
+
61
+ ### Snapshots
62
+
63
+ The whole data changes are stored as acl1, acl2, acl3,..., acln files. But the reconstruct process takes time to read and apply all these acl files. In order to reduce reconstruction time snapshots are introduced. They store the exact shapes and contents of the items changed after a certain acl file. For example,
64
+
65
+ ```
66
+ acl1, snapshot1, acl2, snapshot2, acl3, snapshot3, acl4, acl5, snapshot5, ...
67
+ ```
68
+
69
+ snapshot1 will store the whole data afte applying acl1. It will shadow acl1.
70
+
71
+ snapshot2 will store the whole changes that are introduced by applying acl2 and will shadow acl2. That is, if a dir/file is changed during acl2, it will be stored as whole in snapshot2 and will shadow the one in snapshot1. If a dir/file is not changed during acl2, it will remain intact in snapshot1.
72
+
73
+ The same as snapshot3, snapshot5 etc. They only contain changed items after the last snapshots. This way the whole data is constructed by various snapshots and the following acls. Since snapshots are holding the whole items, they don't need reconstruction but only need to be able to read a certain dir/file and content out.
74
+
75
+ ### Snapshot Indexes
76
+
77
+ Each Snapshot is a big key value pair map. It is stored in two files: posMap file and valueList file.
78
+
79
+ - posMap file is storing the [start, length] of the content of each key in the valueList file.
80
+ - valueList file is storing the values of this snapshot.
81
+
82
+ For example,
83
+ posMap file contains
84
+
85
+ ```
86
+ key1 [0, 28]
87
+ key2 [28, 128]
88
+ key3 ...
89
+ ```
90
+
91
+ valueList file contains
92
+
93
+ ```
94
+ content1 (from 0, length 28)
95
+ content2 (from 28, length 128)
96
+ content3 (...)
97
+ ```
98
+
99
+ In this format, when posMap is loaded we can read arbitrary key content from valueList file. But if posMap is big, itself will be indexed using prefixPM file.
100
+ prefixPM file is storing the [start, length] of each prefix (like the first 2 characthers of keys) to the posMap file.
101
+
102
+ ```
103
+ prefix1 [0, 38]
104
+ prefix2 [38, 120]
105
+ ```
106
+
107
+ This way we only need to load prefixPM files in memory. Corresponding posmap content and valueList content can be loaded on-demand.
108
+
109
+ For example, we can store 20 years of [Hacker](https://news.ycombinator.com/item?id=46435308) [News](https://github.com/DOSAYGO-STUDIO/HackerBook) which is 22GB into datafolder in the layout of
110
+
111
+ ```
112
+ hackernews/{year}/{month}/{day}/{id}
113
+ ```
114
+
115
+ And each file contains the full story and the nested comments in a Javascript object. Even with 22GB contents, since they are partitioned naturally and each folder only contains a few thousands files they can be read via datafolder UI directly within the browser.
116
+
117
+ ### Snapshot Shadowing and Compaction
118
+
119
+ The same key with updated values will be stored in multiple snapshots and the latest one shadows all previous ones. At some point, thes redundant key/values will take up too much disc space. datafolder can automatically detect the threshold, and save the snapshots into new compacted ones with these redundances removed. These compactions are done in different processes and won't use the main process's time.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datafolder",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "author": {
5
5
  "name": "lysh",
6
6
  "email": "datafolderdev@outlook.com"
@@ -11,7 +11,7 @@
11
11
  "type": "git",
12
12
  "url": "git+https://github.com/datafolderdev/datafolder"
13
13
  },
14
- "homepage": "https://github.com/datafolderdev/datafolder",
14
+ "homepage": "https://github.com/datafolderdev/datafolder/wiki/Internals-of-datafolder",
15
15
  "bugs": {
16
16
  "url": "https://github.com/datafolderdev/datafolder/issues",
17
17
  "email": "datafolderdev@outlook.com"
@@ -0,0 +1,49 @@
1
+ import { join, basename } from "node:path";
2
+ import { readFile, readdir, writeFile } from "node:fs/promises";
3
+
4
+ import { iterPMBuffer, loadPrefixPMFile } from "../src/QySnapshots.js";
5
+ import { ensureDir, folderOrFileExists } from "../src/QyUtils.js";
6
+
7
+ logger.level = "info";
8
+
9
+ run();
10
+
11
+ async function loadPosMapFile(posMapFilePath) {
12
+ const buffer = await readFile(posMapFilePath);
13
+ const posMap = {};
14
+ iterPMBuffer(buffer, (key, nums) => (posMap[key] = nums));
15
+ return posMap;
16
+ }
17
+
18
+ async function run() {
19
+ const folderPath = process.argv[2];
20
+ let destFolderPath = process.argv[3];
21
+ if (!folderPath || !destFolderPath) {
22
+ console.log(`Usage: node ${basename(import.meta.filename)} folder/to/convert folder/to/save or inplace`);
23
+ } else {
24
+ if (destFolderPath == "inplace") destFolderPath = folderPath;
25
+ await convertFolder(folderPath, destFolderPath);
26
+ }
27
+ }
28
+
29
+ async function convertFolder(folder, destFolder) {
30
+ const promises = [];
31
+ for (const item of await readdir(folder, { withFileTypes: true })) {
32
+ const { name } = item;
33
+ if (item.isDirectory()) {
34
+ promises.push(convertFolder(join(folder, name), join(destFolder, name)));
35
+ } else {
36
+ const match = /^[0-9]+_(pM|prefixPM)\.txt/.exec(name);
37
+ if (match) {
38
+ const filePath = join(folder, name);
39
+ const destFilePath = join(destFolder, "dump_" + name + ".json");
40
+ if (await folderOrFileExists(destFilePath)) continue;
41
+ await ensureDir(destFolder);
42
+ console.log(`Converting ${filePath} -> ${destFilePath}`);
43
+ const map = await (match[1] == "pM" ? loadPosMapFile(filePath) : loadPrefixPMFile(filePath));
44
+ promises.push(writeFile(destFilePath, JSON.stringify(map, null, 2)));
45
+ }
46
+ }
47
+ }
48
+ await Promise.all(promises);
49
+ }
@@ -16,7 +16,7 @@ const { port, staticRoot } = config;
16
16
 
17
17
  const fastify = Fastify({ logger: false, forceCloseConnections: true });
18
18
 
19
- const qyDB = new QyDB();
19
+ const qyDB = new QyDB(join(__dirname, "../tmp/QyTrigger/1"));
20
20
 
21
21
  let sigintReceived = false;
22
22
 
@@ -45,7 +45,7 @@ async function insertRpcs() {
45
45
 
46
46
  async function handleGet(request, reply) {
47
47
  const { query, params } = request;
48
- const result = await qyDB.callRpc(params.funName, JSON.parse(query.args));
48
+ const result = await qyDB.callRpc(params.funName, query && query.args ? JSON.parse(query.args) : undefined);
49
49
  if (Error.isError(result)) {
50
50
  logger.warn(result.message);
51
51
  }
package/src/QyCD.js CHANGED
@@ -38,6 +38,15 @@ class r {
38
38
  triggerFile(r) {
39
39
  return this.qyCache.getTriggerFile(r);
40
40
  }
41
+ get triggerFileList() {
42
+ return this.qyCache.triggerFileList;
43
+ }
44
+ rpcFile(r) {
45
+ return this.qyCache.getRpcFile(r);
46
+ }
47
+ get rpcFileList() {
48
+ return this.qyCache.rpcFileList;
49
+ }
41
50
  view(r, e, t = this.currentDir) {
42
51
  return i(r) && (r = s(r)), this.viewP(r, e, t);
43
52
  }
@@ -50,32 +59,32 @@ class r {
50
59
  queryFilesMulti(r, e, t, i = this.currentDir) {
51
60
  return this.qyCache.queryFilesMulti(r, e, t, i);
52
61
  }
53
- queryTree(r, e, t, i, s, n = this.currentDir) {
54
- return u(this.qyCache.queryFiles(r, e, i, s, n), t);
62
+ queryTree(r, e, t, i, s, h = this.currentDir) {
63
+ return n(this.qyCache.queryFiles(r, e, i, s, h), t);
55
64
  }
56
65
  queryTreeMulti(r, e, t, i, s = this.currentDir) {
57
- return u(this.qyCache.queryFilesMulti(r, t, i, s), e);
66
+ return n(this.qyCache.queryFilesMulti(r, t, i, s), e);
58
67
  }
59
68
  resetCurrentDir() {
60
69
  this.currentDir = void 0;
61
70
  }
62
71
  fileListToTree(r, e) {
63
- return u(r, e);
72
+ return n(r, e);
64
73
  }
65
74
  }
66
75
 
67
- function u(r, t) {
76
+ function n(r, t) {
68
77
  var i, s = {};
69
78
  for (i of r) {
70
- var n, u = i.parentList;
79
+ var h, n = i.parentList;
71
80
  let e = s;
72
- for (n of u) {
81
+ for (h of n) {
73
82
  var {
74
- name: h,
83
+ name: u,
75
84
  fullPathHash: c
76
- } = n;
85
+ } = h;
77
86
  let r = e.subdirMap;
78
- r = r || (e.subdirMap = {}), e = (e = r[h]) || (r[h] = {
87
+ r = r || (e.subdirMap = {}), e = (e = r[u]) || (r[u] = {
79
88
  fullPathHash: c
80
89
  });
81
90
  }
package/src/QyCache.js CHANGED
@@ -193,12 +193,18 @@ class t extends r {
193
193
  getOrCreateTriggerFile(r) {
194
194
  return this.triggerRootDir.getOrCreateFile(r);
195
195
  }
196
+ get triggerFileList() {
197
+ return this.triggerRootDir.fileList;
198
+ }
196
199
  getRpcFile(r) {
197
200
  return this.rpcRootDir.getFile(r);
198
201
  }
199
202
  getOrCreateRpcFile(r) {
200
203
  return this.rpcRootDir.getOrCreateFile(r);
201
204
  }
205
+ get rpcFileList() {
206
+ return this.rpcRootDir.fileList;
207
+ }
202
208
  insertTriggerToTree(r, e, t, i) {
203
209
  var {
204
210
  rootDir: o,
package/src/QyFetch.js CHANGED
@@ -7,7 +7,7 @@ class e extends r {
7
7
  super(r), Object.assign(this, {
8
8
  stack: []
9
9
  });
10
- for (let s of [ "dir", "indexDir", "file", "view", "queryFiles", "queryFilesMulti", "queryTree", "queryTreeMulti" ]) this[s] = (...r) => {
10
+ for (let s of [ "dir", "indexDir", "file", "view", "triggerFile", "triggerFiles", "rpcFile", "rpcFiles", "queryFiles", "queryFilesMulti", "queryTree", "queryTreeMulti" ]) this[s] = (...r) => {
11
11
  return e = this, r = super[s].apply(this, r), e.stack.push(r), e;
12
12
  var e;
13
13
  };
package/src/QyKVData.js CHANGED
@@ -83,7 +83,11 @@ class a {
83
83
  compactInterval: s,
84
84
  unloadInterval: r
85
85
  }), n.bgLoadPosMaps ? (async a => {
86
- for (var t of Object.keys(a.qySnapshots.numToSnapshotMap).sort((a, t) => t - a)) {
86
+ var t, {
87
+ qySnapshots: o,
88
+ options: e
89
+ } = a;
90
+ for (t of Object.keys(o.numToSnapshotMap).sort((a, t) => t - a)) {
87
91
  if (await async function s(p, i, r, h, c, l) {
88
92
  if (p.isStopping) return void (l && l.close());
89
93
  let d = p.qySnapshots;
@@ -113,7 +117,7 @@ class a {
113
117
  }(a, t), a.isStopping) break;
114
118
  if (await c(0), a.isStopping) break;
115
119
  }
116
- !a.isStopping && a.options.compactAtStart && l(a);
120
+ !a.isStopping && e.compactAtStart && l(a);
117
121
  })(this) : n.compactAtStart && l(this), this;
118
122
  }
119
123
  async stop() {
@@ -126,8 +130,8 @@ class a {
126
130
  unloadInterval: n,
127
131
  qyKVDataCleaner: s
128
132
  } = this;
129
- clearInterval(e), clearInterval(n), await a.stop(), o && o.closeAllFDs(),
130
- await t.stop(), await s.stop();
133
+ clearInterval(e), clearInterval(n), this.onSnapshotSavedChangeId(await a.stop()),
134
+ o && o.closeAllFDs(), await t.stop(), await s.stop();
131
135
  }
132
136
  save(a, t, o, e) {
133
137
  var n = this.qyKVDataSaver;
@@ -156,15 +160,20 @@ class a {
156
160
  o[a] = void 0;
157
161
  }
158
162
  onSnapshotSavedChangeId(a, t) {
159
- if (this.snapshotSavedChangeId = a, 3 == this.compactState && a >= this.compactMaxChangeId && (K(this),
160
- this.compactState = 1), t) {
161
- var o, a = t, e = this.qySnapshots, n = e.keyToSnapshotMap;
162
- for (o in a.posMap) {
163
- var s = n[o];
164
- s && e.removeKeyFromSnapshot(s, o);
163
+ this.snapshotSavedChangeId = a;
164
+ var {
165
+ compactState: o,
166
+ compactMaxChangeId: e,
167
+ qyKVDataSaver: n
168
+ } = this;
169
+ if (3 == o && e <= a && (K(this), this.compactState = 1), t) {
170
+ var s, o = t, p = this.qySnapshots, i = p.keyToSnapshotMap;
171
+ for (s in o.posMap) {
172
+ var r = i[s];
173
+ r && p.removeKeyFromSnapshot(r, s);
165
174
  }
166
- e.addMaxSnapshot(a), logger.info("addPMOnlySnapshot: " + e.snapshotMaxChangeId),
167
- this.qyKVDataSaver.releaseSnapshot(t.snapshotNum);
175
+ p.addMaxSnapshot(o), logger.info("addPMOnlySnapshot: " + p.snapshotMaxChangeId),
176
+ n.releaseSnapshot(t.snapshotNum);
168
177
  }
169
178
  }
170
179
  onCompactUpdates(a) {
@@ -19,7 +19,7 @@ import {
19
19
  } from "./QySnapshotSaver.js";
20
20
 
21
21
  import {
22
- delCmdKey as m
22
+ delCmdKey as u
23
23
  } from "./QyAcl.js";
24
24
 
25
25
  class s extends a {
@@ -46,7 +46,7 @@ class s extends a {
46
46
  maxChangeId: a,
47
47
  snapshotMaxChangeId: a,
48
48
  currentAclNum: (t || s) + 1
49
- }), o.start(u(this)), n.start(a, t, e);
49
+ }), o.start(m(this)), n.start(a, t, e);
50
50
  }
51
51
  async _op_stop() {
52
52
  var {
@@ -57,8 +57,8 @@ class s extends a {
57
57
  maxChangeId: o,
58
58
  snapshotMaxChangeId: n
59
59
  } = this;
60
- this.isStopping = !0, await a.stop(), s.saveSnapshotAtStop && n < o && (this.isSavingSnapshot && await new Promise(a => this.saveSnapshotResolve = a),
61
- logger.log(`Saving snapshot ${e} at stop.`), t.saveSnapshot(e)), await t.stop();
60
+ return this.isStopping = !0, await a.stop(), s.saveSnapshotAtStop && n < o && (this.isSavingSnapshot && await new Promise(a => this.saveSnapshotResolve = a),
61
+ logger.log(`Saving snapshot ${e} at stop.`), t.saveSnapshot(e)), t.stop();
62
62
  }
63
63
  callSave(a, s, t) {
64
64
  return r(this, a, s, t, !0);
@@ -88,16 +88,16 @@ function r(a, s, t, e, o) {
88
88
  var l = t, c = e;
89
89
  if (c) {
90
90
  var g = c.length;
91
- for (let a = 0; a < g; a += 2) m(l, c[a]);
91
+ for (let a = 0; a < g; a += 2) u(l, c[a]);
92
92
  }
93
93
  return r.castSave(s, t, e), a.maxChangeId = s, !S && s - i >= h.snapshotMaxChangeCount && (++a.currentAclNum,
94
- n.switch(u(a)), r.saveSnapshot(p), Object.assign(a, {
94
+ n.switch(m(a)), r.saveSnapshot(p), Object.assign(a, {
95
95
  snapshotMaxChangeId: s,
96
96
  isSavingSnapshot: !0
97
97
  })), v;
98
98
  }
99
99
 
100
- function u(a) {
100
+ function m(a) {
101
101
  return t(a.aclFolder, a.currentAclNum + "_acl.txt");
102
102
  }
103
103
 
@@ -3,7 +3,7 @@ import {
3
3
  } from "node:path";
4
4
 
5
5
  import {
6
- open as h
6
+ open as f
7
7
  } from "node:fs/promises";
8
8
 
9
9
  import {
@@ -19,7 +19,8 @@ import {
19
19
  } from "./QyKVDataCleaner.js";
20
20
 
21
21
  import {
22
- QySnapshots as n
22
+ QySnapshots as n,
23
+ iterPMBuffer as h
23
24
  } from "./QySnapshots.js";
24
25
 
25
26
  import {
@@ -66,7 +67,7 @@ class s extends a {
66
67
  info: n,
67
68
  prefixPM: i
68
69
  } = s;
69
- (null == i || r(i) || await f(a, s)) && (await t.savePosMap(s),
70
+ (null == i || r(i) || await M(a, s)) && (await t.savePosMap(s),
70
71
  e[p] = n, o[p] = s.prefixPM);
71
72
  })(this, a, s)));
72
73
  }
@@ -77,7 +78,7 @@ class s extends a {
77
78
  qySnapshots: e,
78
79
  vLInfoMap: p
79
80
  } = a;
80
- await f(a, s) && (await M(a, s), await e.saveKeyValueMap(s), {
81
+ await M(a, s) && (await m(a, s), await e.saveKeyValueMap(s), {
81
82
  snapshotNum: a,
82
83
  info: e,
83
84
  posMap: s,
@@ -97,12 +98,12 @@ class s extends a {
97
98
  snapshotNum: r,
98
99
  info: f,
99
100
  keyValueMap: h
100
- } = (await M(a, i), i);
101
+ } = (await m(a, i), i);
101
102
  Object.assign(i, {
102
103
  posMap: void 0,
103
104
  prefixPM: void 0
104
105
  });
105
- for (t of s) t != i && (n.push(t.snapshotNum), await M(a, t), Object.assign(h, t.keyValueMap),
106
+ for (t of s) t != i && (n.push(t.snapshotNum), await m(a, t), Object.assign(h, t.keyValueMap),
106
107
  Object.assign(t, {
107
108
  keyValueMap: void 0,
108
109
  posMap: void 0,
@@ -142,23 +143,23 @@ class s extends a {
142
143
  }
143
144
  }
144
145
 
145
- async function f(a, s) {
146
- var o = a.qySnapshots;
146
+ async function M(a, s) {
147
+ a = a.qySnapshots;
147
148
  let {
148
- prefixPM: t,
149
- posMap: e
149
+ prefixPM: o,
150
+ posMap: t
150
151
  } = s;
151
- var p, n = o.getPMFilePath(s), i = await h(n);
152
- for (p of Object.keys(t).sort()) {
153
- var [ r, f ] = t[p], r = await l(n, i, r, f);
154
- if (!r) return void i.close();
155
- o.iterPMBuffer(r, (a, s) => e[a] = s);
152
+ var e, p = a.getPMFilePath(s), n = await f(p);
153
+ for (e of Object.keys(o).sort()) {
154
+ var [ i, r ] = o[e], i = await l(p, n, i, r);
155
+ if (!i) return n.close(), !1;
156
+ h(i, (a, s) => t[a] = s);
156
157
  }
157
- return i.close(), !0;
158
+ return n.close(), !0;
158
159
  }
159
160
 
160
- async function M(a, s) {
161
- var o, a = a.qySnapshots, t = s.posMap, e = s.keyValueMap = {}, p = a.getVLFilePath(s), n = await h(p);
161
+ async function m(a, s) {
162
+ var o, a = a.qySnapshots, t = s.posMap, e = s.keyValueMap = {}, p = a.getVLFilePath(s), n = await f(p);
162
163
  for (o of Object.keys(t).sort()) {
163
164
  var [ i, r ] = t[o];
164
165
  if (0 < r) {