datafolder 1.0.7 → 1.0.9

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 (124) hide show
  1. package/README.md +4 -1
  2. package/index.js +1 -1
  3. package/internal.md +5 -3
  4. package/package.json +7 -5
  5. package/scripts/airbnb.js +77 -72
  6. package/scripts/airbnb2.js +78 -73
  7. package/scripts/airbnb2_queries.js +181 -229
  8. package/scripts/airbnb_queries.js +214 -265
  9. package/scripts/convertBinFiles.js +47 -49
  10. package/scripts/downloadFile.js +41 -40
  11. package/scripts/express.js +44 -48
  12. package/scripts/fastify.js +71 -80
  13. package/scripts/flights.js +71 -75
  14. package/scripts/flights2.js +78 -81
  15. package/scripts/flights2_queries.js +417 -455
  16. package/scripts/flights_queries.js +381 -407
  17. package/src/QyAcl.d.ts +17 -0
  18. package/src/QyAcl.js +113 -116
  19. package/src/QyAclSaver.d.ts +4 -0
  20. package/src/QyAclSaver.js +3 -3
  21. package/src/QyAclSaver_Worker.d.ts +9 -0
  22. package/src/QyAclSaver_Worker.js +2 -4
  23. package/src/QyAclWriter.d.ts +5 -0
  24. package/src/QyBatch.d.ts +42 -0
  25. package/src/QyBatch.js +8 -8
  26. package/src/QyBinWriter.d.ts +15 -0
  27. package/src/QyCD.d.ts +37 -0
  28. package/src/QyCD.js +8 -6
  29. package/src/QyCache.d.ts +44 -0
  30. package/src/QyCache.js +65 -59
  31. package/src/QyCompressor.d.ts +9 -0
  32. package/src/QyCompressor.js +29 -29
  33. package/src/QyCompressor_Worker.d.ts +1 -0
  34. package/src/QyDB.d.ts +42 -0
  35. package/src/QyDB.js +13 -11
  36. package/src/QyDefaultOptions.d.ts +1 -0
  37. package/src/QyDefaultOptions.js +3 -0
  38. package/src/QyDir.d.ts +48 -0
  39. package/src/QyDir.js +41 -41
  40. package/src/QyFetch.d.ts +7 -0
  41. package/src/QyFetch.js +2 -3
  42. package/src/QyFile.d.ts +19 -0
  43. package/src/QyFile.js +35 -34
  44. package/src/QyFileContentUtils.d.ts +13 -0
  45. package/src/QyFileContentUtils.js +126 -126
  46. package/src/QyFileLogger.d.ts +11 -0
  47. package/src/QyIndex.d.ts +6 -0
  48. package/src/QyIndex.js +73 -75
  49. package/src/QyIndexPropTree.d.ts +7 -0
  50. package/src/QyIndexPropTree.js +24 -24
  51. package/src/QyIntersectNode.d.ts +7 -0
  52. package/src/QyIntersectNode.js +15 -15
  53. package/src/QyItem.d.ts +30 -0
  54. package/src/QyItem.js +32 -28
  55. package/src/QyJsonSaver.d.ts +6 -0
  56. package/src/QyKVData.d.ts +12 -0
  57. package/src/QyKVData.js +11 -10
  58. package/src/QyKVDataCleaner.d.ts +4 -0
  59. package/src/QyKVDataCleaner.js +1 -1
  60. package/src/QyKVDataCleaner_Worker.d.ts +13 -0
  61. package/src/QyKVDataCleaner_Worker.js +2 -4
  62. package/src/QyKVDataLoader.d.ts +9 -0
  63. package/src/QyKVDataLoader.js +11 -11
  64. package/src/QyKVDataSaver.d.ts +4 -0
  65. package/src/QyKVDataSaver.js +6 -6
  66. package/src/QyKVDataSaver_Worker.d.ts +13 -0
  67. package/src/QyKVDataSaver_Worker.js +7 -9
  68. package/src/QyLogFileSaver.d.ts +4 -0
  69. package/src/QyLogFileSaver.js +1 -1
  70. package/src/QyLogFileSaver_Worker.d.ts +8 -0
  71. package/src/QyLogFileSaver_Worker.js +2 -4
  72. package/src/QyLogger.d.ts +10 -0
  73. package/src/QyLogger.js +40 -40
  74. package/src/QyMessageWorker.d.ts +5 -0
  75. package/src/QyMessageWorker.js +7 -7
  76. package/src/QyMessager.d.ts +7 -0
  77. package/src/QyMessager.js +30 -27
  78. package/src/QyMessager_Run.d.ts +1 -0
  79. package/src/QyMessager_Run.js +11 -6
  80. package/src/QyMod.d.ts +1 -0
  81. package/src/QyMod.js +6 -6
  82. package/src/QyOpRunner.d.ts +10 -0
  83. package/src/QyOpRunner.js +39 -39
  84. package/src/QyQueue.d.ts +11 -0
  85. package/src/QySaver.d.ts +10 -0
  86. package/src/QySnapshotCompactor.d.ts +4 -0
  87. package/src/QySnapshotCompactor.js +1 -1
  88. package/src/QySnapshotCompactor_Worker.d.ts +11 -0
  89. package/src/QySnapshotCompactor_Worker.js +2 -4
  90. package/src/QySnapshotSaver.d.ts +4 -0
  91. package/src/QySnapshotSaver.js +1 -1
  92. package/src/QySnapshotSaver_Worker.d.ts +16 -0
  93. package/src/QySnapshotSaver_Worker.js +17 -16
  94. package/src/QySnapshots.d.ts +27 -0
  95. package/src/QySnapshots.js +3 -3
  96. package/src/QyStructure.d.ts +1 -0
  97. package/src/QyStructure.js +18 -18
  98. package/src/QyTrigger.d.ts +9 -0
  99. package/src/QyTrigger.js +30 -30
  100. package/src/QyUnionNode.d.ts +17 -0
  101. package/src/QyUnionNode.js +2 -2
  102. package/src/QyUtils.d.ts +53 -0
  103. package/src/QyUtils.js +140 -117
  104. package/src/QyWorker.d.ts +5 -0
  105. package/src/QyWorker.js +18 -18
  106. package/src/hackernews.d.ts +1 -0
  107. package/src/hackernews.js +59 -47
  108. package/src/rpc/getRpcFiles.js +1 -0
  109. package/src/simulate.d.ts +1 -0
  110. package/src/simulate2.d.ts +1 -0
  111. package/src/soc_pokec.d.ts +1 -0
  112. package/static/codemirror.bundle/editor.bundle.js +399 -184
  113. package/static/datafolderui/QyStyledElement.js +2 -2
  114. package/static/datafolderui/QyUIFolderViewer.js +97 -77
  115. package/static/datafolderui/QyUIUtils.js +4 -4
  116. package/static/datafolderui/code-mirror.js +7 -7
  117. package/static/datafolderui/display-tree.js +13 -10
  118. package/static/datafolderui/hscrpt.js +26 -7
  119. package/static/datafolderui/json-view.js +27 -0
  120. package/static/datafolderui/main-page.js +65 -37
  121. package/static/datafolderui/query-editor.js +8 -8
  122. package/static/datafolderui/rpc-editor.js +31 -0
  123. package/static/datafolderui/trigger-editor.js +9 -6
  124. package/static/datafolderui/QyUIFolderTree.js +0 -188
