datafolder 1.0.5 → 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.
- package/README.md +5 -0
- package/internal.md +119 -0
- package/package.json +2 -2
- package/sample.js +19 -19
- package/scripts/convertBinFiles.js +49 -0
- package/scripts/fastify.js +2 -2
- package/src/QyAcl.js +44 -44
- package/src/QyAclSaver_Worker.js +2 -2
- package/src/QyAclWriter.js +1 -1
- package/src/QyBatch.js +1 -3
- package/src/QyBinWriter.js +65 -66
- package/src/QyCD.js +72 -27
- package/src/QyCache.js +39 -33
- package/src/QyDB.js +4 -30
- package/src/QyDefaultOptions.js +2 -1
- package/src/QyFetch.js +4 -22
- package/src/QyFileLogger.js +2 -2
- package/src/QyKVData.js +168 -183
- package/src/QyKVDataCleaner_Worker.js +15 -14
- package/src/QyKVDataSaver_Worker.js +20 -20
- package/src/QyLogFileSaver.js +1 -1
- package/src/QyLogFileSaver_Worker.js +2 -2
- package/src/QyMod.js +7 -7
- package/src/QyOpRunner.js +19 -16
- package/src/QySnapshotCompactor_Worker.js +54 -52
- package/src/QySnapshotSaver_Worker.js +55 -53
- package/src/QySnapshots.js +157 -147
- package/src/QyUnionNode.js +2 -2
- package/src/rpc/evalCode.js +4 -4
- package/src/rpc/getFileContent.js +4 -4
- package/src/rpc/getSubItems.js +12 -12
- package/src/rpc/getTriggerFiles.js +1 -0
- package/src/rpc/queryFiles.js +1 -1
- package/src/soc_pokec.js +7 -7
- package/static/codemirror.bundle/editor.bundle.js +27906 -0
- package/static/datafolderui/QyStyledElement.js +6 -15
- package/static/datafolderui/QyUIFolderTree.js +130 -145
- package/static/datafolderui/QyUIFolderViewer.js +210 -0
- package/static/datafolderui/QyUIUtils.js +10 -5
- package/static/datafolderui/code-mirror.js +35 -0
- package/static/datafolderui/display-tree.js +21 -94
- package/static/datafolderui/explore-tree.js +66 -81
- package/static/datafolderui/folder-tree.js +71 -2
- package/static/datafolderui/index.css +7 -0
- package/static/datafolderui/index.html +1 -0
- package/static/datafolderui/json-view.js +7 -3
- package/static/datafolderui/main-page.js +42 -60
- package/static/datafolderui/query-editor.js +178 -198
- package/static/datafolderui/trigger-editor.js +30 -0
package/README.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
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
|
+
### Changelog
|
|
6
|
+
|
|
7
|
+
1.0.7 Added trigger editor and [internal.md](https://github.com/datafolderdev/datafolder/wiki/Internals-of-datafolder)
|
|
8
|
+
1.0.6 Added code mirror as the query editor.
|
|
9
|
+
|
|
5
10
|
## Installation
|
|
6
11
|
|
|
7
12
|
```javascript
|
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.
|
|
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"
|
package/sample.js
CHANGED
|
@@ -5,33 +5,33 @@ run();
|
|
|
5
5
|
|
|
6
6
|
async function run() {
|
|
7
7
|
await dataFolder.start();
|
|
8
|
-
dataFolder.insert("
|
|
8
|
+
dataFolder.insert("traindata/2026/1/20/chat_0001", {
|
|
9
9
|
userid: "user_0001",
|
|
10
10
|
email: "a@b.com",
|
|
11
|
-
|
|
12
|
-
score: 5,
|
|
11
|
+
feedbackScore: 5,
|
|
13
12
|
address: { city: "a", zipcode: 123 },
|
|
14
13
|
});
|
|
15
|
-
dataFolder.insert("
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
score: 3,
|
|
20
|
-
address: { city: "b", zipcode: 321 },
|
|
21
|
-
});
|
|
22
|
-
const { email, password, address, score } = dataFolder.view("users/2026/user_0002");
|
|
23
|
-
logger.log(email, password, score, address);
|
|
14
|
+
dataFolder.insert("traindata/2026/1/20/chat_0001/prompt", "What is async/await?");
|
|
15
|
+
dataFolder.insert("traindata/2026/1/20/chat_0001/answer", "async/await is ...");
|
|
16
|
+
const { userid, email, address, feedbackScore } = dataFolder.view("traindata/2026/1/20/chat_0001");
|
|
17
|
+
logger.log(userid, email, feedbackScore, address);
|
|
24
18
|
address.zipcode = 678;
|
|
25
|
-
dataFolder.insert("
|
|
26
|
-
logger.log(dataFolder.view("
|
|
19
|
+
dataFolder.insert("traindata/2026/1/20/chat_0001", { feedbackScore: feedbackScore + 1, address });
|
|
20
|
+
logger.log(dataFolder.view("traindata/2026/1/20/chat_0001", ["feedbackScore", "address"]));
|
|
21
|
+
logger.log(dataFolder.view("traindata/2026/1/20/chat_0001/prompt"));
|
|
22
|
+
logger.log(dataFolder.view("traindata/2026/1/20/chat_0001/answer"));
|
|
27
23
|
await dataFolder.stop();
|
|
28
24
|
await dataFolder.start();
|
|
29
25
|
{
|
|
30
|
-
const file = dataFolder.file("
|
|
31
|
-
const { email,
|
|
32
|
-
logger.log(email,
|
|
33
|
-
dataFolder.insert(file, {
|
|
34
|
-
logger.log(
|
|
26
|
+
const file = dataFolder.file("traindata/2026/1/20/chat_0001");
|
|
27
|
+
const { email, feedbackScore } = file.view();
|
|
28
|
+
logger.log(email, feedbackScore);
|
|
29
|
+
dataFolder.insert(file, { feedbackScore: feedbackScore + 1 });
|
|
30
|
+
logger.log(
|
|
31
|
+
file.view(),
|
|
32
|
+
dataFolder.view("traindata/2026/1/20/chat_0001/prompt"),
|
|
33
|
+
dataFolder.view("traindata/2026/1/20/chat_0001/answer"),
|
|
34
|
+
);
|
|
35
35
|
}
|
|
36
36
|
await dataFolder.stop();
|
|
37
37
|
}
|
|
@@ -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
|
+
}
|
package/scripts/fastify.js
CHANGED
|
@@ -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/QyAcl.js
CHANGED
|
@@ -2,13 +2,13 @@ import u from "node:fs/promises";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
mergeContent as m,
|
|
5
|
-
mergeContentChanges as
|
|
5
|
+
mergeContentChanges as n,
|
|
6
6
|
setValByNamePath as a
|
|
7
7
|
} from "./QyFileContentUtils.js";
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
isEmptyObj as g,
|
|
11
|
-
arrayLast as
|
|
11
|
+
arrayLast as s
|
|
12
12
|
} from "./QyUtils.js";
|
|
13
13
|
|
|
14
14
|
import {
|
|
@@ -20,14 +20,14 @@ function r(r, e) {
|
|
|
20
20
|
A: r,
|
|
21
21
|
R: t,
|
|
22
22
|
M: a,
|
|
23
|
-
D:
|
|
23
|
+
D: s
|
|
24
24
|
} = r;
|
|
25
|
-
|
|
25
|
+
s && delete s[e], e.startsWith("c") ? a && delete a[e] : (r && delete r[e],
|
|
26
26
|
t && delete t[e]);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function e(r, e) {
|
|
30
|
-
var t, a,
|
|
30
|
+
var t, a, s, n, {
|
|
31
31
|
D: e,
|
|
32
32
|
A: i,
|
|
33
33
|
R: o,
|
|
@@ -38,27 +38,27 @@ function e(r, e) {
|
|
|
38
38
|
var c, l = r[a] || (r[a] = {});
|
|
39
39
|
for (c of i[a]) l[c] = 1;
|
|
40
40
|
}
|
|
41
|
-
for (
|
|
42
|
-
var u = r[
|
|
41
|
+
for (s in o) {
|
|
42
|
+
var u = r[s];
|
|
43
43
|
if (u) {
|
|
44
|
-
var d = o[
|
|
44
|
+
var d = o[s];
|
|
45
45
|
if (d) {
|
|
46
46
|
for (var h of d) delete u[h];
|
|
47
|
-
g(u) && (r[
|
|
47
|
+
g(u) && (r[s] = void 0);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
for (
|
|
51
|
+
for (n in f) r[n] = m(r[n], f[n]);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function t(r, e) {
|
|
55
55
|
var t, {
|
|
56
56
|
D: r,
|
|
57
57
|
A: a,
|
|
58
|
-
R:
|
|
59
|
-
M:
|
|
58
|
+
R: s,
|
|
59
|
+
M: n
|
|
60
60
|
} = r;
|
|
61
|
-
for (t of [ r, a,
|
|
61
|
+
for (t of [ r, a, s, n ]) for (var i in t) e(i);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
class i {
|
|
@@ -85,10 +85,10 @@ class i {
|
|
|
85
85
|
|
|
86
86
|
case "M":
|
|
87
87
|
{
|
|
88
|
-
var
|
|
89
|
-
let r =
|
|
90
|
-
|
|
91
|
-
r[e] = null !=
|
|
88
|
+
var s = this.cmdArgAsMapObj;
|
|
89
|
+
let r = s.M;
|
|
90
|
+
s = (r = null == r ? s.M = {} : r)[e];
|
|
91
|
+
r[e] = null != s ? n(s, t) : t;
|
|
92
92
|
break;
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -100,15 +100,15 @@ class i {
|
|
|
100
100
|
if (r) {
|
|
101
101
|
++this.batchSize;
|
|
102
102
|
var e, t, a, {
|
|
103
|
-
D:
|
|
104
|
-
M:
|
|
103
|
+
D: s,
|
|
104
|
+
M: n
|
|
105
105
|
} = r;
|
|
106
|
-
for (e in
|
|
106
|
+
for (e in s) this.pushCmd("D", e);
|
|
107
107
|
for (t of [ "A", "R" ]) {
|
|
108
108
|
var i, o = r[t];
|
|
109
109
|
for (i in o) for (var f of o[i]) this.pushCmd(t, i, f);
|
|
110
110
|
}
|
|
111
|
-
for (a in
|
|
111
|
+
for (a in n) this.pushCmd("M", a, n[a]);
|
|
112
112
|
}
|
|
113
113
|
return this;
|
|
114
114
|
}
|
|
@@ -122,20 +122,20 @@ class i {
|
|
|
122
122
|
for (r of [ "A", "R" ]) {
|
|
123
123
|
var t, a = e[r];
|
|
124
124
|
for (t in a) {
|
|
125
|
-
var
|
|
126
|
-
0 <
|
|
125
|
+
var s = Object.keys(a[t]);
|
|
126
|
+
0 < s.length ? a[t] = s : delete a[t];
|
|
127
127
|
}
|
|
128
128
|
g(a) && delete e[r];
|
|
129
129
|
}
|
|
130
130
|
var {
|
|
131
|
-
D:
|
|
131
|
+
D: n,
|
|
132
132
|
M: i
|
|
133
133
|
} = e;
|
|
134
|
-
return
|
|
134
|
+
return n && g(n) && delete e.D, i && g(i) && delete e.M, e;
|
|
135
135
|
}
|
|
136
136
|
toAclBuffer(r) {
|
|
137
137
|
var e, t = this.batchSize;
|
|
138
|
-
if (0 < t) return r = r, t = t, e = this.takeCmdArgAsListObj(), e = Buffer.from(JSON.stringify(e)
|
|
138
|
+
if (0 < t) return r = r, t = t, e = this.takeCmdArgAsListObj(), e = Buffer.from(JSON.stringify(e)),
|
|
139
139
|
Buffer.concat([ l([ r, t, e.length ]), e ]);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -148,7 +148,7 @@ function f(r, e) {
|
|
|
148
148
|
let t = r.cmdArgAsMapObj;
|
|
149
149
|
if (t && e && !(e.length <= 1)) {
|
|
150
150
|
for (let r = 0; r < e.length - 1; ++r) if (null == (t = t[e[r]])) return;
|
|
151
|
-
delete t[
|
|
151
|
+
delete t[s(e)];
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -163,19 +163,19 @@ async function c(e, r = !1, t = 0) {
|
|
|
163
163
|
let A = [ 1, 2, 4, 8 ];
|
|
164
164
|
|
|
165
165
|
function l(e) {
|
|
166
|
-
var t = e.length, a = new Array(t),
|
|
167
|
-
let
|
|
166
|
+
var t = e.length, a = new Array(t), s = new Array(t);
|
|
167
|
+
let n = t << 6, i = 1;
|
|
168
168
|
for (let r = 0; r < t; ++r) {
|
|
169
|
-
var o = a[r] = (o = e[r]) <= 255 ? 0 : o <= 65535 ? 1 : o <= 4294967295 ? 2 : 3, o = (
|
|
170
|
-
|
|
169
|
+
var o = a[r] = (o = e[r]) <= 255 ? 0 : o <= 65535 ? 1 : o <= 4294967295 ? 2 : 3, o = (n |= o << (r << 1),
|
|
170
|
+
s[r] = A[o]);
|
|
171
171
|
i += o;
|
|
172
172
|
}
|
|
173
173
|
var f = Buffer.allocUnsafe(i);
|
|
174
|
-
f.writeUInt8(
|
|
174
|
+
f.writeUInt8(n);
|
|
175
175
|
let c = 1;
|
|
176
176
|
for (let r = 0; r < t; ++r) {
|
|
177
177
|
h = d = u = l = void 0;
|
|
178
|
-
var [ l, u, d, h = 0 ] = [ f, e[r],
|
|
178
|
+
var [ l, u, d, h = 0 ] = [ f, e[r], s[r], c ];
|
|
179
179
|
switch (d) {
|
|
180
180
|
case 1:
|
|
181
181
|
l.writeUInt8(u, h);
|
|
@@ -192,15 +192,15 @@ function l(e) {
|
|
|
192
192
|
default:
|
|
193
193
|
l.writeBigUInt64BE(BigInt(u), h);
|
|
194
194
|
}
|
|
195
|
-
c +=
|
|
195
|
+
c += s[r];
|
|
196
196
|
}
|
|
197
197
|
return f;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
function h(e, t, a) {
|
|
201
|
-
var
|
|
202
|
-
for (let r = 0; r <
|
|
203
|
-
var i = A[
|
|
201
|
+
var s = e.readUInt8(t), n = (++t, s >> 6 & 3);
|
|
202
|
+
for (let r = 0; r < n; ++r) {
|
|
203
|
+
var i = A[s >> (r << 1) & 3];
|
|
204
204
|
a[r] = ((r, e, t = 0) => {
|
|
205
205
|
switch (e) {
|
|
206
206
|
case 1:
|
|
@@ -220,25 +220,25 @@ function h(e, t, a) {
|
|
|
220
220
|
return t;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
async function v(t, r, a,
|
|
224
|
-
var
|
|
223
|
+
async function v(t, r, a, s) {
|
|
224
|
+
var n = [];
|
|
225
225
|
for (let e = 0; e < r.length; ) {
|
|
226
226
|
var i = [], o = h(r, e, i), [ i, f, c ] = i;
|
|
227
|
-
if (i <=
|
|
227
|
+
if (i <= s) d.warn(`changeId ${i} <= currentChangeId ${s} at ${t}:${e}. Ignored.`),
|
|
228
228
|
e = o + c; else {
|
|
229
|
-
|
|
229
|
+
s + f != i && d.warn(`currentChangeId ${s} + batchSize ${f} != changeId ` + i);
|
|
230
230
|
try {
|
|
231
231
|
var l = JSON.parse(r.subarray(o, o + c));
|
|
232
|
-
|
|
232
|
+
n.push([ i, l ]), s = i, e = o + c;
|
|
233
233
|
} catch (r) {
|
|
234
234
|
if (d.error(`Parse ${t} failed at ${e}:`, r), a) return d.warn(`Autorepair by truncating ${t} to ` + e),
|
|
235
235
|
null == (f = await u.truncate(t, e)) ? d.log("truncate success") : d.error("truncate failed:", f),
|
|
236
|
-
|
|
236
|
+
n;
|
|
237
237
|
throw r;
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
-
return
|
|
241
|
+
return n;
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
export {
|
package/src/QyAclSaver_Worker.js
CHANGED
package/src/QyAclWriter.js
CHANGED