mirror of
https://gitlab.com/soapbox-pub/ditto.git
synced 2025-12-06 11:29:46 +00:00
Remove SQLite support
This commit is contained in:
parent
f76d0af16d
commit
dc8d09a9da
28 changed files with 156 additions and 568 deletions
|
|
@ -42,4 +42,3 @@ postgres:
|
||||||
DITTO_NSEC: nsec1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs4rm7hz
|
DITTO_NSEC: nsec1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs4rm7hz
|
||||||
DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres
|
DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres
|
||||||
POSTGRES_HOST_AUTH_METHOD: trust
|
POSTGRES_HOST_AUTH_METHOD: trust
|
||||||
ALLOW_TO_USE_DATABASE_URL: true
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
"@/": "./src/",
|
"@/": "./src/",
|
||||||
"@b-fuze/deno-dom": "jsr:@b-fuze/deno-dom@^0.1.47",
|
"@b-fuze/deno-dom": "jsr:@b-fuze/deno-dom@^0.1.47",
|
||||||
"@bradenmacdonald/s3-lite-client": "jsr:@bradenmacdonald/s3-lite-client@^0.7.4",
|
"@bradenmacdonald/s3-lite-client": "jsr:@bradenmacdonald/s3-lite-client@^0.7.4",
|
||||||
"@db/sqlite": "jsr:@db/sqlite@^0.11.1",
|
|
||||||
"@electric-sql/pglite": "npm:@soapbox.pub/pglite@^0.2.10",
|
"@electric-sql/pglite": "npm:@soapbox.pub/pglite@^0.2.10",
|
||||||
"@hono/hono": "jsr:@hono/hono@^4.4.6",
|
"@hono/hono": "jsr:@hono/hono@^4.4.6",
|
||||||
"@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1",
|
"@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1",
|
||||||
|
|
@ -37,7 +36,6 @@
|
||||||
"@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.30.1",
|
"@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.30.1",
|
||||||
"@scure/base": "npm:@scure/base@^1.1.6",
|
"@scure/base": "npm:@scure/base@^1.1.6",
|
||||||
"@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs",
|
"@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs",
|
||||||
"@soapbox/kysely-deno-sqlite": "jsr:@soapbox/kysely-deno-sqlite@^2.1.0",
|
|
||||||
"@soapbox/kysely-pglite": "jsr:@soapbox/kysely-pglite@^0.0.1",
|
"@soapbox/kysely-pglite": "jsr:@soapbox/kysely-pglite@^0.0.1",
|
||||||
"@soapbox/stickynotes": "jsr:@soapbox/stickynotes@^0.4.0",
|
"@soapbox/stickynotes": "jsr:@soapbox/stickynotes@^0.4.0",
|
||||||
"@std/assert": "jsr:@std/assert@^0.225.1",
|
"@std/assert": "jsr:@std/assert@^0.225.1",
|
||||||
|
|
|
||||||
65
deno.lock
generated
65
deno.lock
generated
|
|
@ -4,8 +4,6 @@
|
||||||
"specifiers": {
|
"specifiers": {
|
||||||
"jsr:@b-fuze/deno-dom@^0.1.47": "jsr:@b-fuze/deno-dom@0.1.48",
|
"jsr:@b-fuze/deno-dom@^0.1.47": "jsr:@b-fuze/deno-dom@0.1.48",
|
||||||
"jsr:@bradenmacdonald/s3-lite-client@^0.7.4": "jsr:@bradenmacdonald/s3-lite-client@0.7.6",
|
"jsr:@bradenmacdonald/s3-lite-client@^0.7.4": "jsr:@bradenmacdonald/s3-lite-client@0.7.6",
|
||||||
"jsr:@db/sqlite@^0.11.1": "jsr:@db/sqlite@0.11.1",
|
|
||||||
"jsr:@denosaurs/plug@1": "jsr:@denosaurs/plug@1.0.6",
|
|
||||||
"jsr:@denosaurs/plug@1.0.3": "jsr:@denosaurs/plug@1.0.3",
|
"jsr:@denosaurs/plug@1.0.3": "jsr:@denosaurs/plug@1.0.3",
|
||||||
"jsr:@gleasonator/policy": "jsr:@gleasonator/policy@0.2.0",
|
"jsr:@gleasonator/policy": "jsr:@gleasonator/policy@0.2.0",
|
||||||
"jsr:@gleasonator/policy@0.2.0": "jsr:@gleasonator/policy@0.2.0",
|
"jsr:@gleasonator/policy@0.2.0": "jsr:@gleasonator/policy@0.2.0",
|
||||||
|
|
@ -25,12 +23,9 @@
|
||||||
"jsr:@nostrify/policies@^0.33.0": "jsr:@nostrify/policies@0.33.0",
|
"jsr:@nostrify/policies@^0.33.0": "jsr:@nostrify/policies@0.33.0",
|
||||||
"jsr:@nostrify/types@^0.30.0": "jsr:@nostrify/types@0.30.1",
|
"jsr:@nostrify/types@^0.30.0": "jsr:@nostrify/types@0.30.1",
|
||||||
"jsr:@nostrify/types@^0.30.1": "jsr:@nostrify/types@0.30.1",
|
"jsr:@nostrify/types@^0.30.1": "jsr:@nostrify/types@0.30.1",
|
||||||
"jsr:@soapbox/kysely-deno-sqlite@^2.1.0": "jsr:@soapbox/kysely-deno-sqlite@2.2.0",
|
|
||||||
"jsr:@soapbox/kysely-pglite@^0.0.1": "jsr:@soapbox/kysely-pglite@0.0.1",
|
"jsr:@soapbox/kysely-pglite@^0.0.1": "jsr:@soapbox/kysely-pglite@0.0.1",
|
||||||
"jsr:@soapbox/stickynotes@^0.4.0": "jsr:@soapbox/stickynotes@0.4.0",
|
"jsr:@soapbox/stickynotes@^0.4.0": "jsr:@soapbox/stickynotes@0.4.0",
|
||||||
"jsr:@std/assert@^0.213.1": "jsr:@std/assert@0.213.1",
|
"jsr:@std/assert@^0.213.1": "jsr:@std/assert@0.213.1",
|
||||||
"jsr:@std/assert@^0.217.0": "jsr:@std/assert@0.217.0",
|
|
||||||
"jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0",
|
|
||||||
"jsr:@std/assert@^0.224.0": "jsr:@std/assert@0.224.0",
|
"jsr:@std/assert@^0.224.0": "jsr:@std/assert@0.224.0",
|
||||||
"jsr:@std/assert@^0.225.1": "jsr:@std/assert@0.225.3",
|
"jsr:@std/assert@^0.225.1": "jsr:@std/assert@0.225.3",
|
||||||
"jsr:@std/bytes@^0.224.0": "jsr:@std/bytes@0.224.0",
|
"jsr:@std/bytes@^0.224.0": "jsr:@std/bytes@0.224.0",
|
||||||
|
|
@ -41,22 +36,17 @@
|
||||||
"jsr:@std/crypto@^0.224.0": "jsr:@std/crypto@0.224.0",
|
"jsr:@std/crypto@^0.224.0": "jsr:@std/crypto@0.224.0",
|
||||||
"jsr:@std/dotenv@^0.224.0": "jsr:@std/dotenv@0.224.2",
|
"jsr:@std/dotenv@^0.224.0": "jsr:@std/dotenv@0.224.2",
|
||||||
"jsr:@std/encoding@0.213.1": "jsr:@std/encoding@0.213.1",
|
"jsr:@std/encoding@0.213.1": "jsr:@std/encoding@0.213.1",
|
||||||
"jsr:@std/encoding@^0.221.0": "jsr:@std/encoding@0.221.0",
|
|
||||||
"jsr:@std/encoding@^0.224.0": "jsr:@std/encoding@0.224.3",
|
"jsr:@std/encoding@^0.224.0": "jsr:@std/encoding@0.224.3",
|
||||||
"jsr:@std/encoding@^0.224.1": "jsr:@std/encoding@0.224.3",
|
"jsr:@std/encoding@^0.224.1": "jsr:@std/encoding@0.224.3",
|
||||||
"jsr:@std/fmt@0.213.1": "jsr:@std/fmt@0.213.1",
|
"jsr:@std/fmt@0.213.1": "jsr:@std/fmt@0.213.1",
|
||||||
"jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0",
|
|
||||||
"jsr:@std/fs@0.213.1": "jsr:@std/fs@0.213.1",
|
"jsr:@std/fs@0.213.1": "jsr:@std/fs@0.213.1",
|
||||||
"jsr:@std/fs@^0.221.0": "jsr:@std/fs@0.221.0",
|
|
||||||
"jsr:@std/fs@^0.229.3": "jsr:@std/fs@0.229.3",
|
"jsr:@std/fs@^0.229.3": "jsr:@std/fs@0.229.3",
|
||||||
"jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.1",
|
"jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.1",
|
||||||
"jsr:@std/io@^0.224": "jsr:@std/io@0.224.7",
|
"jsr:@std/io@^0.224": "jsr:@std/io@0.224.7",
|
||||||
"jsr:@std/json@^0.223.0": "jsr:@std/json@0.223.0",
|
"jsr:@std/json@^0.223.0": "jsr:@std/json@0.223.0",
|
||||||
"jsr:@std/media-types@^0.224.1": "jsr:@std/media-types@0.224.1",
|
"jsr:@std/media-types@^0.224.1": "jsr:@std/media-types@0.224.1",
|
||||||
"jsr:@std/path@0.213.1": "jsr:@std/path@0.213.1",
|
"jsr:@std/path@0.213.1": "jsr:@std/path@0.213.1",
|
||||||
"jsr:@std/path@0.217": "jsr:@std/path@0.217.0",
|
|
||||||
"jsr:@std/path@^0.213.1": "jsr:@std/path@0.213.1",
|
"jsr:@std/path@^0.213.1": "jsr:@std/path@0.213.1",
|
||||||
"jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0",
|
|
||||||
"jsr:@std/streams@^0.223.0": "jsr:@std/streams@0.223.0",
|
"jsr:@std/streams@^0.223.0": "jsr:@std/streams@0.223.0",
|
||||||
"npm:@isaacs/ttlcache@^1.4.1": "npm:@isaacs/ttlcache@1.4.1",
|
"npm:@isaacs/ttlcache@^1.4.1": "npm:@isaacs/ttlcache@1.4.1",
|
||||||
"npm:@noble/hashes@^1.4.0": "npm:@noble/hashes@1.4.0",
|
"npm:@noble/hashes@^1.4.0": "npm:@noble/hashes@1.4.0",
|
||||||
|
|
@ -116,13 +106,6 @@
|
||||||
"jsr:@std/io@^0.224"
|
"jsr:@std/io@^0.224"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@db/sqlite@0.11.1": {
|
|
||||||
"integrity": "546434e7ed762db07e6ade0f963540dd5e06723b802937bf260ff855b21ef9c5",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@denosaurs/plug@1",
|
|
||||||
"jsr:@std/path@0.217"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@denosaurs/plug@1.0.3": {
|
"@denosaurs/plug@1.0.3": {
|
||||||
"integrity": "b010544e386bea0ff3a1d05e0c88f704ea28cbd4d753439c2f1ee021a85d4640",
|
"integrity": "b010544e386bea0ff3a1d05e0c88f704ea28cbd4d753439c2f1ee021a85d4640",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -132,15 +115,6 @@
|
||||||
"jsr:@std/path@0.213.1"
|
"jsr:@std/path@0.213.1"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@denosaurs/plug@1.0.6": {
|
|
||||||
"integrity": "6cf5b9daba7799837b9ffbe89f3450510f588fafef8115ddab1ff0be9cb7c1a7",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/encoding@^0.221.0",
|
|
||||||
"jsr:@std/fmt@^0.221.0",
|
|
||||||
"jsr:@std/fs@^0.221.0",
|
|
||||||
"jsr:@std/path@^0.221.0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@gleasonator/policy@0.2.0": {
|
"@gleasonator/policy@0.2.0": {
|
||||||
"integrity": "3fe58b853ab203b2b67e65b64391dbcf5c07bc1caaf46e97b2f8ed5b14f30fdf",
|
"integrity": "3fe58b853ab203b2b67e65b64391dbcf5c07bc1caaf46e97b2f8ed5b14f30fdf",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -293,12 +267,6 @@
|
||||||
"@nostrify/types@0.30.1": {
|
"@nostrify/types@0.30.1": {
|
||||||
"integrity": "245da176f6893a43250697db51ad32bfa29bf9b1cdc1ca218043d9abf6de5ae5"
|
"integrity": "245da176f6893a43250697db51ad32bfa29bf9b1cdc1ca218043d9abf6de5ae5"
|
||||||
},
|
},
|
||||||
"@soapbox/kysely-deno-sqlite@2.2.0": {
|
|
||||||
"integrity": "668ec94600bc4b4d7bd618dd7ca65d4ef30ee61c46ffcb379b6f45203c08517a",
|
|
||||||
"dependencies": [
|
|
||||||
"npm:kysely@^0.27.2"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@soapbox/kysely-pglite@0.0.1": {
|
"@soapbox/kysely-pglite@0.0.1": {
|
||||||
"integrity": "7a4221aa780aad6fba9747c45c59dfb1c62017ba8cad9db5607f6e5822c058d5",
|
"integrity": "7a4221aa780aad6fba9747c45c59dfb1c62017ba8cad9db5607f6e5822c058d5",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -311,12 +279,6 @@
|
||||||
"@std/assert@0.213.1": {
|
"@std/assert@0.213.1": {
|
||||||
"integrity": "24c28178b30c8e0782c18e8e94ea72b16282207569cdd10ffb9d1d26f2edebfe"
|
"integrity": "24c28178b30c8e0782c18e8e94ea72b16282207569cdd10ffb9d1d26f2edebfe"
|
||||||
},
|
},
|
||||||
"@std/assert@0.217.0": {
|
|
||||||
"integrity": "c98e279362ca6982d5285c3b89517b757c1e3477ee9f14eb2fdf80a45aaa9642"
|
|
||||||
},
|
|
||||||
"@std/assert@0.221.0": {
|
|
||||||
"integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a"
|
|
||||||
},
|
|
||||||
"@std/assert@0.224.0": {
|
"@std/assert@0.224.0": {
|
||||||
"integrity": "8643233ec7aec38a940a8264a6e3eed9bfa44e7a71cc6b3c8874213ff401967f"
|
"integrity": "8643233ec7aec38a940a8264a6e3eed9bfa44e7a71cc6b3c8874213ff401967f"
|
||||||
},
|
},
|
||||||
|
|
@ -351,18 +313,12 @@
|
||||||
"@std/encoding@0.213.1": {
|
"@std/encoding@0.213.1": {
|
||||||
"integrity": "fcbb6928713dde941a18ca5db88ca1544d0755ec8fb20fe61e2dc8144b390c62"
|
"integrity": "fcbb6928713dde941a18ca5db88ca1544d0755ec8fb20fe61e2dc8144b390c62"
|
||||||
},
|
},
|
||||||
"@std/encoding@0.221.0": {
|
|
||||||
"integrity": "d1dd76ef0dc5d14088411e6dc1dede53bf8308c95d1537df1214c97137208e45"
|
|
||||||
},
|
|
||||||
"@std/encoding@0.224.3": {
|
"@std/encoding@0.224.3": {
|
||||||
"integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf"
|
"integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf"
|
||||||
},
|
},
|
||||||
"@std/fmt@0.213.1": {
|
"@std/fmt@0.213.1": {
|
||||||
"integrity": "a06d31777566d874b9c856c10244ac3e6b660bdec4c82506cd46be052a1082c3"
|
"integrity": "a06d31777566d874b9c856c10244ac3e6b660bdec4c82506cd46be052a1082c3"
|
||||||
},
|
},
|
||||||
"@std/fmt@0.221.0": {
|
|
||||||
"integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a"
|
|
||||||
},
|
|
||||||
"@std/fs@0.213.1": {
|
"@std/fs@0.213.1": {
|
||||||
"integrity": "fbcaf099f8a85c27ab0712b666262cda8fe6d02e9937bf9313ecaea39a22c501",
|
"integrity": "fbcaf099f8a85c27ab0712b666262cda8fe6d02e9937bf9313ecaea39a22c501",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -370,13 +326,6 @@
|
||||||
"jsr:@std/path@^0.213.1"
|
"jsr:@std/path@^0.213.1"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@std/fs@0.221.0": {
|
|
||||||
"integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@^0.221.0",
|
|
||||||
"jsr:@std/path@^0.221.0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/fs@0.229.3": {
|
"@std/fs@0.229.3": {
|
||||||
"integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb"
|
"integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb"
|
||||||
},
|
},
|
||||||
|
|
@ -434,18 +383,6 @@
|
||||||
"jsr:@std/assert@^0.213.1"
|
"jsr:@std/assert@^0.213.1"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@std/path@0.217.0": {
|
|
||||||
"integrity": "1217cc25534bca9a2f672d7fe7c6f356e4027df400c0e85c0ef3e4343bc67d11",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@^0.217.0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/path@0.221.0": {
|
|
||||||
"integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095",
|
|
||||||
"dependencies": [
|
|
||||||
"jsr:@std/assert@^0.221.0"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@std/streams@0.223.0": {
|
"@std/streams@0.223.0": {
|
||||||
"integrity": "d6b28e498ced3960b04dc5d251f2dcfc1df244b5ec5a48dc23a8f9b490be3b99"
|
"integrity": "d6b28e498ced3960b04dc5d251f2dcfc1df244b5ec5a48dc23a8f9b490be3b99"
|
||||||
}
|
}
|
||||||
|
|
@ -1972,12 +1909,10 @@
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"jsr:@b-fuze/deno-dom@^0.1.47",
|
"jsr:@b-fuze/deno-dom@^0.1.47",
|
||||||
"jsr:@bradenmacdonald/s3-lite-client@^0.7.4",
|
"jsr:@bradenmacdonald/s3-lite-client@^0.7.4",
|
||||||
"jsr:@db/sqlite@^0.11.1",
|
|
||||||
"jsr:@hono/hono@^4.4.6",
|
"jsr:@hono/hono@^4.4.6",
|
||||||
"jsr:@lambdalisue/async@^2.1.1",
|
"jsr:@lambdalisue/async@^2.1.1",
|
||||||
"jsr:@nostrify/db@^0.31.2",
|
"jsr:@nostrify/db@^0.31.2",
|
||||||
"jsr:@nostrify/nostrify@^0.30.1",
|
"jsr:@nostrify/nostrify@^0.30.1",
|
||||||
"jsr:@soapbox/kysely-deno-sqlite@^2.1.0",
|
|
||||||
"jsr:@soapbox/kysely-pglite@^0.0.1",
|
"jsr:@soapbox/kysely-pglite@^0.0.1",
|
||||||
"jsr:@soapbox/stickynotes@^0.4.0",
|
"jsr:@soapbox/stickynotes@^0.4.0",
|
||||||
"jsr:@std/assert@^0.225.1",
|
"jsr:@std/assert@^0.225.1",
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ ssh -L 9229:localhost:9229 <user>@<host>
|
||||||
|
|
||||||
Then, in Chromium, go to `chrome://inspect` and the Ditto server should be available.
|
Then, in Chromium, go to `chrome://inspect` and the Ditto server should be available.
|
||||||
|
|
||||||
## SQLite performance
|
## SQL performance
|
||||||
|
|
||||||
To track slow queries, first set `DEBUG=ditto:sqlite.worker` in the environment so only SQLite logs are shown.
|
To track slow queries, first set `DEBUG=ditto:sql` in the environment so only SQL logs are shown.
|
||||||
|
|
||||||
Then, grep for any logs above 0.001s:
|
Then, grep for any logs above 0.001s:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,10 @@ const DATABASE_URL = Deno.env.get('DATABASE_URL');
|
||||||
if (DATABASE_URL) {
|
if (DATABASE_URL) {
|
||||||
vars.DATABASE_URL = await question('input', 'Database URL', DATABASE_URL);
|
vars.DATABASE_URL = await question('input', 'Database URL', DATABASE_URL);
|
||||||
} else {
|
} else {
|
||||||
const database = await question('list', 'Which database do you want to use?', ['postgres', 'sqlite']);
|
const database = await question('list', 'Which database do you want to use?', ['postgres', 'pglite']);
|
||||||
if (database === 'sqlite') {
|
if (database === 'pglite') {
|
||||||
const path = await question('input', 'Path to SQLite database', 'data/db.sqlite3');
|
const path = await question('input', 'Path to PGlite data directory', 'data/pgdata');
|
||||||
vars.DATABASE_URL = `sqlite://${path}`;
|
vars.DATABASE_URL = `file://${path}`;
|
||||||
}
|
}
|
||||||
if (database === 'postgres') {
|
if (database === 'postgres') {
|
||||||
const host = await question('input', 'Postgres host', 'localhost');
|
const host = await question('input', 'Postgres host', 'localhost');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import url from 'node:url';
|
|
||||||
|
|
||||||
import * as dotenv from '@std/dotenv';
|
import * as dotenv from '@std/dotenv';
|
||||||
import { getPublicKey, nip19 } from 'nostr-tools';
|
import { getPublicKey, nip19 } from 'nostr-tools';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
@ -89,20 +87,6 @@ class Conf {
|
||||||
return Deno.env.get('TEST_DATABASE_URL') ?? 'memory://';
|
return Deno.env.get('TEST_DATABASE_URL') ?? 'memory://';
|
||||||
}
|
}
|
||||||
static db = {
|
static db = {
|
||||||
get url(): url.UrlWithStringQuery {
|
|
||||||
return url.parse(Conf.databaseUrl);
|
|
||||||
},
|
|
||||||
get dialect(): 'sqlite' | 'postgres' | undefined {
|
|
||||||
switch (Conf.db.url.protocol) {
|
|
||||||
case 'sqlite:':
|
|
||||||
return 'sqlite';
|
|
||||||
case 'pglite:':
|
|
||||||
case 'postgres:':
|
|
||||||
case 'postgresql:':
|
|
||||||
return 'postgres';
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
/** Database query timeout configurations. */
|
/** Database query timeout configurations. */
|
||||||
timeouts: {
|
timeouts: {
|
||||||
/** Default query timeout when another setting isn't more specific. */
|
/** Default query timeout when another setting isn't more specific. */
|
||||||
|
|
@ -221,21 +205,6 @@ class Conf {
|
||||||
static get sentryDsn(): string | undefined {
|
static get sentryDsn(): string | undefined {
|
||||||
return Deno.env.get('SENTRY_DSN');
|
return Deno.env.get('SENTRY_DSN');
|
||||||
}
|
}
|
||||||
/** SQLite settings. */
|
|
||||||
static sqlite = {
|
|
||||||
/**
|
|
||||||
* Number of bytes to use for memory-mapped IO.
|
|
||||||
* https://www.sqlite.org/pragma.html#pragma_mmap_size
|
|
||||||
*/
|
|
||||||
get mmapSize(): number {
|
|
||||||
const value = Deno.env.get('SQLITE_MMAP_SIZE');
|
|
||||||
if (value) {
|
|
||||||
return Number(value);
|
|
||||||
} else {
|
|
||||||
return 1024 * 1024 * 1024;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
/** Postgres settings. */
|
/** Postgres settings. */
|
||||||
static pg = {
|
static pg = {
|
||||||
/** Number of connections to use in the pool. */
|
/** Number of connections to use in the pool. */
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ const createTokenController: AppController = async (c) => {
|
||||||
async function getToken(
|
async function getToken(
|
||||||
{ pubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] },
|
{ pubkey, secret, relays = [] }: { pubkey: string; secret?: string; relays?: string[] },
|
||||||
): Promise<`token1${string}`> {
|
): Promise<`token1${string}`> {
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await DittoDB.getInstance();
|
||||||
const token = generateToken();
|
const token = generateToken();
|
||||||
|
|
||||||
const serverSeckey = generateSecretKey();
|
const serverSeckey = generateSecretKey();
|
||||||
|
|
|
||||||
|
|
@ -578,7 +578,7 @@ const zappedByController: AppController = async (c) => {
|
||||||
const id = c.req.param('id');
|
const id = c.req.param('id');
|
||||||
const params = c.get('listPagination');
|
const params = c.get('listPagination');
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await DittoDB.getInstance();
|
||||||
|
|
||||||
const zaps = await kysely.selectFrom('event_zaps')
|
const zaps = await kysely.selectFrom('event_zaps')
|
||||||
.selectAll()
|
.selectAll()
|
||||||
|
|
|
||||||
|
|
@ -222,7 +222,7 @@ async function topicToFilter(
|
||||||
|
|
||||||
async function getTokenPubkey(token: string): Promise<string | undefined> {
|
async function getTokenPubkey(token: string): Promise<string | undefined> {
|
||||||
if (token.startsWith('token1')) {
|
if (token.startsWith('token1')) {
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await DittoDB.getInstance();
|
||||||
|
|
||||||
const { user_pubkey } = await kysely
|
const { user_pubkey } = await kysely
|
||||||
.selectFrom('nip46_tokens')
|
.selectFrom('nip46_tokens')
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,66 @@
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import { NDatabaseSchema, NPostgresSchema } from '@nostrify/db';
|
|
||||||
import { FileMigrationProvider, Kysely, Migrator } from 'kysely';
|
import { FileMigrationProvider, Kysely, Migrator } from 'kysely';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoPglite } from '@/db/adapters/DittoPglite.ts';
|
import { DittoPglite } from '@/db/adapters/DittoPglite.ts';
|
||||||
import { DittoPostgres } from '@/db/adapters/DittoPostgres.ts';
|
import { DittoPostgres } from '@/db/adapters/DittoPostgres.ts';
|
||||||
import { DittoSQLite } from '@/db/adapters/DittoSQLite.ts';
|
|
||||||
import { DittoTables } from '@/db/DittoTables.ts';
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
|
|
||||||
export type DittoDatabase = {
|
|
||||||
dialect: 'sqlite';
|
|
||||||
kysely: Kysely<DittoTables> & Kysely<NDatabaseSchema>;
|
|
||||||
} | {
|
|
||||||
dialect: 'postgres';
|
|
||||||
kysely: Kysely<DittoTables> & Kysely<NPostgresSchema>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class DittoDB {
|
export class DittoDB {
|
||||||
private static db: Promise<DittoDatabase> | undefined;
|
private static kysely: Promise<Kysely<DittoTables>> | undefined;
|
||||||
|
|
||||||
static getInstance(): Promise<DittoDatabase> {
|
static getInstance(): Promise<Kysely<DittoTables>> {
|
||||||
if (!this.db) {
|
if (!this.kysely) {
|
||||||
this.db = this._getInstance();
|
this.kysely = this._getInstance();
|
||||||
}
|
}
|
||||||
return this.db;
|
return this.kysely;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async _getInstance(): Promise<DittoDatabase> {
|
static async _getInstance(): Promise<Kysely<DittoTables>> {
|
||||||
const result = {} as DittoDatabase;
|
const { protocol } = new URL(Conf.databaseUrl);
|
||||||
|
|
||||||
switch (Conf.db.url.protocol) {
|
let kysely: Kysely<DittoTables>;
|
||||||
case 'sqlite:':
|
|
||||||
result.dialect = 'sqlite';
|
switch (protocol) {
|
||||||
result.kysely = await DittoSQLite.getInstance();
|
case 'file:':
|
||||||
break;
|
case 'memory:':
|
||||||
case 'pglite:':
|
kysely = await DittoPglite.getInstance();
|
||||||
result.dialect = 'postgres';
|
|
||||||
result.kysely = await DittoPglite.getInstance();
|
|
||||||
break;
|
break;
|
||||||
case 'postgres:':
|
case 'postgres:':
|
||||||
case 'postgresql:':
|
case 'postgresql:':
|
||||||
result.dialect = 'postgres';
|
kysely = await DittoPostgres.getInstance();
|
||||||
result.kysely = await DittoPostgres.getInstance();
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error('Unsupported database URL.');
|
throw new Error('Unsupported database URL.');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.migrate(result.kysely);
|
await this.migrate(kysely);
|
||||||
|
|
||||||
return result;
|
return kysely;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get poolSize(): number {
|
static get poolSize(): number {
|
||||||
if (Conf.db.dialect === 'postgres') {
|
const { protocol } = new URL(Conf.databaseUrl);
|
||||||
|
|
||||||
|
if (['postgres:', 'postgresql:'].includes(protocol)) {
|
||||||
return DittoPostgres.poolSize;
|
return DittoPostgres.poolSize;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get availableConnections(): number {
|
static get availableConnections(): number {
|
||||||
if (Conf.db.dialect === 'postgres') {
|
const { protocol } = new URL(Conf.databaseUrl);
|
||||||
|
|
||||||
|
if (['postgres:', 'postgresql:'].includes(protocol)) {
|
||||||
return DittoPostgres.availableConnections;
|
return DittoPostgres.availableConnections;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Migrate the database to the latest version. */
|
/** Migrate the database to the latest version. */
|
||||||
static async migrate(kysely: DittoDatabase['kysely']) {
|
static async migrate(kysely: Kysely<DittoTables>) {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
db: kysely,
|
db: kysely,
|
||||||
provider: new FileMigrationProvider({
|
provider: new FileMigrationProvider({
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
export interface DittoTables {
|
import { NPostgresSchema } from '@nostrify/db';
|
||||||
|
|
||||||
|
export interface DittoTables extends NPostgresSchema {
|
||||||
nip46_tokens: NIP46TokenRow;
|
nip46_tokens: NIP46TokenRow;
|
||||||
author_stats: AuthorStatsRow;
|
author_stats: AuthorStatsRow;
|
||||||
event_stats: EventStatsRow;
|
event_stats: EventStatsRow;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { PGlite } from '@electric-sql/pglite';
|
import { PGlite } from '@electric-sql/pglite';
|
||||||
import { NPostgresSchema } from '@nostrify/db';
|
|
||||||
import { PgliteDialect } from '@soapbox/kysely-pglite';
|
import { PgliteDialect } from '@soapbox/kysely-pglite';
|
||||||
import { Kysely } from 'kysely';
|
import { Kysely } from 'kysely';
|
||||||
|
|
||||||
|
|
@ -8,17 +7,17 @@ import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { KyselyLogger } from '@/db/KyselyLogger.ts';
|
import { KyselyLogger } from '@/db/KyselyLogger.ts';
|
||||||
|
|
||||||
export class DittoPglite {
|
export class DittoPglite {
|
||||||
static db: Kysely<DittoTables> & Kysely<NPostgresSchema> | undefined;
|
static db: Kysely<DittoTables> | undefined;
|
||||||
|
|
||||||
// deno-lint-ignore require-await
|
// deno-lint-ignore require-await
|
||||||
static async getInstance(): Promise<Kysely<DittoTables> & Kysely<NPostgresSchema>> {
|
static async getInstance(): Promise<Kysely<DittoTables>> {
|
||||||
if (!this.db) {
|
if (!this.db) {
|
||||||
this.db = new Kysely({
|
this.db = new Kysely<DittoTables>({
|
||||||
dialect: new PgliteDialect({
|
dialect: new PgliteDialect({
|
||||||
database: new PGlite(this.path),
|
database: new PGlite(Conf.databaseUrl),
|
||||||
}),
|
}),
|
||||||
log: KyselyLogger,
|
log: KyselyLogger,
|
||||||
}) as Kysely<DittoTables> & Kysely<NPostgresSchema>;
|
}) as Kysely<DittoTables>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.db;
|
return this.db;
|
||||||
|
|
@ -31,26 +30,4 @@ export class DittoPglite {
|
||||||
static get availableConnections(): number {
|
static get availableConnections(): number {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the relative or absolute path based on the `DATABASE_URL`. */
|
|
||||||
static get path(): string | undefined {
|
|
||||||
if (Conf.databaseUrl === 'pglite://:memory:') {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { host, pathname } = Conf.db.url;
|
|
||||||
|
|
||||||
if (!pathname) return '';
|
|
||||||
|
|
||||||
// Get relative path.
|
|
||||||
if (host === '') {
|
|
||||||
return pathname;
|
|
||||||
} else if (host === '.') {
|
|
||||||
return pathname;
|
|
||||||
} else if (host) {
|
|
||||||
return host + pathname;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { NPostgresSchema } from '@nostrify/db';
|
|
||||||
import {
|
import {
|
||||||
BinaryOperationNode,
|
BinaryOperationNode,
|
||||||
FunctionNode,
|
FunctionNode,
|
||||||
|
|
@ -18,17 +17,17 @@ import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { KyselyLogger } from '@/db/KyselyLogger.ts';
|
import { KyselyLogger } from '@/db/KyselyLogger.ts';
|
||||||
|
|
||||||
export class DittoPostgres {
|
export class DittoPostgres {
|
||||||
static db: Kysely<DittoTables> & Kysely<NPostgresSchema> | undefined;
|
static kysely: Kysely<DittoTables> | undefined;
|
||||||
static postgres?: postgres.Sql;
|
static postgres?: postgres.Sql;
|
||||||
|
|
||||||
// deno-lint-ignore require-await
|
// deno-lint-ignore require-await
|
||||||
static async getInstance(): Promise<Kysely<DittoTables> & Kysely<NPostgresSchema>> {
|
static async getInstance(): Promise<Kysely<DittoTables>> {
|
||||||
if (!this.postgres) {
|
if (!this.postgres) {
|
||||||
this.postgres = postgres(Conf.databaseUrl, { max: Conf.pg.poolSize });
|
this.postgres = postgres(Conf.databaseUrl, { max: Conf.pg.poolSize });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.db) {
|
if (!this.kysely) {
|
||||||
this.db = new Kysely({
|
this.kysely = new Kysely<DittoTables>({
|
||||||
dialect: {
|
dialect: {
|
||||||
createAdapter() {
|
createAdapter() {
|
||||||
return new PostgresAdapter();
|
return new PostgresAdapter();
|
||||||
|
|
@ -46,10 +45,10 @@ export class DittoPostgres {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
log: KyselyLogger,
|
log: KyselyLogger,
|
||||||
}) as Kysely<DittoTables> & Kysely<NPostgresSchema>;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.db;
|
return this.kysely;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get poolSize() {
|
static get poolSize() {
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import { NDatabaseSchema } from '@nostrify/db';
|
|
||||||
import { PolySqliteDialect } from '@soapbox/kysely-deno-sqlite';
|
|
||||||
import { Kysely, sql } from 'kysely';
|
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
import { DittoTables } from '@/db/DittoTables.ts';
|
|
||||||
import { KyselyLogger } from '@/db/KyselyLogger.ts';
|
|
||||||
import SqliteWorker from '@/workers/sqlite.ts';
|
|
||||||
|
|
||||||
export class DittoSQLite {
|
|
||||||
static db: Kysely<DittoTables> & Kysely<NDatabaseSchema> | undefined;
|
|
||||||
|
|
||||||
static async getInstance(): Promise<Kysely<DittoTables> & Kysely<NDatabaseSchema>> {
|
|
||||||
if (!this.db) {
|
|
||||||
const sqliteWorker = new SqliteWorker();
|
|
||||||
await sqliteWorker.open(this.path);
|
|
||||||
|
|
||||||
this.db = new Kysely({
|
|
||||||
dialect: new PolySqliteDialect({
|
|
||||||
database: sqliteWorker,
|
|
||||||
}),
|
|
||||||
log: KyselyLogger,
|
|
||||||
}) as Kysely<DittoTables> & Kysely<NDatabaseSchema>;
|
|
||||||
|
|
||||||
// Set PRAGMA values.
|
|
||||||
await Promise.all([
|
|
||||||
sql`PRAGMA synchronous = normal`.execute(this.db),
|
|
||||||
sql`PRAGMA temp_store = memory`.execute(this.db),
|
|
||||||
sql`PRAGMA foreign_keys = ON`.execute(this.db),
|
|
||||||
sql`PRAGMA auto_vacuum = FULL`.execute(this.db),
|
|
||||||
sql`PRAGMA journal_mode = WAL`.execute(this.db),
|
|
||||||
sql.raw(`PRAGMA mmap_size = ${Conf.sqlite.mmapSize}`).execute(this.db),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return this.db;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the relative or absolute path based on the `DATABASE_URL`. */
|
|
||||||
static get path() {
|
|
||||||
if (Conf.databaseUrl === 'sqlite://:memory:') {
|
|
||||||
return ':memory:';
|
|
||||||
}
|
|
||||||
|
|
||||||
const { host, pathname } = Conf.db.url;
|
|
||||||
|
|
||||||
if (!pathname) return '';
|
|
||||||
|
|
||||||
// Get relative path.
|
|
||||||
if (host === '') {
|
|
||||||
return pathname;
|
|
||||||
} else if (host === '.') {
|
|
||||||
return pathname;
|
|
||||||
} else if (host) {
|
|
||||||
return host + pathname;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
import { Kysely, sql } from 'kysely';
|
import { Kysely } from 'kysely';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
export async function up(_db: Kysely<any>): Promise<void> {
|
||||||
|
// This migration used to create an FTS table for SQLite, but SQLite support was removed.
|
||||||
export async function up(db: Kysely<any>): Promise<void> {
|
|
||||||
if (Conf.db.dialect === 'sqlite') {
|
|
||||||
await sql`CREATE VIRTUAL TABLE events_fts USING fts5(id, content)`.execute(db);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(db: Kysely<any>): Promise<void> {
|
export async function down(_db: Kysely<any>): Promise<void> {
|
||||||
await db.schema.dropTable('events_fts').ifExists().execute();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,13 @@
|
||||||
import { Kysely, sql } from 'kysely';
|
import { Kysely } from 'kysely';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
|
|
||||||
export async function up(db: Kysely<any>): Promise<void> {
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
await db.schema.alterTable('events').renameTo('nostr_events').execute();
|
await db.schema.alterTable('events').renameTo('nostr_events').execute();
|
||||||
await db.schema.alterTable('tags').renameTo('nostr_tags').execute();
|
await db.schema.alterTable('tags').renameTo('nostr_tags').execute();
|
||||||
await db.schema.alterTable('nostr_tags').renameColumn('tag', 'name').execute();
|
await db.schema.alterTable('nostr_tags').renameColumn('tag', 'name').execute();
|
||||||
|
|
||||||
if (Conf.db.dialect === 'sqlite') {
|
|
||||||
await db.schema.dropTable('events_fts').execute();
|
|
||||||
await sql`CREATE VIRTUAL TABLE nostr_fts5 USING fts5(event_id, content)`.execute(db);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(db: Kysely<any>): Promise<void> {
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
await db.schema.alterTable('nostr_events').renameTo('events').execute();
|
await db.schema.alterTable('nostr_events').renameTo('events').execute();
|
||||||
await db.schema.alterTable('nostr_tags').renameTo('tags').execute();
|
await db.schema.alterTable('nostr_tags').renameTo('tags').execute();
|
||||||
await db.schema.alterTable('tags').renameColumn('name', 'tag').execute();
|
await db.schema.alterTable('tags').renameColumn('name', 'tag').execute();
|
||||||
|
|
||||||
if (Conf.db.dialect === 'sqlite') {
|
|
||||||
await db.schema.dropTable('nostr_fts5').execute();
|
|
||||||
await sql`CREATE VIRTUAL TABLE events_fts USING fts5(id, content)`.execute(db);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export const signerMiddleware: AppMiddleware = async (c, next) => {
|
||||||
|
|
||||||
if (bech32.startsWith('token1')) {
|
if (bech32.startsWith('token1')) {
|
||||||
try {
|
try {
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await DittoDB.getInstance();
|
||||||
|
|
||||||
const { user_pubkey, server_seckey, relays } = await kysely
|
const { user_pubkey, server_seckey, relays } = await kysely
|
||||||
.selectFrom('nip46_tokens')
|
.selectFrom('nip46_tokens')
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise<void
|
||||||
throw new RelayError('blocked', 'user is disabled');
|
throw new RelayError('blocked', 'user is disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await DittoDB.getInstance();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
storeEvent(event, signal),
|
storeEvent(event, signal),
|
||||||
|
|
@ -104,7 +104,7 @@ async function existsInDB(event: DittoEvent): Promise<boolean> {
|
||||||
async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<void> {
|
async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<void> {
|
||||||
await hydrateEvents({ events: [event], store: await Storages.db(), signal });
|
await hydrateEvents({ events: [event], store: await Storages.db(), signal });
|
||||||
|
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await DittoDB.getInstance();
|
||||||
const domain = await kysely
|
const domain = await kysely
|
||||||
.selectFrom('pubkey_domains')
|
.selectFrom('pubkey_domains')
|
||||||
.select('domain')
|
.select('domain')
|
||||||
|
|
@ -118,7 +118,7 @@ async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise<voi
|
||||||
async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise<undefined> {
|
async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise<undefined> {
|
||||||
if (NKinds.ephemeral(event.kind)) return;
|
if (NKinds.ephemeral(event.kind)) return;
|
||||||
const store = await Storages.db();
|
const store = await Storages.db();
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await DittoDB.getInstance();
|
||||||
|
|
||||||
await updateStats({ event, store, kysely }).catch(debug);
|
await updateStats({ event, store, kysely }).catch(debug);
|
||||||
await store.event(event, { signal });
|
await store.event(event, { signal });
|
||||||
|
|
@ -146,7 +146,7 @@ async function parseMetadata(event: NostrEvent, signal: AbortSignal): Promise<vo
|
||||||
|
|
||||||
// Track pubkey domain.
|
// Track pubkey domain.
|
||||||
try {
|
try {
|
||||||
const { kysely } = await DittoDB.getInstance();
|
const kysely = await DittoDB.getInstance();
|
||||||
const { domain } = parseNip05(nip05);
|
const { domain } = parseNip05(nip05);
|
||||||
|
|
||||||
await sql`
|
await sql`
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export class Storages {
|
||||||
private static _pubsub: Promise<InternalRelay> | undefined;
|
private static _pubsub: Promise<InternalRelay> | undefined;
|
||||||
private static _search: Promise<SearchStore> | undefined;
|
private static _search: Promise<SearchStore> | undefined;
|
||||||
|
|
||||||
/** SQLite database to store events this Ditto server cares about. */
|
/** SQL database to store events this Ditto server cares about. */
|
||||||
public static async db(): Promise<EventsDB> {
|
public static async db(): Promise<EventsDB> {
|
||||||
if (!this._db) {
|
if (!this._db) {
|
||||||
this._db = (async () => {
|
this._db = (async () => {
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,11 @@ import {
|
||||||
NStore,
|
NStore,
|
||||||
} from '@nostrify/nostrify';
|
} from '@nostrify/nostrify';
|
||||||
import { Stickynotes } from '@soapbox/stickynotes';
|
import { Stickynotes } from '@soapbox/stickynotes';
|
||||||
|
import { Kysely } from 'kysely';
|
||||||
import { nip27 } from 'nostr-tools';
|
import { nip27 } from 'nostr-tools';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoDatabase } from '@/db/DittoDB.ts';
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { dbEventsCounter } from '@/metrics.ts';
|
import { dbEventsCounter } from '@/metrics.ts';
|
||||||
import { RelayError } from '@/RelayError.ts';
|
import { RelayError } from '@/RelayError.ts';
|
||||||
import { purifyEvent } from '@/storages/hydrate.ts';
|
import { purifyEvent } from '@/storages/hydrate.ts';
|
||||||
|
|
@ -30,7 +31,7 @@ type TagCondition = ({ event, count, value }: {
|
||||||
value: string;
|
value: string;
|
||||||
}) => boolean;
|
}) => boolean;
|
||||||
|
|
||||||
/** SQLite database storage adapter for Nostr events. */
|
/** SQL database storage adapter for Nostr events. */
|
||||||
class EventsDB implements NStore {
|
class EventsDB implements NStore {
|
||||||
private store: NDatabase | NPostgres;
|
private store: NDatabase | NPostgres;
|
||||||
private console = new Stickynotes('ditto:db:events');
|
private console = new Stickynotes('ditto:db:events');
|
||||||
|
|
@ -52,21 +53,11 @@ class EventsDB implements NStore {
|
||||||
't': ({ event, count, value }) => (event.kind === 1985 ? count < 20 : count < 5) && value.length < 50,
|
't': ({ event, count, value }) => (event.kind === 1985 ? count < 20 : count < 5) && value.length < 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(private database: DittoDatabase) {
|
constructor(private kysely: Kysely<DittoTables>) {
|
||||||
const { dialect, kysely } = database;
|
|
||||||
|
|
||||||
if (dialect === 'postgres') {
|
|
||||||
this.store = new NPostgres(kysely, {
|
this.store = new NPostgres(kysely, {
|
||||||
indexTags: EventsDB.indexTags,
|
indexTags: EventsDB.indexTags,
|
||||||
indexSearch: EventsDB.searchText,
|
indexSearch: EventsDB.searchText,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.store = new NDatabase(kysely, {
|
|
||||||
fts: 'sqlite',
|
|
||||||
indexTags: EventsDB.indexTags,
|
|
||||||
searchText: EventsDB.searchText,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Insert an event (and its tags) into the database. */
|
/** Insert an event (and its tags) into the database. */
|
||||||
|
|
@ -273,7 +264,7 @@ class EventsDB implements NStore {
|
||||||
return tags.map(([_tag, value]) => value).join('\n');
|
return tags.map(([_tag, value]) => value).join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Converts filters to more performant, simpler filters that are better for SQLite. */
|
/** Converts filters to more performant, simpler filters. */
|
||||||
async expandFilters(filters: NostrFilter[]): Promise<NostrFilter[]> {
|
async expandFilters(filters: NostrFilter[]): Promise<NostrFilter[]> {
|
||||||
filters = structuredClone(filters);
|
filters = structuredClone(filters);
|
||||||
|
|
||||||
|
|
@ -286,7 +277,7 @@ class EventsDB implements NStore {
|
||||||
) as { key: 'domain'; value: string } | undefined)?.value;
|
) as { key: 'domain'; value: string } | undefined)?.value;
|
||||||
|
|
||||||
if (domain) {
|
if (domain) {
|
||||||
const query = this.database.kysely
|
const query = this.kysely
|
||||||
.selectFrom('pubkey_domains')
|
.selectFrom('pubkey_domains')
|
||||||
.select('pubkey')
|
.select('pubkey')
|
||||||
.where('domain', '=', domain);
|
.where('domain', '=', domain);
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ interface HydrateOpts {
|
||||||
|
|
||||||
/** Hydrate events using the provided storage. */
|
/** Hydrate events using the provided storage. */
|
||||||
async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
async function hydrateEvents(opts: HydrateOpts): Promise<DittoEvent[]> {
|
||||||
const { events, store, signal, kysely = (await DittoDB.getInstance()).kysely } = opts;
|
const { events, store, signal, kysely = await DittoDB.getInstance() } = opts;
|
||||||
|
|
||||||
if (!events.length) {
|
if (!events.length) {
|
||||||
return events;
|
return events;
|
||||||
|
|
|
||||||
121
src/test.ts
121
src/test.ts
|
|
@ -1,21 +1,17 @@
|
||||||
import fs from 'node:fs/promises';
|
import { PGlite } from '@electric-sql/pglite';
|
||||||
import path from 'node:path';
|
|
||||||
|
|
||||||
import { Database as Sqlite } from '@db/sqlite';
|
|
||||||
import { NDatabase, NDatabaseSchema, NPostgresSchema } from '@nostrify/db';
|
|
||||||
import { NostrEvent } from '@nostrify/nostrify';
|
import { NostrEvent } from '@nostrify/nostrify';
|
||||||
import { DenoSqlite3Dialect } from '@soapbox/kysely-deno-sqlite';
|
import { PgliteDialect } from '@soapbox/kysely-pglite';
|
||||||
import { finalizeEvent, generateSecretKey } from 'nostr-tools';
|
import { finalizeEvent, generateSecretKey } from 'nostr-tools';
|
||||||
import { FileMigrationProvider, Kysely, Migrator } from 'kysely';
|
import { Kysely } from 'kysely';
|
||||||
import { PostgresJSDialect, PostgresJSDialectConfig } from 'kysely-postgres-js';
|
import { PostgresJSDialect, PostgresJSDialectConfig } from 'kysely-postgres-js';
|
||||||
import postgres from 'postgres';
|
import postgres from 'postgres';
|
||||||
|
|
||||||
import { DittoDatabase, DittoDB } from '@/db/DittoDB.ts';
|
import { Conf } from '@/config.ts';
|
||||||
|
import { DittoDB } from '@/db/DittoDB.ts';
|
||||||
import { DittoTables } from '@/db/DittoTables.ts';
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { purifyEvent } from '@/storages/hydrate.ts';
|
import { purifyEvent } from '@/storages/hydrate.ts';
|
||||||
import { KyselyLogger } from '@/db/KyselyLogger.ts';
|
import { KyselyLogger } from '@/db/KyselyLogger.ts';
|
||||||
import { EventsDB } from '@/storages/EventsDB.ts';
|
import { EventsDB } from '@/storages/EventsDB.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
|
|
||||||
/** Import an event fixture by name in tests. */
|
/** Import an event fixture by name in tests. */
|
||||||
export async function eventFixture(name: string): Promise<NostrEvent> {
|
export async function eventFixture(name: string): Promise<NostrEvent> {
|
||||||
|
|
@ -42,97 +38,45 @@ export function genEvent(t: Partial<NostrEvent> = {}, sk: Uint8Array = generateS
|
||||||
return purifyEvent(event);
|
return purifyEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get an in-memory SQLite database to use for testing. It's automatically destroyed when it goes out of scope. */
|
|
||||||
export async function getTestDB() {
|
|
||||||
const kysely = new Kysely<DittoTables>({
|
|
||||||
dialect: new DenoSqlite3Dialect({
|
|
||||||
database: new Sqlite(':memory:'),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const migrator = new Migrator({
|
|
||||||
db: kysely,
|
|
||||||
provider: new FileMigrationProvider({
|
|
||||||
fs,
|
|
||||||
path,
|
|
||||||
migrationFolder: new URL(import.meta.resolve('./db/migrations')).pathname,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
await migrator.migrateToLatest();
|
|
||||||
|
|
||||||
const store = new NDatabase(kysely);
|
|
||||||
|
|
||||||
return {
|
|
||||||
store,
|
|
||||||
kysely,
|
|
||||||
[Symbol.asyncDispose]: () => kysely.destroy(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create an database for testing. */
|
/** Create an database for testing. */
|
||||||
export const createTestDB = async (databaseUrl?: string) => {
|
export const createTestDB = async (databaseUrl = Conf.testDatabaseUrl) => {
|
||||||
databaseUrl ??= Deno.env.get('DATABASE_URL') ?? 'sqlite://:memory:';
|
const { protocol } = new URL(databaseUrl);
|
||||||
|
|
||||||
let dialect: 'sqlite' | 'postgres' = (() => {
|
const kysely: Kysely<DittoTables> = (() => {
|
||||||
const protocol = databaseUrl.split(':')[0];
|
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case 'sqlite':
|
case 'postgres:':
|
||||||
return 'sqlite';
|
case 'postgresql:':
|
||||||
case 'postgres':
|
return new Kysely({
|
||||||
return protocol;
|
|
||||||
case 'postgresql':
|
|
||||||
return 'postgres';
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported protocol: ${protocol}`);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
const allowToUseDATABASE_URL = Deno.env.get('ALLOW_TO_USE_DATABASE_URL')?.toLowerCase() ?? '';
|
|
||||||
if (allowToUseDATABASE_URL !== 'true' && dialect === 'postgres') {
|
|
||||||
console.warn(
|
|
||||||
'%cRunning tests with sqlite, if you meant to use Postgres, run again with ALLOW_TO_USE_DATABASE_URL environment variable set to true',
|
|
||||||
'color: yellow;',
|
|
||||||
);
|
|
||||||
dialect = 'sqlite';
|
|
||||||
}
|
|
||||||
|
|
||||||
console.warn(`Using: ${dialect}`);
|
|
||||||
|
|
||||||
const db: DittoDatabase = { dialect } as DittoDatabase;
|
|
||||||
|
|
||||||
if (dialect === 'sqlite') {
|
|
||||||
// migration 021_pgfts_index.ts calls 'Conf.db.dialect',
|
|
||||||
// and this calls the DATABASE_URL environment variable.
|
|
||||||
// The following line ensures to NOT use the DATABASE_URL that may exist in an .env file.
|
|
||||||
Deno.env.set('DATABASE_URL', 'sqlite://:memory:');
|
|
||||||
|
|
||||||
db.kysely = new Kysely({
|
|
||||||
dialect: new DenoSqlite3Dialect({
|
|
||||||
database: new Sqlite(':memory:'),
|
|
||||||
}),
|
|
||||||
}) as Kysely<DittoTables> & Kysely<NDatabaseSchema>;
|
|
||||||
} else {
|
|
||||||
db.kysely = new Kysely({
|
|
||||||
// @ts-ignore Kysely version mismatch.
|
// @ts-ignore Kysely version mismatch.
|
||||||
dialect: new PostgresJSDialect({
|
dialect: new PostgresJSDialect({
|
||||||
postgres: postgres(Conf.databaseUrl, {
|
postgres: postgres(databaseUrl, {
|
||||||
max: Conf.pg.poolSize,
|
max: Conf.pg.poolSize,
|
||||||
}) as unknown as PostgresJSDialectConfig['postgres'],
|
}) as unknown as PostgresJSDialectConfig['postgres'],
|
||||||
}),
|
}),
|
||||||
log: KyselyLogger,
|
log: KyselyLogger,
|
||||||
}) as Kysely<DittoTables> & Kysely<NPostgresSchema>;
|
});
|
||||||
|
case 'file:':
|
||||||
|
case 'memory:':
|
||||||
|
return new Kysely({
|
||||||
|
dialect: new PgliteDialect({
|
||||||
|
database: new PGlite(databaseUrl),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported database URL protocol: ${protocol}`);
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
await DittoDB.migrate(db.kysely);
|
await DittoDB.migrate(kysely);
|
||||||
const store = new EventsDB(db);
|
const store = new EventsDB(kysely);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dialect,
|
|
||||||
store,
|
store,
|
||||||
kysely: db.kysely,
|
kysely,
|
||||||
[Symbol.asyncDispose]: async () => {
|
[Symbol.asyncDispose]: async () => {
|
||||||
if (dialect === 'postgres') {
|
// If we're testing against real Postgres, we will reuse the database
|
||||||
|
// between tests, so we should drop the tables to keep each test fresh.
|
||||||
|
if (['postgres:', 'postgresql:'].includes(protocol)) {
|
||||||
for (
|
for (
|
||||||
const table of [
|
const table of [
|
||||||
'author_stats',
|
'author_stats',
|
||||||
|
|
@ -142,16 +86,13 @@ export const createTestDB = async (databaseUrl?: string) => {
|
||||||
'kysely_migration_lock',
|
'kysely_migration_lock',
|
||||||
'nip46_tokens',
|
'nip46_tokens',
|
||||||
'pubkey_domains',
|
'pubkey_domains',
|
||||||
'unattached_media',
|
|
||||||
'nostr_events',
|
'nostr_events',
|
||||||
'nostr_tags',
|
|
||||||
'nostr_pgfts',
|
|
||||||
'event_zaps',
|
'event_zaps',
|
||||||
]
|
]
|
||||||
) {
|
) {
|
||||||
await db.kysely.schema.dropTable(table).ifExists().cascade().execute();
|
await kysely.schema.dropTable(table).ifExists().cascade().execute();
|
||||||
}
|
}
|
||||||
await db.kysely.destroy();
|
await kysely.destroy();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { NostrFilter } from '@nostrify/nostrify';
|
import { NostrFilter } from '@nostrify/nostrify';
|
||||||
import { Stickynotes } from '@soapbox/stickynotes';
|
import { Stickynotes } from '@soapbox/stickynotes';
|
||||||
import { sql } from 'kysely';
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
import { Conf } from '@/config.ts';
|
import { Conf } from '@/config.ts';
|
||||||
import { DittoDatabase, DittoDB } from '@/db/DittoDB.ts';
|
import { DittoDB } from '@/db/DittoDB.ts';
|
||||||
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { handleEvent } from '@/pipeline.ts';
|
import { handleEvent } from '@/pipeline.ts';
|
||||||
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
import { AdminSigner } from '@/signers/AdminSigner.ts';
|
||||||
import { Time } from '@/utils/time.ts';
|
import { Time } from '@/utils/time.ts';
|
||||||
|
|
@ -13,13 +14,12 @@ const console = new Stickynotes('ditto:trends');
|
||||||
/** Get trending tag values for a given tag in the given time frame. */
|
/** Get trending tag values for a given tag in the given time frame. */
|
||||||
export async function getTrendingTagValues(
|
export async function getTrendingTagValues(
|
||||||
/** Kysely instance to execute queries on. */
|
/** Kysely instance to execute queries on. */
|
||||||
{ dialect, kysely }: DittoDatabase,
|
kysely: Kysely<DittoTables>,
|
||||||
/** Tag name to filter by, eg `t` or `r`. */
|
/** Tag name to filter by, eg `t` or `r`. */
|
||||||
tagNames: string[],
|
tagNames: string[],
|
||||||
/** Filter of eligible events. */
|
/** Filter of eligible events. */
|
||||||
filter: NostrFilter,
|
filter: NostrFilter,
|
||||||
): Promise<{ value: string; authors: number; uses: number }[]> {
|
): Promise<{ value: string; authors: number; uses: number }[]> {
|
||||||
if (dialect === 'postgres') {
|
|
||||||
let query = kysely
|
let query = kysely
|
||||||
.selectFrom([
|
.selectFrom([
|
||||||
'nostr_events',
|
'nostr_events',
|
||||||
|
|
@ -58,43 +58,6 @@ export async function getTrendingTagValues(
|
||||||
authors: Number(row.authors),
|
authors: Number(row.authors),
|
||||||
uses: Number(row.uses),
|
uses: Number(row.uses),
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
if (dialect === 'sqlite') {
|
|
||||||
let query = kysely
|
|
||||||
.selectFrom('nostr_tags')
|
|
||||||
.select(({ fn }) => [
|
|
||||||
'nostr_tags.value',
|
|
||||||
fn.agg<number>('count', ['nostr_tags.pubkey']).distinct().as('authors'),
|
|
||||||
fn.countAll<number>().as('uses'),
|
|
||||||
])
|
|
||||||
.where('nostr_tags.name', 'in', tagNames)
|
|
||||||
.groupBy('nostr_tags.value')
|
|
||||||
.orderBy((c) => c.fn.agg('count', ['nostr_tags.pubkey']).distinct(), 'desc');
|
|
||||||
|
|
||||||
if (filter.kinds) {
|
|
||||||
query = query.where('nostr_tags.kind', 'in', filter.kinds);
|
|
||||||
}
|
|
||||||
if (typeof filter.since === 'number') {
|
|
||||||
query = query.where('nostr_tags.created_at', '>=', filter.since);
|
|
||||||
}
|
|
||||||
if (typeof filter.until === 'number') {
|
|
||||||
query = query.where('nostr_tags.created_at', '<=', filter.until);
|
|
||||||
}
|
|
||||||
if (typeof filter.limit === 'number') {
|
|
||||||
query = query.limit(filter.limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = await query.execute();
|
|
||||||
|
|
||||||
return rows.map((row) => ({
|
|
||||||
value: row.value,
|
|
||||||
authors: Number(row.authors),
|
|
||||||
uses: Number(row.uses),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get trending tags and publish an event with them. */
|
/** Get trending tags and publish an event with them. */
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export class SimpleLRU<
|
||||||
|
|
||||||
constructor(fetchFn: FetchFn<K, V, { signal: AbortSignal }>, opts: LRUCache.Options<K, V, void>) {
|
constructor(fetchFn: FetchFn<K, V, { signal: AbortSignal }>, opts: LRUCache.Options<K, V, void>) {
|
||||||
this.cache = new LRUCache({
|
this.cache = new LRUCache({
|
||||||
fetchMethod: (key, _staleValue, { signal }) => fetchFn(key, { signal: signal as AbortSignal }),
|
fetchMethod: (key, _staleValue, { signal }) => fetchFn(key, { signal: signal as unknown as AbortSignal }),
|
||||||
...opts,
|
...opts,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { z } from 'zod';
|
||||||
|
|
||||||
import { DittoTables } from '@/db/DittoTables.ts';
|
import { DittoTables } from '@/db/DittoTables.ts';
|
||||||
import { findQuoteTag, findReplyTag, getTagSet } from '@/utils/tags.ts';
|
import { findQuoteTag, findReplyTag, getTagSet } from '@/utils/tags.ts';
|
||||||
import { Conf } from '@/config.ts';
|
|
||||||
|
|
||||||
interface UpdateStatsOpts {
|
interface UpdateStatsOpts {
|
||||||
kysely: Kysely<DittoTables>;
|
kysely: Kysely<DittoTables>;
|
||||||
|
|
@ -197,16 +196,13 @@ export async function updateAuthorStats(
|
||||||
notes_count: 0,
|
notes_count: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let query = kysely
|
const prev = await kysely
|
||||||
.selectFrom('author_stats')
|
.selectFrom('author_stats')
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where('pubkey', '=', pubkey);
|
.forUpdate()
|
||||||
|
.where('pubkey', '=', pubkey)
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
if (Conf.db.dialect === 'postgres') {
|
|
||||||
query = query.forUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
const prev = await query.executeTakeFirst();
|
|
||||||
const stats = fn(prev ?? empty);
|
const stats = fn(prev ?? empty);
|
||||||
|
|
||||||
if (prev) {
|
if (prev) {
|
||||||
|
|
@ -249,16 +245,13 @@ export async function updateEventStats(
|
||||||
reactions: '{}',
|
reactions: '{}',
|
||||||
};
|
};
|
||||||
|
|
||||||
let query = kysely
|
const prev = await kysely
|
||||||
.selectFrom('event_stats')
|
.selectFrom('event_stats')
|
||||||
.selectAll()
|
.selectAll()
|
||||||
.where('event_id', '=', eventId);
|
.forUpdate()
|
||||||
|
.where('event_id', '=', eventId)
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
if (Conf.db.dialect === 'postgres') {
|
|
||||||
query = query.forUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
const prev = await query.executeTakeFirst();
|
|
||||||
const stats = fn(prev ?? empty);
|
const stats = fn(prev ?? empty);
|
||||||
|
|
||||||
if (prev) {
|
if (prev) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import Debug from '@soapbox/stickynotes/debug';
|
import Debug from '@soapbox/stickynotes/debug';
|
||||||
import * as Comlink from 'comlink';
|
import * as Comlink from 'comlink';
|
||||||
|
|
||||||
import './handlers/abortsignal.ts';
|
import '@/workers/handlers/abortsignal.ts';
|
||||||
import '@/sentry.ts';
|
import '@/sentry.ts';
|
||||||
|
|
||||||
const debug = Debug('ditto:fetch.worker');
|
const debug = Debug('ditto:fetch.worker');
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
import * as Comlink from 'comlink';
|
|
||||||
import { asyncGeneratorTransferHandler } from 'comlink-async-generator';
|
|
||||||
import { CompiledQuery, QueryResult } from 'kysely';
|
|
||||||
|
|
||||||
import type { SqliteWorker as _SqliteWorker } from './sqlite.worker.ts';
|
|
||||||
|
|
||||||
Comlink.transferHandlers.set('asyncGenerator', asyncGeneratorTransferHandler);
|
|
||||||
|
|
||||||
class SqliteWorker {
|
|
||||||
#worker: Worker;
|
|
||||||
#client: ReturnType<typeof Comlink.wrap<typeof _SqliteWorker>>;
|
|
||||||
#ready: Promise<void>;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.#worker = new Worker(new URL('./sqlite.worker.ts', import.meta.url).href, { type: 'module' });
|
|
||||||
this.#client = Comlink.wrap<typeof _SqliteWorker>(this.#worker);
|
|
||||||
|
|
||||||
this.#ready = new Promise<void>((resolve) => {
|
|
||||||
const handleEvent = (event: MessageEvent) => {
|
|
||||||
if (event.data[0] === 'ready') {
|
|
||||||
this.#worker.removeEventListener('message', handleEvent);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.#worker.addEventListener('message', handleEvent);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async open(path: string): Promise<void> {
|
|
||||||
await this.#ready;
|
|
||||||
return this.#client.open(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeQuery<R>(query: CompiledQuery): Promise<QueryResult<R>> {
|
|
||||||
await this.#ready;
|
|
||||||
return this.#client.executeQuery(query) as Promise<QueryResult<R>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
async *streamQuery<R>(query: CompiledQuery): AsyncIterableIterator<QueryResult<R>> {
|
|
||||||
await this.#ready;
|
|
||||||
|
|
||||||
for await (const result of await this.#client.streamQuery(query)) {
|
|
||||||
yield result as QueryResult<R>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(): Promise<void> {
|
|
||||||
return this.#client.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SqliteWorker;
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
/// <reference lib="webworker" />
|
|
||||||
import { Database as SQLite } from '@db/sqlite';
|
|
||||||
import * as Comlink from 'comlink';
|
|
||||||
import { CompiledQuery, QueryResult } from 'kysely';
|
|
||||||
import { asyncGeneratorTransferHandler } from 'comlink-async-generator';
|
|
||||||
|
|
||||||
import '@/sentry.ts';
|
|
||||||
|
|
||||||
let db: SQLite | undefined;
|
|
||||||
|
|
||||||
export const SqliteWorker = {
|
|
||||||
open(path: string): void {
|
|
||||||
db = new SQLite(path);
|
|
||||||
},
|
|
||||||
executeQuery<R>({ sql, parameters }: CompiledQuery): QueryResult<R> {
|
|
||||||
if (!db) throw new Error('Database not open');
|
|
||||||
|
|
||||||
return {
|
|
||||||
rows: db!.prepare(sql).all(...parameters as any[]) as R[],
|
|
||||||
numAffectedRows: BigInt(db!.changes),
|
|
||||||
insertId: BigInt(db!.lastInsertRowId),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async *streamQuery<R>({ sql, parameters }: CompiledQuery): AsyncIterableIterator<QueryResult<R>> {
|
|
||||||
if (!db) throw new Error('Database not open');
|
|
||||||
|
|
||||||
const stmt = db.prepare(sql).bind(...parameters as any[]);
|
|
||||||
for (const row of stmt) {
|
|
||||||
yield {
|
|
||||||
rows: [row],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
destroy() {
|
|
||||||
db?.close();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Comlink.transferHandlers.set('asyncGenerator', asyncGeneratorTransferHandler);
|
|
||||||
Comlink.expose(SqliteWorker);
|
|
||||||
|
|
||||||
self.postMessage(['ready']);
|
|
||||||
Loading…
Add table
Reference in a new issue