datafolder 1.0.0

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 (182) hide show
  1. package/README.md +169 -0
  2. package/index.js +3 -0
  3. package/package.json +27 -0
  4. package/sample.js +37 -0
  5. package/scripts/airbnb.js +73 -0
  6. package/scripts/airbnb2.js +74 -0
  7. package/scripts/airbnb2_queries.js +231 -0
  8. package/scripts/airbnb_queries.js +264 -0
  9. package/scripts/downloadFile.js +42 -0
  10. package/scripts/express.js +46 -0
  11. package/scripts/fastify.js +79 -0
  12. package/scripts/flights.js +75 -0
  13. package/scripts/flights2.js +81 -0
  14. package/scripts/flights2_queries.js +453 -0
  15. package/scripts/flights_queries.js +403 -0
  16. package/src/QyAcl.js +252 -0
  17. package/src/QyAclSaver.js +14 -0
  18. package/src/QyAclSaver_Worker.js +29 -0
  19. package/src/QyAclWriter.js +19 -0
  20. package/src/QyBatch.js +501 -0
  21. package/src/QyBinWriter.js +140 -0
  22. package/src/QyCD.js +61 -0
  23. package/src/QyCache.js +212 -0
  24. package/src/QyCompressor.js +60 -0
  25. package/src/QyCompressor_Worker.js +13 -0
  26. package/src/QyDB.js +122 -0
  27. package/src/QyDefaultOptions.js +59 -0
  28. package/src/QyDir.js +192 -0
  29. package/src/QyFetch.js +38 -0
  30. package/src/QyFile.js +124 -0
  31. package/src/QyFileContentUtils.js +175 -0
  32. package/src/QyFileLogger.js +58 -0
  33. package/src/QyIndex.js +237 -0
  34. package/src/QyIndexPropTree.js +92 -0
  35. package/src/QyIntersectNode.js +48 -0
  36. package/src/QyItem.js +105 -0
  37. package/src/QyJsonSaver.js +20 -0
  38. package/src/QyKVData.js +289 -0
  39. package/src/QyKVDataCleaner.js +17 -0
  40. package/src/QyKVDataCleaner_Worker.js +94 -0
  41. package/src/QyKVDataLoader.js +65 -0
  42. package/src/QyKVDataSaver.js +17 -0
  43. package/src/QyKVDataSaver_Worker.js +81 -0
  44. package/src/QyLogFileSaver.js +14 -0
  45. package/src/QyLogFileSaver_Worker.js +26 -0
  46. package/src/QyLogger.js +76 -0
  47. package/src/QyMessageWorker.js +41 -0
  48. package/src/QyMessager.js +84 -0
  49. package/src/QyMessager_Run.js +13 -0
  50. package/src/QyMod.js +15 -0
  51. package/src/QyOpRunner.js +73 -0
  52. package/src/QyQueue.js +60 -0
  53. package/src/QySaver.js +83 -0
  54. package/src/QySnapshotCompactor.js +17 -0
  55. package/src/QySnapshotCompactor_Worker.js +151 -0
  56. package/src/QySnapshotSaver.js +17 -0
  57. package/src/QySnapshotSaver_Worker.js +164 -0
  58. package/src/QySnapshots.js +354 -0
  59. package/src/QyStructure.js +50 -0
  60. package/src/QyTrigger.js +133 -0
  61. package/src/QyUnionNode.js +105 -0
  62. package/src/QyUtils.js +250 -0
  63. package/src/QyWorker.js +33 -0
  64. package/src/hackernews.js +93 -0
  65. package/src/rpc/evalCode.js +9 -0
  66. package/src/rpc/getFileContent.js +16 -0
  67. package/src/rpc/getSubItems.js +32 -0
  68. package/src/rpc/queryFiles.js +33 -0
  69. package/src/rpc/reloadRpc.js +1 -0
  70. package/src/simulate.js +114 -0
  71. package/src/simulate2.js +110 -0
  72. package/src/soc_pokec.js +43 -0
  73. package/static/bootstrap-5.3.8-dist/css/bootstrap.min.css +6 -0
  74. package/static/bootstrap-5.3.8-dist/js/bootstrap.bundle.js +6312 -0
  75. package/static/datafolderui/QyStyledElement.js +30 -0
  76. package/static/datafolderui/QyUIFolderTree.js +203 -0
  77. package/static/datafolderui/QyUIUtils.js +42 -0
  78. package/static/datafolderui/display-tree.js +108 -0
  79. package/static/datafolderui/explore-tree.js +122 -0
  80. package/static/datafolderui/folder-tree.js +5 -0
  81. package/static/datafolderui/hscrpt.js +25 -0
  82. package/static/datafolderui/index.css +6 -0
  83. package/static/datafolderui/index.html +20 -0
  84. package/static/datafolderui/json-view.js +74 -0
  85. package/static/datafolderui/kv-input.js +66 -0
  86. package/static/datafolderui/list-input.js +52 -0
  87. package/static/datafolderui/main-page.js +98 -0
  88. package/static/datafolderui/num-input.js +27 -0
  89. package/static/datafolderui/query-editor.js +229 -0
  90. package/static/datafolderui/querySelectorShadowRoot.js +24 -0
  91. package/static/datafolderui/resizable-input.js +67 -0
  92. package/static/favicon.ico +0 -0
  93. package/static/icons-1.13.1/font/bootstrap-icons.min.css +5 -0
  94. package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff +0 -0
  95. package/static/icons-1.13.1/font/fonts/bootstrap-icons.woff2 +0 -0
  96. package/static/json-view/dist/jsonview.js +433 -0
  97. package/static/tree.js/tree.css +106 -0
  98. package/static/tree.js/tree.js +374 -0
  99. package/static/w2ui/dist/w2ui.min.css +2 -0
  100. package/static/w2ui/src/less/icons/icons.json +1 -0
  101. package/static/w2ui/src/less/icons/preview.html +33 -0
  102. package/static/w2ui/src/less/icons/readme.md +15 -0
  103. package/static/w2ui/src/less/icons/svg/box.svg +3 -0
  104. package/static/w2ui/src/less/icons/svg/check.svg +3 -0
  105. package/static/w2ui/src/less/icons/svg/colors.svg +3 -0
  106. package/static/w2ui/src/less/icons/svg/columns.svg +5 -0
  107. package/static/w2ui/src/less/icons/svg/cross.svg +4 -0
  108. package/static/w2ui/src/less/icons/svg/drop.svg +6 -0
  109. package/static/w2ui/src/less/icons/svg/empty.svg +3 -0
  110. package/static/w2ui/src/less/icons/svg/eye-dropper.svg +4 -0
  111. package/static/w2ui/src/less/icons/svg/info.svg +8 -0
  112. package/static/w2ui/src/less/icons/svg/paste.svg +4 -0
  113. package/static/w2ui/src/less/icons/svg/pencil.svg +5 -0
  114. package/static/w2ui/src/less/icons/svg/plus.svg +3 -0
  115. package/static/w2ui/src/less/icons/svg/reload.svg +5 -0
  116. package/static/w2ui/src/less/icons/svg/search.svg +3 -0
  117. package/static/w2ui/src/less/icons/svg/settings.svg +8 -0
  118. package/static/w2ui/src/less/icons/w2ui-font.css +36 -0
  119. package/static/w2ui/src/less/icons/w2ui-font.woff +0 -0
  120. package/static/w2ui/src/less/src/buttons.less +184 -0
  121. package/static/w2ui/src/less/src/common.less +541 -0
  122. package/static/w2ui/src/less/src/fields.less +302 -0
  123. package/static/w2ui/src/less/src/form.less +319 -0
  124. package/static/w2ui/src/less/src/grid.less +1057 -0
  125. package/static/w2ui/src/less/src/icons.less +37 -0
  126. package/static/w2ui/src/less/src/layout.less +86 -0
  127. package/static/w2ui/src/less/src/mixins.less +87 -0
  128. package/static/w2ui/src/less/src/popup.less +277 -0
  129. package/static/w2ui/src/less/src/sidebar.less +343 -0
  130. package/static/w2ui/src/less/src/tabs.less +145 -0
  131. package/static/w2ui/src/less/src/toolbar.less +219 -0
  132. package/static/w2ui/src/less/src/tooltip.less +948 -0
  133. package/static/w2ui/src/less/w2ui.less +196 -0
  134. package/static/w2ui/src/locale/az-az.json +114 -0
  135. package/static/w2ui/src/locale/ba-ba.json +114 -0
  136. package/static/w2ui/src/locale/bg-bg.json +114 -0
  137. package/static/w2ui/src/locale/ca-es.json +114 -0
  138. package/static/w2ui/src/locale/da-dk.json +114 -0
  139. package/static/w2ui/src/locale/de-de.json +114 -0
  140. package/static/w2ui/src/locale/en-gb.json +17 -0
  141. package/static/w2ui/src/locale/en-us.json +17 -0
  142. package/static/w2ui/src/locale/es-ar.json +115 -0
  143. package/static/w2ui/src/locale/es-cr.json +119 -0
  144. package/static/w2ui/src/locale/es-es.json +114 -0
  145. package/static/w2ui/src/locale/es-mx.json +114 -0
  146. package/static/w2ui/src/locale/fr-fr.json +114 -0
  147. package/static/w2ui/src/locale/gl-es.json +114 -0
  148. package/static/w2ui/src/locale/hr-hr.json +114 -0
  149. package/static/w2ui/src/locale/hu-hu.json +114 -0
  150. package/static/w2ui/src/locale/id-id.json +114 -0
  151. package/static/w2ui/src/locale/it-it.json +114 -0
  152. package/static/w2ui/src/locale/ja-jp.json +114 -0
  153. package/static/w2ui/src/locale/kk-kz.json +116 -0
  154. package/static/w2ui/src/locale/ko-kr.json +114 -0
  155. package/static/w2ui/src/locale/lt-lt.json +114 -0
  156. package/static/w2ui/src/locale/nl-nl.json +114 -0
  157. package/static/w2ui/src/locale/no-no.json +114 -0
  158. package/static/w2ui/src/locale/pl-pl.json +114 -0
  159. package/static/w2ui/src/locale/pt-br.json +114 -0
  160. package/static/w2ui/src/locale/ru-ru.json +116 -0
  161. package/static/w2ui/src/locale/sk-sk.json +114 -0
  162. package/static/w2ui/src/locale/sl-si.json +116 -0
  163. package/static/w2ui/src/locale/sr-rs.json +114 -0
  164. package/static/w2ui/src/locale/tr-tr.json +114 -0
  165. package/static/w2ui/src/locale/uk-ua.json +114 -0
  166. package/static/w2ui/src/locale/zh-cn.json +114 -0
  167. package/static/w2ui/src/locale/zh-tw.json +114 -0
  168. package/static/w2ui/src/query.js +583 -0
  169. package/static/w2ui/src/readme.md +11 -0
  170. package/static/w2ui/src/w2base.js +311 -0
  171. package/static/w2ui/src/w2compat.js +207 -0
  172. package/static/w2ui/src/w2field.js +1854 -0
  173. package/static/w2ui/src/w2form.js +2411 -0
  174. package/static/w2ui/src/w2grid.js +8916 -0
  175. package/static/w2ui/src/w2layout.js +1194 -0
  176. package/static/w2ui/src/w2locale.js +129 -0
  177. package/static/w2ui/src/w2popup.js +909 -0
  178. package/static/w2ui/src/w2sidebar.js +1769 -0
  179. package/static/w2ui/src/w2tabs.js +669 -0
  180. package/static/w2ui/src/w2toolbar.js +1180 -0
  181. package/static/w2ui/src/w2tooltip.js +3134 -0
  182. package/static/w2ui/src/w2utils.js +2457 -0
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # datafolder
2
+
3
+ datafolder is a highly efficient hierarchical virtual folder/file structured JSON database. It is implemented in pure native Node.js Javascript and has zero dependency, with full index and trigger features supported. The data can be organized into folders and sub folders, thus naturally partitioned. For example, airports, flights, orders can be stored as
4
+
5
+ ```
6
+ airports/{country}/{state}/{airport_id} flights/{from}/{to}/{flight_id} orders/{year}/{month}/{day}/{order_id}
7
+ ```
8
+
9
+ ## Installation
10
+
11
+ ```javascript
12
+ npm install "datafolder"
13
+ ```
14
+
15
+ ## How to use
16
+
17
+ ```javascript
18
+ const DataFolder = require("datafolder");
19
+ const dataFolder = new DataFolder(); //Specify where to store the data. Default is ./data/sampleDataFolder
20
+ await dataFolder.start();
21
+ dataFolder.insert("users/2026/user_0001", {
22
+ userid: "user_0001",
23
+ email: "a@b.com",
24
+ score: 5,
25
+ address: { city: "a", zipcode: 123 },
26
+ });
27
+ dataFolder.insert("users/2026/user_0002", {
28
+ userid: "user_0002",
29
+ email: "c@b.com",
30
+ score: 3,
31
+ address: { city: "b", zipcode: 321 },
32
+ });
33
+ const { email, password, address, score } = dataFolder.view("users/2026/user_0002");
34
+ address.zipcode = 678;
35
+ dataFolder.insert("users/2026/user_0002", { score: score + 1, address });
36
+ await dataFolder.stop();
37
+ ```
38
+
39
+ Or operate with dir/file objects:
40
+
41
+ ```javascript
42
+ const file = dataFolder.file("users/2026/user_0002");
43
+ const { score, address } = file.view({ score: 1, address: 1 });
44
+ dataFolder.insert(file, { score: score + 1, address: { city: "c", zipcode: 663 } });
45
+ ```
46
+
47
+ ## Features
48
+
49
+ ### 1. Simple CRUD APIs
50
+
51
+ #### Create
52
+
53
+ ```javascript
54
+ dataFolder.createFile("path/to/file");
55
+ dataFolder.createDir("path/to/dir");
56
+ dataFolder.insert("path/to/file", { email, address: { city, zipcode } });
57
+ ```
58
+
59
+ #### Read
60
+
61
+ ```javascript
62
+ const dir = dataFolder.dir("path/to/dir");
63
+ const { subdirList, fileList, sortedSubdirList } = dir;
64
+ const file = dataFolder.file("path/to/file");
65
+ const { email, address } = dataFolder.view("path/to/file", { email: 1, addres: 1 });
66
+ ```
67
+
68
+ #### Update
69
+
70
+ ```javascript
71
+ dataFolder.insert("path/to/file", { email: newEmail, address: { zipcode: newZipcode } });
72
+ dataFolder.insert(file, { score: score + 1 });
73
+ ```
74
+
75
+ #### Delete
76
+
77
+ ```javascript
78
+ dataFolder.delDir("path/to/dir");
79
+ dataFolder.delFile("path/to/file");
80
+ dataFolder.delContent("path/to/file");
81
+ dataFolder.remove("path/to/file", { email: 1, address: { zipcode: 1 } });
82
+ dataFolder.remove(file, { email: 1, address: { zipcode: 1 } });
83
+ ```
84
+
85
+ ### 2. Batch APIs
86
+
87
+ ```javascript
88
+ const [dir, file, { email, score }] = dataFolder.fetch
89
+ .dir("path/to/dir")
90
+ .file("path/to/file")
91
+ .view("path/to/file")
92
+ .run();
93
+ dataFolder.batch
94
+ .insert("path/to/file", { email: "email1@a.b.com" })
95
+ .insert("path/to/file", { email: "email2", score: 100 })
96
+ .remove("path/to/file", { address: { city: 1 } })
97
+ .run();
98
+ ```
99
+
100
+ ### 3. Powerful Query Support. Auto Create Needed Indexes
101
+
102
+ ```javascript
103
+ dataFolder.queryFiles("flights/**", { _from: "airports/LAX" });
104
+ dataFolder.queryFiles(["flights", "*", 1, (x) => x >= 5 && x <= 7], {
105
+ $or: [{ _from: "airports/BIS" }, { _to: "airports/BIS" }],
106
+ });
107
+ dataFolder.queryFilesMulti([
108
+ [["airbnb", "Loft", "Entire home/apt", "Real Bed"], { address: { street: "Porto, Porto, Portugal" } }],
109
+ ["airbnb/Loft/**", { minimum_nights: (x) => x >= 60 }],
110
+ ]);
111
+ ```
112
+
113
+ ### 4. Flexible Trigger Support to Monitor Any Changes
114
+
115
+ ```javascript
116
+ function onEmailChanged({ studentId, email }) {
117
+ const { oldValue, newValue } = email;
118
+ // Logic to handle email change.
119
+ }
120
+ dataFolder.insertTrigger("emailChanged", "students/{studentId}", "email");
121
+ dataFolder.on("emailChanged", onEmailChanged);
122
+ dataFolder.insert("students/student1", { email: "email1" });
123
+ dataFolder.insert("students/student2", { email: "email2" });
124
+ dataFolder.insert("students/student1", { email: "email1_changed" });
125
+ ```
126
+
127
+ Will trigger the following calls:
128
+
129
+ ```javascript
130
+ onEmailChanged({ studentId: "student1", email: { oldValue: undefined, newValue: "email1" } });
131
+ onEmailChanged({ studentId: "student2", email: { oldValue: undefined, newValue: "email2" } });
132
+ onEmailChanged({ studentId: "student1", email: { oldValue: "email1", newValue: "email1_changed" } });
133
+ ```
134
+
135
+ ## Example
136
+
137
+ Once you install datafolder via
138
+
139
+ ```bash
140
+ npm insall datafolder
141
+ ```
142
+
143
+ you can cd to node_modules/datafolder, then run
144
+
145
+ ```bash
146
+ npm install
147
+ ```
148
+
149
+ to install the dev dependency fastify. Then run
150
+
151
+ ```bash
152
+ npm run airbnb
153
+ ```
154
+
155
+ It will download the [AirBnB data](https://raw.githubusercontent.com/neelabalan/mongodb-sample-dataset/refs/heads/main/sample_airbnb/listingsAndReviews.json) (around 100MB) and insert into the following folder structure:
156
+
157
+ ```
158
+ airbnb/{property_type}/{room_type}/{bed_type}/{_id}
159
+ ```
160
+
161
+ Once the data is inserted, it will open the browser at [datafolderui](http://localhost:3628/datafolderui/index.html) and the whole folder structures can be navigated.
162
+
163
+ Next time to bring up the UI:
164
+
165
+ ```bash
166
+ npm run ui
167
+ ```
168
+
169
+ Complex query examples can be found in the scripts folder source code.
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ const { QyDB } = require("./src/QyDB.js");
2
+ module.exports = QyDB;
3
+ module.exports.default = QyDB;
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "datafolder",
3
+ "version": "1.0.0",
4
+ "author": "lysh <1wopjdzn1@mozmail.com>",
5
+ "description": "datafolder is a highly efficient hierarchical virtual folder/file structured JSON database. It is implemented in pure native Node.js Javascript and has zero dependency, with full index and trigger features supported.",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "Hierarchical JSON Database",
9
+ "virtual folder/file structured",
10
+ "LSM Based Persistency",
11
+ "Pure Javascript",
12
+ "Zero Dependency",
13
+ "Highly Efficient"
14
+ ],
15
+ "dependencies": {},
16
+ "devDependencies": {
17
+ "fastify": "^5.0.0",
18
+ "@fastify/compress": "^8.1.0",
19
+ "@fastify/express": "^4.0.2",
20
+ "@fastify/static": "^8.3.0"
21
+ },
22
+ "exports": "./index.js",
23
+ "scripts": {
24
+ "ui": "start http://localhost:3628/datafolderui/index.html && node ./scripts/fastify.js",
25
+ "airbnb": "node ./scripts/airbnb2.js && start http://localhost:3628/datafolderui/index.html && node ./scripts/fastify.js"
26
+ }
27
+ }
package/sample.js ADDED
@@ -0,0 +1,37 @@
1
+ const DataFolder = require("datafolder");
2
+ const dataFolder = new DataFolder();
3
+
4
+ run();
5
+
6
+ async function run() {
7
+ await dataFolder.start();
8
+ dataFolder.insert("users/2026/user_0001", {
9
+ userid: "user_0001",
10
+ email: "a@b.com",
11
+ password: "123",
12
+ score: 5,
13
+ address: { city: "a", zipcode: 123 },
14
+ });
15
+ dataFolder.insert("users/2026/user_0002", {
16
+ userid: "user_0002",
17
+ email: "c@b.com",
18
+ password: "333",
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);
24
+ address.zipcode = 678;
25
+ dataFolder.insert("users/2026/user_0002", { score: score + 1, address });
26
+ logger.log(dataFolder.view("users/2026/user_0002", ["score", "address"]));
27
+ await dataFolder.stop();
28
+ await dataFolder.start();
29
+ {
30
+ const file = dataFolder.file("users/2026/user_0002");
31
+ const { email, password, score } = file.view();
32
+ logger.log(email, password, score);
33
+ dataFolder.insert(file, { score: score + 1 });
34
+ logger.log(file.view());
35
+ }
36
+ await dataFolder.stop();
37
+ }
@@ -0,0 +1,73 @@
1
+ const path = require("node:path");
2
+ const fsPromises = require("node:fs/promises");
3
+ const { QyDB } = require("../src/QyDB.js");
4
+ const { folderOrFileExists, isSimpleType } = require("../src/QyUtils.js");
5
+ const { runQueries } = require("./airbnb_queries.js");
6
+ const { downloadFile } = require("./downloadFile.js");
7
+
8
+ const fileName = "listingsAndReviews.json";
9
+ const fileUrl = `https://raw.githubusercontent.com/neelabalan/mongodb-sample-dataset/refs/heads/main/sample_airbnb/${fileName}`;
10
+ const airbnbJsonFilePath = path.join(__dirname, "../data/", fileName);
11
+ const qyDB = new QyDB();
12
+ const { rootFolder } = qyDB;
13
+
14
+ run();
15
+ async function insertData() {
16
+ console.time("insertData");
17
+ const { batch } = qyDB;
18
+ const fileHandle = await fsPromises.open(airbnbJsonFilePath, "r");
19
+ for await (const line of fileHandle.readLines()) {
20
+ const oneItem = convert(JSON.parse(line));
21
+ batch.insert(["airbnb1", oneItem._id], oneItem);
22
+ }
23
+ batch.run();
24
+ console.timeEnd("insertData");
25
+ }
26
+ async function run() {
27
+ try {
28
+ logger.log(`Downloading ${fileName}`);
29
+ await downloadFile(fileUrl, airbnbJsonFilePath);
30
+ console.log("done");
31
+ } catch (exception) {
32
+ logger.error(`Downloading ${fileUrl} failed:`, exception);
33
+ return;
34
+ }
35
+ console.time("total");
36
+ if (process.argv[3]) {
37
+ logger.level = process.argv[3];
38
+ }
39
+ let shouldInsertData = true;
40
+ if (process.argv[2] == "true") {
41
+ try {
42
+ await fsPromises.rm(rootFolder, { recursive: true });
43
+ } catch (exception) {}
44
+ shouldInsertData = true;
45
+ } else if (!(await folderOrFileExists(rootFolder))) shouldInsertData = true;
46
+ if (shouldInsertData) {
47
+ console.time("run");
48
+ await qyDB.start();
49
+ await insertData();
50
+ await qyDB.stop();
51
+ console.timeEnd("run");
52
+ }
53
+ console.time("queryWithStartEnd");
54
+ console.time("start");
55
+ await qyDB.start();
56
+ console.timeEnd("start");
57
+ runQueries(qyDB);
58
+ await qyDB.stop();
59
+ console.timeEnd("queryWithStartEnd");
60
+ console.timeEnd("total");
61
+ }
62
+ function convert(item) {
63
+ if (isSimpleType(item)) return item;
64
+ const { $date } = item;
65
+ if ($date != undefined) return new Date(+$date.$numberLong);
66
+ const number = item.$numberInt || item.$numberLong || item.$numberDecimal || item.$numberDouble;
67
+ if (number != undefined) return +number;
68
+ for (const prop in item) {
69
+ item[prop] = convert(item[prop]);
70
+ }
71
+ return item;
72
+ }
73
+ Object.assign(module.exports, { runQueries });
@@ -0,0 +1,74 @@
1
+ const path = require("node:path");
2
+ const fsPromises = require("node:fs/promises");
3
+ const { QyDB } = require("../src/QyDB.js");
4
+ const { folderOrFileExists, isSimpleType } = require("../src/QyUtils.js");
5
+ const { runQueries } = require("./airbnb2_queries.js");
6
+ const { downloadFile } = require("./downloadFile.js");
7
+
8
+ const fileName = "listingsAndReviews.json";
9
+ const fileUrl = `https://raw.githubusercontent.com/neelabalan/mongodb-sample-dataset/refs/heads/main/sample_airbnb/${fileName}`;
10
+ const airbnbJsonFilePath = path.join(__dirname, "../data/", fileName);
11
+ const qyDB = new QyDB();
12
+ const { rootFolder } = qyDB;
13
+
14
+ run();
15
+ async function insertData() {
16
+ console.time("insertData");
17
+ const { batch } = qyDB;
18
+ const fileHandle = await fsPromises.open(airbnbJsonFilePath, "r");
19
+ for await (const line of fileHandle.readLines()) {
20
+ const oneItem = convert(JSON.parse(line));
21
+ const { property_type, room_type, bed_type, _id } = oneItem;
22
+ batch.insert(["airbnb", property_type, room_type, bed_type, _id], oneItem);
23
+ }
24
+ batch.run();
25
+ console.timeEnd("insertData");
26
+ }
27
+ async function run() {
28
+ try {
29
+ logger.log(`Downloading ${fileName}`);
30
+ await downloadFile(fileUrl, airbnbJsonFilePath);
31
+ console.log("done");
32
+ } catch (exception) {
33
+ logger.error(`Downloading ${fileUrl} failed:`, exception);
34
+ return;
35
+ }
36
+ console.time("total");
37
+ if (process.argv[3]) {
38
+ logger.level = process.argv[3];
39
+ }
40
+ let shouldInsertData = true;
41
+ if (process.argv[2] == "true") {
42
+ try {
43
+ await fsPromises.rm(rootFolder, { recursive: true });
44
+ } catch (exception) {}
45
+ shouldInsertData = true;
46
+ } else if (!(await folderOrFileExists(rootFolder))) shouldInsertData = true;
47
+ if (shouldInsertData) {
48
+ console.time("run");
49
+ await qyDB.start();
50
+ await insertData();
51
+ await qyDB.stop();
52
+ console.timeEnd("run");
53
+ }
54
+ console.time("queryWithStartEnd");
55
+ console.time("start");
56
+ await qyDB.start();
57
+ console.timeEnd("start");
58
+ runQueries(qyDB);
59
+ await qyDB.stop();
60
+ console.timeEnd("queryWithStartEnd");
61
+ console.timeEnd("total");
62
+ }
63
+ function convert(item) {
64
+ if (isSimpleType(item)) return item;
65
+ const { $date } = item;
66
+ if ($date != undefined) return new Date(+$date.$numberLong);
67
+ const number = item.$numberInt || item.$numberLong || item.$numberDecimal || item.$numberDouble;
68
+ if (number != undefined) return +number;
69
+ for (const prop in item) {
70
+ item[prop] = convert(item[prop]);
71
+ }
72
+ return item;
73
+ }
74
+ Object.assign(module.exports, { runQueries });
@@ -0,0 +1,231 @@
1
+ const assert = require("node:assert/strict");
2
+
3
+ function query1(qyDB) {
4
+ console.time("query1");
5
+ assert.deepEqual(
6
+ qyDB
7
+ .queryFiles(["airbnb", "Loft", "Entire home/apt", "Real Bed"], {
8
+ address: { street: "Porto, Porto, Portugal" },
9
+ })
10
+ .map((x) => x.view(["name"]).name),
11
+ [
12
+ "Porto city centre apartment H",
13
+ "Romantic loft near historic center",
14
+ "Lovely sun light in a stylish loft, Downtown Spot",
15
+ "Charming & Sunny Mezzanine Loft",
16
+ "Be Happy in Porto",
17
+ "Quarry Apartment",
18
+ "Loft with balcony and view of Casa da Música PORTO",
19
+ "For 4 or 6 - Wonderful Apartment downtown",
20
+ "Clérigos Apartments - Apt. 4E for 2/3 Pax",
21
+ "Bright Top Floor Two Rooms Apartment w/ Terrace",
22
+ "ReUrban Flats - Santa Catarina #3",
23
+ "The Blue and White Porto House - Vintage",
24
+ "4twoLoft at Great Location",
25
+ "Step In Porto - Loft w/Mezzanine",
26
+ "Your OWM Invicta",
27
+ "Pury Apartments",
28
+ "Live Porto & Douro - Breiner III",
29
+ ],
30
+ );
31
+ console.timeEnd("query1");
32
+ }
33
+ function query2(qyDB) {
34
+ console.time("query2");
35
+ assert.deepEqual(
36
+ qyDB
37
+ .queryFiles(
38
+ ["airbnb", "Loft", "Entire home/apt", "Real Bed"],
39
+ {
40
+ address: { street: "Porto, Porto, Portugal" },
41
+ },
42
+ 9,
43
+ )
44
+ .map((x) => x.view(["name"]).name),
45
+ [
46
+ "Porto city centre apartment H",
47
+ "Romantic loft near historic center",
48
+ "Lovely sun light in a stylish loft, Downtown Spot",
49
+ "Charming & Sunny Mezzanine Loft",
50
+ "Be Happy in Porto",
51
+ "Quarry Apartment",
52
+ "Loft with balcony and view of Casa da Música PORTO",
53
+ "For 4 or 6 - Wonderful Apartment downtown",
54
+ "Clérigos Apartments - Apt. 4E for 2/3 Pax",
55
+ ],
56
+ );
57
+ console.timeEnd("query2");
58
+ }
59
+ function query3(qyDB) {
60
+ console.time("query3");
61
+ assert.deepEqual(
62
+ qyDB
63
+ .queryFiles(
64
+ ["airbnb", "Loft", "Entire home/apt", "Real Bed"],
65
+ { address: { street: "Porto, Porto, Portugal" } },
66
+ 9,
67
+ 9,
68
+ )
69
+ .map((x) => x.view(["name"]).name),
70
+ [
71
+ "Bright Top Floor Two Rooms Apartment w/ Terrace",
72
+ "ReUrban Flats - Santa Catarina #3",
73
+ "The Blue and White Porto House - Vintage",
74
+ "4twoLoft at Great Location",
75
+ "Step In Porto - Loft w/Mezzanine",
76
+ "Your OWM Invicta",
77
+ "Pury Apartments",
78
+ "Live Porto & Douro - Breiner III",
79
+ ],
80
+ );
81
+ console.timeEnd("query3");
82
+ }
83
+ function query4(qyDB) {
84
+ console.time("query4");
85
+ assert.deepEqual(
86
+ qyDB.fetch
87
+ .queryFilesMulti([
88
+ [["airbnb", "Loft", "Entire home/apt", "Real Bed"], { address: { street: "Porto, Porto, Portugal" } }],
89
+ ["airbnb/Loft/**", { minimum_nights: (x) => x >= 60 }],
90
+ ])
91
+ .run()[0]
92
+ .map((x) => x.view(["name"]).name)
93
+ .sort(),
94
+ [
95
+ "Porto city centre apartment H",
96
+ "Romantic loft near historic center",
97
+ "Lovely sun light in a stylish loft, Downtown Spot",
98
+ "Charming & Sunny Mezzanine Loft",
99
+ "Brand New Luxury Downtown Studio",
100
+ "Be Happy in Porto",
101
+ "Quarry Apartment",
102
+ "Loft with balcony and view of Casa da Música PORTO",
103
+ "For 4 or 6 - Wonderful Apartment downtown",
104
+ "Clérigos Apartments - Apt. 4E for 2/3 Pax",
105
+ "Bright Top Floor Two Rooms Apartment w/ Terrace",
106
+ "ReUrban Flats - Santa Catarina #3",
107
+ "The Blue and White Porto House - Vintage",
108
+ "4twoLoft at Great Location",
109
+ "Step In Porto - Loft w/Mezzanine",
110
+ "Your OWM Invicta",
111
+ "Pury Apartments",
112
+ "Live Porto & Douro - Breiner III",
113
+ ].sort(),
114
+ );
115
+ console.timeEnd("query4");
116
+ }
117
+ function query5(qyDB) {
118
+ console.time("query5");
119
+ assert.deepEqual(
120
+ qyDB
121
+ .queryFilesMulti(
122
+ [
123
+ [
124
+ ["airbnb", "Loft", "Entire home/apt", "Real Bed"],
125
+ { address: { street: "Porto, Porto, Portugal" } },
126
+ ],
127
+ ["airbnb/Loft/**", { minimum_nights: (x) => x >= 60 }],
128
+ ],
129
+ 3,
130
+ )
131
+ .map((x) => x.view(["name"]).name),
132
+ [
133
+ "Porto city centre apartment H",
134
+ "Romantic loft near historic center",
135
+ "Lovely sun light in a stylish loft, Downtown Spot",
136
+ ],
137
+ );
138
+ console.timeEnd("query5");
139
+ }
140
+ function query6(qyDB) {
141
+ console.time("query6");
142
+ assert.deepEqual(
143
+ qyDB
144
+ .queryFilesMulti(
145
+ [
146
+ [
147
+ ["airbnb", "Loft", "Entire home/apt", "Real Bed"],
148
+ { address: { street: "Porto, Porto, Portugal" } },
149
+ ],
150
+ ["airbnb/Loft/**", { minimum_nights: (x) => x >= 60 }],
151
+ ],
152
+ 20,
153
+ 10,
154
+ )
155
+ .map((x) => x.view(["name"]).name),
156
+ [
157
+ "ReUrban Flats - Santa Catarina #3",
158
+ "The Blue and White Porto House - Vintage",
159
+ "4twoLoft at Great Location",
160
+ "Step In Porto - Loft w/Mezzanine",
161
+ "Your OWM Invicta",
162
+ "Pury Apartments",
163
+ "Live Porto & Douro - Breiner III",
164
+ "Brand New Luxury Downtown Studio",
165
+ ],
166
+ );
167
+ console.timeEnd("query6");
168
+ }
169
+ function query7(qyDB) {
170
+ console.time("query7");
171
+ assert.deepEqual(
172
+ qyDB.queryFiles(["airbnb", "Loft", "**"], { minimum_nights: (x) => x >= 60 }).map((x) => x.view(["name"]).name),
173
+ ["Romantic loft near historic center", "Brand New Luxury Downtown Studio"],
174
+ );
175
+ console.timeEnd("query7");
176
+ }
177
+ function query8(qyDB) {
178
+ console.time("query8");
179
+ assert.deepEqual(
180
+ qyDB
181
+ .queryFiles(["airbnb", "Loft", ""], { minimum_nights: (x) => x >= 60 }, 10, 10)
182
+ .map((x) => x.view(["name"]).name),
183
+ [],
184
+ );
185
+ console.timeEnd("query8");
186
+ }
187
+ function query9(qyDB) {
188
+ console.time("query9");
189
+ assert.deepEqual(
190
+ qyDB
191
+ .queryFilesMulti(
192
+ [
193
+ [
194
+ ["airbnb", "Loft", "Entire home/apt", "Real Bed"],
195
+ { address: { street: "Porto, Porto, Portugal" } },
196
+ ],
197
+ ["airbnb/Loft/**", { minimum_nights: (x) => x >= 60 }],
198
+ ],
199
+ 3,
200
+ )
201
+ .map((x) => x.view(["name"]).name),
202
+ [
203
+ "Porto city centre apartment H",
204
+ "Romantic loft near historic center",
205
+ "Lovely sun light in a stylish loft, Downtown Spot",
206
+ ],
207
+ );
208
+ console.timeEnd("query9");
209
+ }
210
+ function runQueries(qyDB) {
211
+ query1(qyDB);
212
+ query1(qyDB);
213
+ query2(qyDB);
214
+ query2(qyDB);
215
+ query3(qyDB);
216
+ query3(qyDB);
217
+ query4(qyDB);
218
+ query4(qyDB);
219
+ query5(qyDB);
220
+ query5(qyDB);
221
+ query6(qyDB);
222
+ query6(qyDB);
223
+ query7(qyDB);
224
+ query7(qyDB);
225
+ query8(qyDB);
226
+ query8(qyDB);
227
+ query9(qyDB);
228
+ query9(qyDB);
229
+ }
230
+
231
+ Object.assign(module.exports, { runQueries });