diff --git a/packages/core/__benches__/relations.bench.ts b/packages/core/__benches__/relations.bench.ts
new file mode 100644
index 000000000..e28e4f01d
--- /dev/null
+++ b/packages/core/__benches__/relations.bench.ts
@@ -0,0 +1,57 @@
+/*
+This file is part of the Notesnook project (https://notesnook.com/)
+
+Copyright (C) 2023 Streetwriters (Private) Limited
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+import { bench, describe } from "vitest";
+import { databaseTest } from "../__tests__/utils";
+
+describe("relations", async () => {
+ const db = await databaseTest();
+ // const totalNotebooks = 10;
+
+ // let parentNotebookId: string | undefined = undefined;
+ // for (let i = 1; i <= 100; ++i) {
+ // const id = await db.notebooks.add({ title: `notebook-somethign-${i}` });
+ // if (parentNotebookId)
+ // await db.relations.add(
+ // { id: parentNotebookId, type: "notebook" },
+ // { id, type: "notebook" }
+ // );
+ // parentNotebookId = id;
+ // for (let j = 1; j <= 100; ++j) {
+ // await db.relations.add(
+ // { type: "notebook", id },
+ // { id: `${j * i}-note`, type: "note" }
+ // );
+ // }
+ // }
+
+ //
+
+ // const id2 = await db.notebooks.add({ title: `notebook-somethign` });
+
+ console.log(await db.notebooks.totalNotes("6516a04a35a073f359e7e801"));
+
+ // console.log(
+ // await db.relations.from({ id: "8-note", type: "note" }, "notebook").unlink()
+ // );
+
+ // bench("get some relations from 10k relations", async () => {
+ // await db.notebooks.totalNotes("6516a04a35a073f359e7e801");
+ // });
+});
diff --git a/packages/core/__tests__/notes.test.ts b/packages/core/__tests__/notes.test.ts
index dc62615af..31f31c7ed 100644
--- a/packages/core/__tests__/notes.test.ts
+++ b/packages/core/__tests__/notes.test.ts
@@ -68,11 +68,12 @@ test("add invalid note", () =>
expect(db.notes.add({ hello: "world" })).rejects.toThrow();
}));
-test("add note", () =>
+test.only("add note", () =>
noteTest().then(async ({ db, id }) => {
- const note = db.notes.note(id);
+ const note = await db.notes.note$(id);
expect(note).toBeDefined();
- expect(await note?.content()).toStrictEqual(TEST_NOTE.content.data);
+ const content = await db.content.get(note!.contentId!);
+ expect(content!.data).toStrictEqual(TEST_NOTE.content.data);
}));
test("get note content", () =>
diff --git a/packages/core/nn.db b/packages/core/nn.db
new file mode 100644
index 000000000..2701cf90a
Binary files /dev/null and b/packages/core/nn.db differ
diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json
index 94a35ef6e..2258b8bed 100644
--- a/packages/core/package-lock.json
+++ b/packages/core/package-lock.json
@@ -20,6 +20,7 @@
"html-to-text": "^9.0.5",
"htmlparser2": "^8.0.1",
"katex": "0.16.2",
+ "kysely": "^0.26.3",
"linkedom": "^0.14.17",
"liqe": "^1.13.0",
"mime-db": "1.52.0",
@@ -30,6 +31,7 @@
},
"devDependencies": {
"@notesnook/crypto": "file:../crypto",
+ "@types/better-sqlite3": "^7.6.5",
"@types/event-source-polyfill": "^1.0.1",
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.2",
@@ -40,6 +42,7 @@
"@types/ws": "^8.5.5",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
+ "better-sqlite3": "^8.6.0",
"cross-env": "^7.0.3",
"dotenv": "^16.0.1",
"event-source-polyfill": "^1.0.31",
@@ -2173,6 +2176,15 @@
"node": ">= 10"
}
},
+ "node_modules/@types/better-sqlite3": {
+ "version": "7.6.9",
+ "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.9.tgz",
+ "integrity": "sha512-FvktcujPDj9XKMJQWFcl2vVl7OdRIqsSRX9b0acWwTmwLK9CF2eqo/FRcmMLNpugKoX/avA6pb7TorDLmpgTnQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/chai": {
"version": "4.3.14",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz",
@@ -2444,6 +2456,57 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/better-sqlite3": {
+ "version": "8.7.0",
+ "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-8.7.0.tgz",
+ "integrity": "sha512-99jZU4le+f3G6aIl6PmmV0cxUIWqKieHxsiF7G34CVFiE+/UabpYqkU0NJIkY/96mQKikHeBjtR27vFfs5JpEw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "bindings": "^1.5.0",
+ "prebuild-install": "^7.1.1"
+ }
+ },
+ "node_modules/bindings": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+ "dev": true,
+ "dependencies": {
+ "file-uri-to-path": "1.0.0"
+ }
+ },
+ "node_modules/bl": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+ "dev": true,
+ "dependencies": {
+ "buffer": "^5.5.0",
+ "inherits": "^2.0.4",
+ "readable-stream": "^3.4.0"
+ }
+ },
"node_modules/boolbase": {
"version": "1.0.0",
"license": "ISC"
@@ -2457,6 +2520,30 @@
"concat-map": "0.0.1"
}
},
+ "node_modules/buffer": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.1.13"
+ }
+ },
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@@ -2523,6 +2610,12 @@
"node": "*"
}
},
+ "node_modules/chownr": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+ "dev": true
+ },
"node_modules/combined-stream": {
"version": "1.0.8",
"dev": true,
@@ -2667,6 +2760,21 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/deep-eql": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
@@ -2679,6 +2787,15 @@
"node": ">=6"
}
},
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
"node_modules/deepmerge": {
"version": "4.3.1",
"license": "MIT",
@@ -2694,6 +2811,15 @@
"node": ">=0.4.0"
}
},
+ "node_modules/detect-libc": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
+ "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/diff-sequences": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
@@ -2773,6 +2899,15 @@
"node": ">=12"
}
},
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
"node_modules/entities": {
"version": "4.5.0",
"license": "BSD-2-Clause",
@@ -2840,6 +2975,15 @@
"node": ">=12.0.0"
}
},
+ "node_modules/expand-template": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/fetch-cookie": {
"version": "2.1.0",
"license": "Unlicense",
@@ -2848,6 +2992,12 @@
"tough-cookie": "^4.0.0"
}
},
+ "node_modules/file-uri-to-path": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+ "dev": true
+ },
"node_modules/form-data": {
"version": "4.0.0",
"dev": true,
@@ -2861,25 +3011,17 @@
"node": ">= 6"
}
},
+ "node_modules/fs-constants": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+ "dev": true
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"dev": true,
"license": "ISC"
},
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
"node_modules/get-func-name": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
@@ -2889,6 +3031,12 @@
"node": "*"
}
},
+ "node_modules/github-from-package": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+ "dev": true
+ },
"node_modules/glob": {
"version": "7.2.3",
"dev": true,
@@ -3032,6 +3180,26 @@
"node": ">=0.10.0"
}
},
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/inflight": {
"version": "1.0.6",
"dev": true,
@@ -3046,6 +3214,12 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true
+ },
"node_modules/is-alphabetical": {
"version": "2.0.1",
"dev": true,
@@ -3252,6 +3426,14 @@
"node": ">= 12"
}
},
+ "node_modules/kysely": {
+ "version": "0.26.3",
+ "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.26.3.tgz",
+ "integrity": "sha512-yWSgGi9bY13b/W06DD2OCDDHQmq1kwTGYlQ4wpZkMOJqMGCstVCFIvxCCVG4KfY1/3G0MhDAcZsip/Lw8/vJWw==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/leac": {
"version": "0.6.0",
"license": "MIT",
@@ -3360,6 +3542,18 @@
"node": ">= 0.6"
}
},
+ "node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"dev": true,
@@ -3371,6 +3565,21 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp-classic": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+ "dev": true
+ },
"node_modules/mlly": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz",
@@ -3397,6 +3606,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/napi-build-utils": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
+ "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
+ "dev": true
+ },
"node_modules/nearley": {
"version": "2.20.1",
"license": "MIT",
@@ -3421,6 +3636,18 @@
"version": "2.20.3",
"license": "MIT"
},
+ "node_modules/node-abi": {
+ "version": "3.56.0",
+ "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.56.0.tgz",
+ "integrity": "sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/node-fetch": {
"version": "2.6.7",
"license": "MIT",
@@ -3644,6 +3871,32 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/prebuild-install": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz",
+ "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==",
+ "dev": true,
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "expand-template": "^2.0.3",
+ "github-from-package": "0.0.0",
+ "minimist": "^1.2.3",
+ "mkdirp-classic": "^0.5.3",
+ "napi-build-utils": "^1.0.1",
+ "node-abi": "^3.3.0",
+ "pump": "^3.0.0",
+ "rc": "^1.2.7",
+ "simple-get": "^4.0.0",
+ "tar-fs": "^2.0.0",
+ "tunnel-agent": "^0.6.0"
+ },
+ "bin": {
+ "prebuild-install": "bin.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
@@ -3678,6 +3931,16 @@
"version": "1.9.0",
"license": "MIT"
},
+ "node_modules/pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dev": true,
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"license": "MIT",
@@ -3708,12 +3971,41 @@
"node": ">=0.12"
}
},
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "dev": true,
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
"node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
"dev": true
},
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/refractor": {
"version": "4.8.1",
"dev": true,
@@ -3781,6 +4073,26 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
"node_modules/safer-buffer": {
"version": "2.1.2",
"dev": true,
@@ -3849,6 +4161,51 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/simple-concat": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/simple-get": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+ "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "decompress-response": "^6.0.0",
+ "once": "^1.3.1",
+ "simple-concat": "^1.0.0"
+ }
+ },
"node_modules/source-map": {
"version": "0.6.1",
"dev": true,
@@ -3889,6 +4246,24 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/strip-literal": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz",
@@ -3917,6 +4292,34 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tar-fs": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
+ "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
+ "dev": true,
+ "dependencies": {
+ "chownr": "^1.1.1",
+ "mkdirp-classic": "^0.5.2",
+ "pump": "^3.0.0",
+ "tar-stream": "^2.1.4"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+ "dev": true,
+ "dependencies": {
+ "bl": "^4.0.3",
+ "end-of-stream": "^1.4.1",
+ "fs-constants": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/test-exclude": {
"version": "6.0.0",
"dev": true,
@@ -3992,6 +4395,18 @@
"version": "2.4.1",
"license": "0BSD"
},
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@@ -4026,6 +4441,12 @@
"requires-port": "^1.0.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
"node_modules/v8-to-istanbul": {
"version": "9.2.0",
"dev": true,
diff --git a/packages/core/package.json b/packages/core/package.json
index ef7814a58..c8137e13a 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -9,6 +9,7 @@
},
"devDependencies": {
"@notesnook/crypto": "file:../crypto",
+ "@types/better-sqlite3": "^7.6.5",
"@types/event-source-polyfill": "^1.0.1",
"@types/html-to-text": "^9.0.0",
"@types/katex": "^0.16.2",
@@ -19,6 +20,7 @@
"@types/ws": "^8.5.5",
"@vitest/coverage-v8": "^0.34.1",
"abortcontroller-polyfill": "^1.7.3",
+ "better-sqlite3": "^8.6.0",
"cross-env": "^7.0.3",
"dotenv": "^16.0.1",
"event-source-polyfill": "^1.0.31",
@@ -54,6 +56,7 @@
"html-to-text": "^9.0.5",
"htmlparser2": "^8.0.1",
"katex": "0.16.2",
+ "kysely": "^0.26.3",
"linkedom": "^0.14.17",
"liqe": "^1.13.0",
"mime-db": "1.52.0",
diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts
index c41e4da85..ad92931de 100644
--- a/packages/core/src/api/index.ts
+++ b/packages/core/src/api/index.ts
@@ -60,6 +60,9 @@ import {
import TokenManager from "./token-manager";
import { Attachment } from "../types";
import { Settings } from "../collections/settings";
+import { DatabaseAccessor, DatabaseSchema, createDatabase } from "../database";
+import { Kysely, SqliteDriver, Transaction } from "kysely";
+import BetterSQLite3 from "better-sqlite3";
type EventSourceConstructor = new (
uri: string,
@@ -111,6 +114,32 @@ class Database {
return this.options.compressor;
};
+ private _sql?: Kysely;
+ sql: DatabaseAccessor = () => {
+ if (this._transaction) return this._transaction;
+
+ if (!this._sql)
+ throw new Error(
+ "Database not initialized. Did you forget to call db.init()?"
+ );
+ return this._sql;
+ };
+
+ private _transaction?: Transaction;
+ transaction = (
+ executor: (tr: Transaction) => void | Promise
+ ) => {
+ if (this._transaction) return executor(this._transaction);
+ return this.sql()
+ .transaction()
+ .execute(async (tr) => {
+ this._transaction = tr;
+ await executor(tr);
+ this._transaction = undefined;
+ })
+ .finally(() => (this._transaction = undefined));
+ };
+
private options?: Options;
EventSource?: EventSourceConstructor;
eventSource?: EventSource | null;
@@ -144,6 +173,7 @@ class Database {
reminders = new Reminders(this);
relations = new Relations(this);
notes = new Notes(this);
+
// constructor() {
// this.sseMutex = new Mutex();
// // this.lastHeartbeat = undefined; // { local: 0, server: 0 };
@@ -170,8 +200,8 @@ class Database {
this
);
EV.subscribe(EVENTS.attachmentDeleted, async (attachment: Attachment) => {
- await this.fs().cancel(attachment.metadata.hash, "upload");
- await this.fs().cancel(attachment.metadata.hash, "download");
+ await this.fs().cancel(attachment.hash, "upload");
+ await this.fs().cancel(attachment.hash, "download");
});
EV.subscribe(EVENTS.userLoggedOut, async () => {
await this.monographs.clear();
@@ -179,6 +209,10 @@ class Database {
this.disconnectSSE();
});
+ this._sql = await createDatabase(
+ new SqliteDriver({ database: BetterSQLite3("nn.db") })
+ );
+
await this._validate();
await this.initCollections();
diff --git a/packages/core/src/api/sync/collector.ts b/packages/core/src/api/sync/collector.ts
index 199cbcc76..204632984 100644
--- a/packages/core/src/api/sync/collector.ts
+++ b/packages/core/src/api/sync/collector.ts
@@ -66,21 +66,21 @@ class Collector {
}
}
- for (const itemType in SYNC_COLLECTIONS_MAP) {
- const collectionKey =
- SYNC_COLLECTIONS_MAP[itemType as keyof typeof SYNC_COLLECTIONS_MAP];
- const collection = this.db[collectionKey].collection;
- for (const chunk of collection.iterateSync(chunkSize)) {
- const items = await this.prepareChunk(
- chunk,
- lastSyncedTimestamp,
- isForceSync,
- key
- );
- if (!items) continue;
- yield { items, type: itemType as keyof typeof SYNC_COLLECTIONS_MAP };
- }
- }
+ // for (const itemType in SYNC_COLLECTIONS_MAP) {
+ // const collectionKey =
+ // SYNC_COLLECTIONS_MAP[itemType as keyof typeof SYNC_COLLECTIONS_MAP];
+ // const collection = this.db[collectionKey].collection;
+ // for (const chunk of collection.iterateSync(chunkSize)) {
+ // const items = await this.prepareChunk(
+ // chunk,
+ // lastSyncedTimestamp,
+ // isForceSync,
+ // key
+ // );
+ // if (!items) continue;
+ // yield { items, type: itemType as keyof typeof SYNC_COLLECTIONS_MAP };
+ // }
+ // }
}
async prepareChunk(
diff --git a/packages/core/src/collections/attachments.ts b/packages/core/src/collections/attachments.ts
index e95e0ada9..4c8d04244 100644
--- a/packages/core/src/collections/attachments.ts
+++ b/packages/core/src/collections/attachments.ts
@@ -29,21 +29,18 @@ import {
isWebClip
} from "../utils/filename";
import { Cipher, DataFormat, SerializedKey } from "@notesnook/crypto";
-import { CachedCollection } from "../database/cached-collection";
import { Output } from "../interfaces";
-import { Attachment, AttachmentMetadata, isDeleted } from "../types";
+import { Attachment } from "../types";
import Database from "../api";
+import { SQLCollection } from "../database/sql-collection";
+import { isCipher } from "../database/crypto";
export class Attachments implements ICollection {
name = "attachments";
key: Cipher<"base64"> | null = null;
- readonly collection: CachedCollection<"attachments", Attachment>;
+ readonly collection: SQLCollection<"attachments", Attachment>;
constructor(private readonly db: Database) {
- this.collection = new CachedCollection(
- db.storage,
- "attachments",
- db.eventManager
- );
+ this.collection = new SQLCollection(db.sql, "attachments", db.eventManager);
this.key = null;
EV.subscribe(
@@ -60,15 +57,15 @@ export class Attachments implements ICollection {
eventData: Record;
}) => {
if (!success || !eventData || !eventData.readOnDownload) return;
- const attachment = this.attachment(filename);
- if (!attachment || !attachment.metadata) return;
+ const attachment = await this.attachment(filename);
+ if (!attachment) return;
const src = await this.read(filename, getOutputType(attachment));
if (!src) return;
EV.publish(EVENTS.mediaAttachmentDownloaded, {
groupId,
- hash: attachment.metadata.hash,
+ hash: attachment.hash,
attachmentType: getAttachmentType(attachment),
src
});
@@ -86,7 +83,7 @@ export class Attachments implements ICollection {
filename: string;
error: string;
}) => {
- const attachment = this.attachment(filename);
+ const attachment = await this.attachment(filename);
if (!attachment) return;
if (success) await this.markAsUploaded(attachment.id);
else
@@ -104,54 +101,50 @@ export class Attachments implements ICollection {
async add(
item: Partial<
- Omit & {
+ Omit & {
key: SerializedKey;
}
- > & {
- metadata: Partial & { hash: string };
- }
+ >
) {
if (!item) return console.error("attachment cannot be undefined");
- if (!item.metadata.hash) throw new Error("Please provide attachment hash.");
+ if (!item.hash) throw new Error("Please provide attachment hash.");
- const oldAttachment = this.all.find(
- (a) => a.metadata.hash === item.metadata?.hash
- );
+ const oldAttachment = await this.attachment(item.hash);
const id = oldAttachment?.id || getId();
const encryptedKey = item.key
- ? await this.encryptKey(item.key)
- : oldAttachment?.key;
+ ? JSON.stringify(await this.encryptKey(item.key))
+ : oldAttachment?.encryptionKey;
const attachment = {
...oldAttachment,
- ...oldAttachment?.metadata,
...item,
- key: encryptedKey
+ encryptionKey: encryptedKey
};
const {
iv,
- length,
+ size,
alg,
hash,
hashType,
filename,
+ mimeType,
salt,
- type,
chunkSize,
- key
+ encryptionKey
} = attachment;
if (
!iv ||
- !length ||
+ !size ||
!alg ||
!hash ||
!hashType ||
- !filename ||
+ // !filename ||
+ // !mimeType ||
!salt ||
!chunkSize ||
- !key
+ !encryptionKey
) {
console.error(
"Attachment is invalid because all properties are required:",
@@ -161,27 +154,33 @@ export class Attachments implements ICollection {
return;
}
- return this.collection.add({
+ await this.collection.upsert({
type: "attachment",
id,
iv,
salt,
- length,
+ size,
alg,
- key,
+ encryptionKey,
chunkSize,
- metadata: {
- hash,
- hashType,
- filename: getFileNameWithExtension(filename, type),
- type: type || "application/octet-stream"
- },
+
+ filename:
+ filename ||
+ getFileNameWithExtension(
+ filename || hash,
+ mimeType || "application/octet-stream"
+ ),
+ hash,
+ hashType,
+ mimeType: mimeType || "application/octet-stream",
+
dateCreated: attachment.dateCreated || Date.now(),
dateModified: attachment.dateModified || Date.now(),
dateUploaded: attachment.dateUploaded,
- dateDeleted: undefined,
failed: attachment.failed
});
+
+ return id;
}
async generateKey() {
@@ -189,7 +188,9 @@ export class Attachments implements ICollection {
return await this.db.crypto().generateRandomKey();
}
- async decryptKey(key: Cipher<"base64">): Promise {
+ async decryptKey(keyJSON: string): Promise {
+ const key = JSON.parse(keyJSON);
+ if (!isCipher(key)) return null;
const encryptionKey = await this._getEncryptionKey();
const plainData = await this.db.storage().decrypt(encryptionKey, key);
if (!plainData) return null;
@@ -197,8 +198,8 @@ export class Attachments implements ICollection {
}
async remove(hashOrId: string, localOnly: boolean) {
- const attachment = this.attachment(hashOrId);
- if (!attachment || !attachment.metadata) return false;
+ const attachment = await this.attachment(hashOrId);
+ if (!attachment) return false;
if (!localOnly && !(await this.canDetach(attachment)))
throw new Error("This attachment is inside a locked note.");
@@ -206,116 +207,115 @@ export class Attachments implements ICollection {
if (
await this.db
.fs()
- .deleteFile(
- attachment.metadata.hash,
- localOnly || !attachment.dateUploaded
- )
+ .deleteFile(attachment.hash, localOnly || !attachment.dateUploaded)
) {
if (!localOnly) {
await this.detach(attachment);
}
- await this.collection.remove(attachment.id);
+ await this.collection.softDelete([attachment.id]);
return true;
}
return false;
}
async detach(attachment: Attachment) {
- for (const note of this.db.relations.from(attachment, "note").resolved()) {
+ for (const note of await this.db.relations
+ .from(attachment, "note")
+ .resolve()) {
if (!note || !note.contentId) continue;
await this.db.content.removeAttachments(note.contentId, [
- attachment.metadata.hash
+ attachment.hash
]);
}
}
private async canDetach(attachment: Attachment) {
- return this.db.relations
- .from(attachment, "note")
- .resolved()
- .every((note) => !note.locked);
+ return (await this.db.relations.from(attachment, "note").resolve()).every(
+ (note) => !note.locked
+ );
}
- ofNote(
+ async ofNote(
noteId: string,
...types: ("files" | "images" | "webclips" | "all")[]
- ): Attachment[] {
- const noteAttachments = this.db.relations
+ ): Promise {
+ const noteAttachments = await this.db.relations
.from({ type: "note", id: noteId }, "attachment")
- .resolved();
+ .resolve();
if (types.includes("all")) return noteAttachments;
return noteAttachments.filter((a) => {
- if (isImage(a.metadata.type) && types.includes("images")) return true;
- else if (isWebClip(a.metadata.type) && types.includes("webclips"))
- return true;
+ if (isImage(a.mimeType) && types.includes("images")) return true;
+ else if (isWebClip(a.mimeType) && types.includes("webclips")) return true;
else if (types.includes("files")) return true;
});
}
- exists(hash: string) {
- const attachment = this.all.find((a) => a.metadata.hash === hash);
- return !!attachment;
+ async exists(hash: string) {
+ return !!(await this.attachment(hash));
}
async read(
hash: string,
outputType: TOutputFormat
): Promise