package/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # datafolder
2
2
 
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.
3
+ datafolder is a lightning fluid JSON database for SSD, 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
6
 
7
+ 1.0.9 Added Typescript type declarations.
8
+
7
9
  1.0.7 Added trigger editor and [internal.md](https://github.com/datafolderdev/datafolder/wiki/Internals-of-datafolder)
10
+
8
11
  1.0.6 Added code mirror as the query editor.
9
12
 
10
13
  ## Installation
package/index.js CHANGED
@@ -1 +1 @@
1
- export { QyDB as default } from "./src/QyDB.js";
1
+ export { QyDB as default } from "./src/QyDB.js";
package/internal.md CHANGED
@@ -24,7 +24,7 @@ Airports can be stored as
24
24
  airports/{country}/{state}/{airport_id}
25
25
  ```
26
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.
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 go with the most comfortable one for your mental mind.
28
28
 
29
29
  ### In-memory Tree Structure
30
30
 
@@ -56,7 +56,7 @@ JSON changes are further separated into the following:
56
56
  2. Adding a property with value.
57
57
  3. Changing a property to new value.
58
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.
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 re-applying these acl files in sequence.
60
60
 
61
61
  ### Snapshots
62
62
 
@@ -114,6 +114,8 @@ hackernews/{year}/{month}/{day}/{id}
114
114
 
115
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
116
 
117
- ### Snapshot Shadowing and Compaction
117
+ <img width="100%" alt="hackernews" src="https://github.com/user-attachments/assets/c7788426-9079-4628-b82f-c1bbff70f6ed" />
118
+
119
+ ### Snapshot Shadowing and compactions
118
120
 
119
121
  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,11 +1,11 @@
1
1
  {
2
2
  "name": "datafolder",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "author": {
5
5
  "name": "lysh",
6
6
  "email": "datafolderdev@outlook.com"
7
7
  },
8
- "description": "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.",
8
+ "description": "datafolder is a lightning fluid JSON database for SSD, 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.",
9
9
  "license": "MIT",
10
10
  "repository": {
11
11
  "type": "git",
@@ -42,15 +42,17 @@
42
42
  ],
43
43
  "dependencies": {},
44
44
  "devDependencies": {
45
- "fastify": "^5.0.0",
46
45
  "@fastify/compress": "^8.1.0",
47
46
  "@fastify/express": "^4.0.2",
48
- "@fastify/static": "^8.3.0"
47
+ "@fastify/static": "^8.3.0",
48
+ "fastify": "^5.0.0"
49
49
  },
50
50
  "exports": "./index.js",
51
+ "types": "./src/QyDB.d.ts",
51
52
  "scripts": {
52
53
  "ui": "start http://localhost:3628/datafolderui/index.html && node ./scripts/fastify.js",
53
- "airbnb": "node ./scripts/airbnb2.js && start http://localhost:3628/datafolderui/index.html && node ./scripts/fastify.js"
54
+ "airbnb": "node ./scripts/airbnb2.js && start http://localhost:3628/datafolderui/index.html && node ./scripts/fastify.js",
55
+ "types:check": "tsc --noEmit"
54
56
  },
55
57
  "type": "module"
56
58
  }
package/scripts/airbnb.js CHANGED
@@ -1,72 +1,77 @@
1
- import { join } from "node:path";
2
- import { open, rm } from "node:fs/promises";
3
- import { QyDB } from "../src/QyDB.js";
4
- import { folderOrFileExists, isSimpleType, __dirname } from "../src/QyUtils.js";
5
- import { runQueries } from "./airbnb_queries.js";
6
- import { downloadFile } from "./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 = 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 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 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
- }
1
+ import { join } from "node:path";
2
+ import { open, rm } from "node:fs/promises";
3
+ import { QyDB } from "../src/QyDB.js";
4
+ import { folderOrFileExists, isSimpleType, __dirname } from "../src/QyUtils.js";
5
+ import { runQueries } from "./airbnb_queries.js";
6
+ import { downloadFile } from "./downloadFile.js";
7
+ const fileName = "listingsAndReviews.json";
8
+ const fileUrl = `https://raw.githubusercontent.com/neelabalan/mongodb-sample-dataset/refs/heads/main/sample_airbnb/${fileName}`;
9
+ const airbnbJsonFilePath = join(__dirname, "../data/", fileName);
10
+ const qyDB = new QyDB();
11
+ const { rootFolder } = qyDB;
12
+ run();
13
+ async function insertData() {
14
+ console.time("insertData");
15
+ const { batch } = qyDB;
16
+ const fileHandle = await open(airbnbJsonFilePath, "r");
17
+ for await (const line of fileHandle.readLines()) {
18
+ const oneItem = convert(JSON.parse(line));
19
+ batch.insert(["airbnb1", oneItem._id], oneItem);
20
+ }
21
+ batch.run();
22
+ console.timeEnd("insertData");
23
+ }
24
+ async function run() {
25
+ try {
26
+ logger.log(`Downloading ${fileName}`);
27
+ await downloadFile(fileUrl, airbnbJsonFilePath);
28
+ console.log("done");
29
+ }
30
+ catch (exception) {
31
+ logger.error(`Downloading ${fileUrl} failed:`, exception);
32
+ return;
33
+ }
34
+ console.time("total");
35
+ if (process.argv[3]) {
36
+ logger.level = process.argv[3];
37
+ }
38
+ let shouldInsertData = true;
39
+ if (process.argv[2] == "true") {
40
+ try {
41
+ await rm(rootFolder, { recursive: true });
42
+ }
43
+ catch (exception) { }
44
+ shouldInsertData = true;
45
+ }
46
+ else if (!(await folderOrFileExists(rootFolder)))
47
+ shouldInsertData = true;
48
+ if (shouldInsertData) {
49
+ console.time("run");
50
+ await qyDB.start();
51
+ await insertData();
52
+ await qyDB.stop();
53
+ console.timeEnd("run");
54
+ }
55
+ console.time("queryWithStartEnd");
56
+ console.time("start");
57
+ await qyDB.start();
58
+ console.timeEnd("start");
59
+ runQueries(qyDB);
60
+ await qyDB.stop();
61
+ console.timeEnd("queryWithStartEnd");
62
+ console.timeEnd("total");
63
+ }
64
+ function convert(item) {
65
+ if (isSimpleType(item))
66
+ return item;
67
+ const { $date } = item;
68
+ if ($date != undefined)
69
+ return new Date(+$date.$numberLong);
70
+ const number = item.$numberInt || item.$numberLong || item.$numberDecimal || item.$numberDouble;
71
+ if (number != undefined)
72
+ return +number;
73
+ for (const prop in item) {
74
+ item[prop] = convert(item[prop]);
75
+ }
76
+ return item;
77
+ }
@@ -1,73 +1,78 @@
1
- import { join } from "node:path";
2
- import { open, rm } from "node:fs/promises";
3
- import { QyDB } from "../src/QyDB.js";
4
- import { folderOrFileExists, isSimpleType, __dirname } from "../src/QyUtils.js";
5
- import { runQueries } from "./airbnb2_queries.js";
6
- import { downloadFile } from "./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 = 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 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 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
- }
1
+ import { join } from "node:path";
2
+ import { open, rm } from "node:fs/promises";
3
+ import { QyDB } from "../src/QyDB.js";
4
+ import { folderOrFileExists, isSimpleType, __dirname } from "../src/QyUtils.js";
5
+ import { runQueries } from "./airbnb2_queries.js";
6
+ import { downloadFile } from "./downloadFile.js";
7
+ const fileName = "listingsAndReviews.json";
8
+ const fileUrl = `https://raw.githubusercontent.com/neelabalan/mongodb-sample-dataset/refs/heads/main/sample_airbnb/${fileName}`;
9
+ const airbnbJsonFilePath = join(__dirname, "../data/", fileName);
10
+ const qyDB = new QyDB();
11
+ const { rootFolder } = qyDB;
12
+ run();
13
+ async function insertData() {
14
+ console.time("insertData");
15
+ const { batch } = qyDB;
16
+ const fileHandle = await open(airbnbJsonFilePath, "r");
17
+ for await (const line of fileHandle.readLines()) {
18
+ const oneItem = convert(JSON.parse(line));
19
+ const { property_type, room_type, bed_type, _id } = oneItem;
20
+ batch.insert(["airbnb", property_type, room_type, bed_type, _id], oneItem);
21
+ }
22
+ batch.run();
23
+ console.timeEnd("insertData");
24
+ }
25
+ async function run() {
26
+ try {
27
+ logger.log(`Downloading ${fileName}`);
28
+ await downloadFile(fileUrl, airbnbJsonFilePath);
29
+ console.log("done");
30
+ }
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 rm(rootFolder, { recursive: true });
43
+ }
44
+ catch (exception) { }
45
+ shouldInsertData = true;
46
+ }
47
+ else if (!(await folderOrFileExists(rootFolder)))
48
+ shouldInsertData = true;
49
+ if (shouldInsertData) {
50
+ console.time("run");
51
+ await qyDB.start();
52
+ await insertData();
53
+ await qyDB.stop();
54
+ console.timeEnd("run");
55
+ }
56
+ console.time("queryWithStartEnd");
57
+ console.time("start");
58
+ await qyDB.start();
59
+ console.timeEnd("start");
60
+ runQueries(qyDB);
61
+ await qyDB.stop();
62
+ console.timeEnd("queryWithStartEnd");
63
+ console.timeEnd("total");
64
+ }
65
+ function convert(item) {
66
+ if (isSimpleType(item))
67
+ return item;
68
+ const { $date } = item;
69
+ if ($date != undefined)
70
+ return new Date(+$date.$numberLong);
71
+ const number = item.$numberInt || item.$numberLong || item.$numberDecimal || item.$numberDouble;
72
+ if (number != undefined)
73
+ return +number;
74
+ for (const prop in item) {
75
+ item[prop] = convert(item[prop]);
76
+ }
77
+ return item;
78
+ }