refactor: migrate from node-sqlite3-wasm to better-sqlite3

Replace all db.run(sql, [params]) calls with db.prepare(sql).run(...params)
across all source modules and test files. Also remove the node-sqlite3-wasm
import from tests/db.test.ts and update the type annotation accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Mueller 2026-04-06 21:17:44 +02:00
parent 25d07a6bc4
commit e760853781
16 changed files with 154 additions and 751 deletions

View File

@ -8,8 +8,6 @@
"name": "display-middleware",
"version": "1.0.0",
"dependencies": {
"@types/bcryptjs": "^2.4.6",
"bcrypt": "^5.1.1",
"bcryptjs": "^3.0.3",
"better-sqlite3": "^9.6.0",
"cors": "^2.8.6",
@ -20,7 +18,7 @@
"pg": "^8.20.0"
},
"devDependencies": {
"@types/bcrypt": "^5.0.2",
"@types/bcryptjs": "^2.4.6",
"@types/better-sqlite3": "^7.6.13",
"@types/cors": "^2.8.19",
"@types/express": "^4.17.25",
@ -505,26 +503,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
"license": "BSD-3-Clause",
"dependencies": {
"detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.11"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
@ -944,20 +922,11 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/bcrypt": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz",
"integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/bcryptjs": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/better-sqlite3": {
@ -1264,12 +1233,6 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"license": "ISC"
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@ -1321,50 +1284,6 @@
"node": ">=0.4.0"
}
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/agent-base/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/agent-base/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
@ -1378,40 +1297,6 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/aproba": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
"integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==",
"license": "ISC"
},
"node_modules/are-we-there-yet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"deprecated": "This package is no longer supported.",
"license": "ISC",
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/are-we-there-yet/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==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@ -1442,12 +1327,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@ -1468,20 +1347,6 @@
],
"license": "MIT"
},
"node_modules/bcrypt": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
"integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"node-addon-api": "^5.0.0"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/bcryptjs": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
@ -1547,16 +1412,6 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/brace-expansion": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/broker-factory": {
"version": "3.1.14",
"resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.14.tgz",
@ -1685,24 +1540,6 @@
"node": "*"
}
},
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"license": "ISC",
"bin": {
"color-support": "bin.js"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -1732,12 +1569,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT"
},
"node_modules/concat-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
@ -1774,12 +1605,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
"license": "ISC"
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -1911,12 +1736,6 @@
"node": ">=0.4.0"
}
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"license": "MIT"
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -1995,12 +1814,6 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@ -2343,36 +2156,6 @@
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"license": "ISC",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/fs-minipass/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"license": "ISC"
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -2397,27 +2180,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"deprecated": "This package is no longer supported.",
"license": "ISC",
"dependencies": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/get-func-name": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
@ -2497,27 +2259,6 @@
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@ -2558,12 +2299,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
"license": "ISC"
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@ -2602,42 +2337,6 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/https-proxy-agent/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/https-proxy-agent/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/human-signals": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
@ -2680,17 +2379,6 @@
],
"license": "BSD-3-Clause"
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@ -2721,15 +2409,6 @@
"node": ">= 0.10"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
@ -2901,30 +2580,6 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"license": "MIT",
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -3026,18 +2681,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
@ -3047,52 +2690,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"license": "ISC",
"engines": {
"node": ">=8"
}
},
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"license": "MIT",
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minizlib/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"license": "MIT",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
@ -3260,53 +2857,12 @@
"node": ">=10"
}
},
"node_modules/node-addon-api": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
"license": "MIT"
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-sqlite3-wasm": {
"version": "0.8.55",
"resolved": "https://registry.npmjs.org/node-sqlite3-wasm/-/node-sqlite3-wasm-0.8.55.tgz",
"integrity": "sha512-C2m7JzZgKiv9XVZ1ts9oPmS56PCvyHeQffTOF2KNO2TVZzq5IW2s+NFeEZn+eP6bnAuD2We/O9cOJSjQVf7Xxw==",
"license": "MIT"
},
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
"license": "ISC",
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/npm-run-path": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
@ -3336,19 +2892,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"deprecated": "This package is no longer supported.",
"license": "ISC",
"dependencies": {
"are-we-there-yet": "^2.0.0",
"console-control-strings": "^1.1.0",
"gauge": "^3.0.0",
"set-blocking": "^2.0.0"
}
},
"node_modules/number-allocator": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz",
@ -3465,15 +3008,6 @@
"node": ">= 0.8"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@ -3863,22 +3397,6 @@
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"license": "MIT"
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rollup": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
@ -4007,12 +3525,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@ -4121,12 +3633,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
@ -4247,32 +3753,6 @@
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-final-newline": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
@ -4384,24 +3864,6 @@
"node": ">=6.4.0"
}
},
"node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
@ -4521,12 +3983,6 @@
"node": ">=0.6"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -5283,22 +4739,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -5332,15 +4772,6 @@
"node": ">=8"
}
},
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"license": "ISC",
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/worker-factory": {
"version": "7.0.49",
"resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.49.tgz",
@ -5424,12 +4855,6 @@
"node": ">=0.4"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"license": "ISC"
},
"node_modules/yocto-queue": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz",

View File

@ -44,10 +44,9 @@ router.get('/:id', (req: Request, res: Response): void => {
}
// Update last_seen
db.run(
`UPDATE displays SET last_seen = datetime('now') WHERE id = ?`,
[displayId]
);
db.prepare(
`UPDATE displays SET last_seen = datetime('now') WHERE id = ?`
).run(displayId);
const json = buildDisplayJson(displayId);
if (!json) {

View File

@ -43,11 +43,10 @@ router.post('/', (req, res): void => {
const { name, description, layout_id, api_token, refresh_sec } = req.body;
if (!name) { res.status(400).json({ error: 'name is required' }); return; }
const result = db.run(
const result = db.prepare(
`INSERT INTO displays (name, description, layout_id, api_token, refresh_sec)
VALUES (?, ?, ?, ?, ?)`,
[name, description ?? null, layout_id ?? null, api_token ?? null, refresh_sec ?? 300]
);
VALUES (?, ?, ?, ?, ?)`
).run(name, description ?? null, layout_id ?? null, api_token ?? null, refresh_sec ?? 300);
const id = result.lastInsertRowid as number;
res.status(201).json(db.prepare('SELECT * FROM displays WHERE id = ?').get(id));
} catch (err) {
@ -78,7 +77,7 @@ router.put('/:id', (req, res): void => {
}
values.push(id);
db.run(`UPDATE displays SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE displays SET ${fields.join(', ')} WHERE id = ?`).run(...values);
const display = db.prepare('SELECT * FROM displays WHERE id = ?').get(id);
if (!display) { res.status(404).json({ error: 'Display not found' }); return; }
res.json(display);
@ -91,7 +90,7 @@ router.put('/:id', (req, res): void => {
router.delete('/:id', (req, res): void => {
try {
const db = getDb();
const result = db.run('DELETE FROM displays WHERE id = ?', [parseInt(req.params.id)]);
const result = db.prepare('DELETE FROM displays WHERE id = ?').run(parseInt(req.params.id));
if ((result.changes as number) === 0) { res.status(404).json({ error: 'Display not found' }); return; }
res.status(204).send();
} catch (err) {

View File

@ -1,16 +1,13 @@
import { Database } from 'node-sqlite3-wasm';
import Database from 'better-sqlite3';
import { migrate001 } from './migrations/001-initial';
let dbInstance: Database | null = null;
let dbInstance: Database.Database | null = null;
export function initDb(path: string): Database {
// node-sqlite3-wasm: pass undefined for in-memory DB
const db = new Database(path === ':memory:' ? undefined : path);
export function initDb(path: string): Database.Database {
const db = new Database(path === ':memory:' ? ':memory:' : path);
// Enable WAL mode (file-based databases only, :memory: uses 'memory' mode)
db.exec('PRAGMA journal_mode = WAL');
// Enable foreign key constraints
db.exec('PRAGMA foreign_keys = ON');
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
// Run migrations
migrate001(db);
@ -19,7 +16,7 @@ export function initDb(path: string): Database {
return db;
}
export function getDb(): Database {
export function getDb(): Database.Database {
if (!dbInstance) {
throw new Error('Database not initialized. Call initDb() first.');
}

View File

@ -1,4 +1,4 @@
import { Database } from 'node-sqlite3-wasm';
import Database from 'better-sqlite3';
export function migrate001(db: Database): void {
db.exec(`

View File

@ -19,10 +19,9 @@ async function main(): Promise<void> {
if (userCount.count === 0) {
const hash = bcrypt.hashSync('admin', 10);
db.run(
`INSERT INTO users (username, password_hash) VALUES (?, ?)`,
['admin', hash]
);
db.prepare(
`INSERT INTO users (username, password_hash) VALUES (?, ?)`
).run('admin', hash);
console.log('Created default admin user (admin/admin)');
}

View File

@ -54,10 +54,9 @@ export function updateDatapointValue(id: number, rawValue: string): Datapoint |
const transformed = applyTransform(rawValue, config);
db.run(
`UPDATE datapoints SET value = ?, status = 'ok', updated_at = datetime('now') WHERE id = ?`,
[transformed, id]
);
db.prepare(
`UPDATE datapoints SET value = ?, status = 'ok', updated_at = datetime('now') WHERE id = ?`
).run(transformed, id);
return db.prepare('SELECT * FROM datapoints WHERE id = ?').get(id) as Datapoint | undefined;
}
@ -87,7 +86,7 @@ export function updateDatapoint(id: number, data: DatapointUpdate): Datapoint |
if (fields.length === 0) return getDatapointById(id);
values.push(id);
db.run(`UPDATE datapoints SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE datapoints SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return getDatapointById(id);
}
@ -97,11 +96,10 @@ export function createDatapointFromMqtt(
topicPath: string
): Datapoint {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO datapoints (name, label, source_type, source_id, topic_id, enabled)
VALUES (?, ?, 'mqtt', ?, ?, 1)`,
[topicPath, topicPath, brokerId, topicId]
);
VALUES (?, ?, 'mqtt', ?, ?, 1)`
).run(topicPath, topicPath, brokerId, topicId);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM datapoints WHERE id = ?').get(id) as Datapoint;
}
@ -112,11 +110,10 @@ export function createDatapointFromPg(
queryName: string
): Datapoint {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO datapoints (name, label, source_type, source_id, query_id, enabled)
VALUES (?, ?, 'postgresql', ?, ?, 1)`,
[queryName, queryName, sourceId, queryId]
);
VALUES (?, ?, 'postgresql', ?, ?, 1)`
).run(queryName, queryName, sourceId, queryId);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM datapoints WHERE id = ?').get(id) as Datapoint;
}

View File

@ -92,17 +92,16 @@ export function getLayoutById(id: number): LayoutWithElements | undefined {
export function createLayout(data: CreateLayoutData): Layout {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO layouts (name, type, width, height, grid_cols, grid_rows)
VALUES (?, ?, ?, ?, ?, ?)`,
[
data.name,
data.type ?? 'free',
data.width ?? 800,
data.height ?? 480,
data.grid_cols ?? null,
data.grid_rows ?? null,
]
VALUES (?, ?, ?, ?, ?, ?)`
).run(
data.name,
data.type ?? 'free',
data.width ?? 800,
data.height ?? 480,
data.grid_cols ?? null,
data.grid_rows ?? null,
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout;
@ -125,41 +124,40 @@ export function updateLayout(id: number, data: UpdateLayoutData): Layout | undef
}
values.push(id);
db.run(`UPDATE layouts SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE layouts SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout | undefined;
}
export function deleteLayout(id: number): boolean {
const db = getDb();
const result = db.run('DELETE FROM layouts WHERE id = ?', [id]);
const result = db.prepare('DELETE FROM layouts WHERE id = ?').run(id);
return (result.changes as number) > 0;
}
export function addElement(layoutId: number, data: CreateElementData): LayoutElement {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO layout_elements
(layout_id, datapoint_id, type, x, y, width, height, font_size, font_weight,
text_align, icon, style, static_text, grid_col, grid_row, sort_order)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
layoutId,
data.datapoint_id ?? null,
data.type,
data.x ?? 0,
data.y ?? 0,
data.width ?? null,
data.height ?? null,
data.font_size ?? 24,
data.font_weight ?? 'normal',
data.text_align ?? 'left',
data.icon ?? null,
data.style ?? 'plain',
data.static_text ?? null,
data.grid_col ?? null,
data.grid_row ?? null,
data.sort_order ?? 0,
]
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
).run(
layoutId,
data.datapoint_id ?? null,
data.type,
data.x ?? 0,
data.y ?? 0,
data.width ?? null,
data.height ?? null,
data.font_size ?? 24,
data.font_weight ?? 'normal',
data.text_align ?? 'left',
data.icon ?? null,
data.style ?? 'plain',
data.static_text ?? null,
data.grid_col ?? null,
data.grid_row ?? null,
data.sort_order ?? 0,
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM layout_elements WHERE id = ?').get(id) as LayoutElement;
@ -200,12 +198,12 @@ export function updateElement(id: number, data: UpdateElementData): LayoutElemen
}
values.push(id);
db.run(`UPDATE layout_elements SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE layout_elements SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return db.prepare('SELECT * FROM layout_elements WHERE id = ?').get(id) as LayoutElement | undefined;
}
export function deleteElement(id: number): boolean {
const db = getDb();
const result = db.run('DELETE FROM layout_elements WHERE id = ?', [id]);
const result = db.prepare('DELETE FROM layout_elements WHERE id = ?').run(id);
return (result.changes as number) > 0;
}

View File

@ -55,18 +55,17 @@ export function getBrokerById(id: number): MqttBroker | undefined {
export function createBroker(data: CreateBrokerData): MqttBroker {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO mqtt_brokers (name, host, port, username, password, use_tls, enabled)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[
data.name,
data.host,
data.port ?? 1883,
data.username ?? null,
data.password ?? null,
data.use_tls ?? 0,
data.enabled ?? 1,
]
VALUES (?, ?, ?, ?, ?, ?, ?)`
).run(
data.name,
data.host,
data.port ?? 1883,
data.username ?? null,
data.password ?? null,
data.use_tls ?? 0,
data.enabled ?? 1,
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM mqtt_brokers WHERE id = ?').get(id) as MqttBroker;
@ -89,14 +88,14 @@ export function updateBroker(id: number, data: UpdateBrokerData): MqttBroker | u
fields.push("updated_at = datetime('now')");
values.push(id);
db.run(`UPDATE mqtt_brokers SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE mqtt_brokers SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return getBrokerById(id);
}
export function deleteBroker(id: number): boolean {
const db = getDb();
disconnectBroker(id);
const result = db.run('DELETE FROM mqtt_brokers WHERE id = ?', [id]);
const result = db.prepare('DELETE FROM mqtt_brokers WHERE id = ?').run(id);
return (result.changes as number) > 0;
}
@ -107,10 +106,9 @@ function buildConnectUrl(broker: MqttBroker): string {
function setStatus(id: number, status: string): void {
const db = getDb();
db.run(
`UPDATE mqtt_brokers SET status = ?, updated_at = datetime('now') WHERE id = ?`,
[status, id]
);
db.prepare(
`UPDATE mqtt_brokers SET status = ?, updated_at = datetime('now') WHERE id = ?`
).run(status, id);
}
export function connectBroker(id: number): Promise<mqtt.MqttClient> {
@ -172,7 +170,7 @@ export function connectBroker(id: number): Promise<mqtt.MqttClient> {
.get(id, t) as MqttTopic | undefined;
if (!topicRecord) continue;
db.run('UPDATE mqtt_topics SET last_payload = ? WHERE id = ?', [p, topicRecord.id]);
db.prepare('UPDATE mqtt_topics SET last_payload = ? WHERE id = ?').run(p, topicRecord.id);
const extracted = extractValue(p, topicRecord.json_path);
if (extracted === null) continue;

View File

@ -40,7 +40,7 @@ export function updateTopic(id: number, data: UpdateTopicData): MqttTopic | unde
}
values.push(id);
db.run(`UPDATE mqtt_topics SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE mqtt_topics SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return db.prepare('SELECT * FROM mqtt_topics WHERE id = ?').get(id) as MqttTopic | undefined;
}
@ -51,11 +51,10 @@ function ensureTopicExists(brokerId: number, topic: string): void {
.get(brokerId, topic);
if (!existing) {
db.run(
db.prepare(
`INSERT INTO mqtt_topics (broker_id, topic, selected, ignored)
VALUES (?, ?, 0, 0)`,
[brokerId, topic]
);
VALUES (?, ?, 0, 0)`
).run(brokerId, topic);
}
}
@ -105,10 +104,9 @@ export function startDiscovery(brokerId: number, prefix?: string): Promise<MqttT
discoveredTopics.add(t);
ensureTopicExists(brokerId, t);
}
db.run(
'UPDATE mqtt_topics SET last_payload = ? WHERE broker_id = ? AND topic = ?',
[p, brokerId, t]
);
db.prepare(
'UPDATE mqtt_topics SET last_payload = ? WHERE broker_id = ? AND topic = ?'
).run(p, brokerId, t);
}
payloadCache.clear();
};
@ -179,11 +177,10 @@ export function addManualTopic(brokerId: number, topic: string): MqttTopic {
if (existing) return existing;
const result = db.run(
const result = db.prepare(
`INSERT INTO mqtt_topics (broker_id, topic, selected, ignored)
VALUES (?, ?, 0, 0)`,
[brokerId, topic]
);
VALUES (?, ?, 0, 0)`
).run(brokerId, topic);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM mqtt_topics WHERE id = ?').get(id) as MqttTopic;
}

View File

@ -39,16 +39,15 @@ export function getQueryById(id: number): PgQuery | undefined {
export function createQuery(data: CreateQueryData): PgQuery {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO pg_queries (source_id, name, query, interval_sec, result_column)
VALUES (?, ?, ?, ?, ?)`,
[
data.source_id,
data.name,
data.query,
data.interval_sec ?? 60,
data.result_column,
]
VALUES (?, ?, ?, ?, ?)`
).run(
data.source_id,
data.name,
data.query,
data.interval_sec ?? 60,
data.result_column,
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM pg_queries WHERE id = ?').get(id) as PgQuery;
@ -67,14 +66,14 @@ export function updateQuery(id: number, data: UpdateQueryData): PgQuery | undefi
if (fields.length === 0) return getQueryById(id);
values.push(id);
db.run(`UPDATE pg_queries SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE pg_queries SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return getQueryById(id);
}
export function deleteQuery(id: number): boolean {
const db = getDb();
stopQueryTimer(id);
const result = db.run('DELETE FROM pg_queries WHERE id = ?', [id]);
const result = db.prepare('DELETE FROM pg_queries WHERE id = ?').run(id);
return (result.changes as number) > 0;
}
@ -99,10 +98,9 @@ async function executeQuery(queryId: number): Promise<void> {
const valueStr = String(rawValue);
const db = getDb();
db.run(
`UPDATE pg_queries SET last_result = ?, last_run_at = datetime('now') WHERE id = ?`,
[valueStr, queryId]
);
db.prepare(
`UPDATE pg_queries SET last_result = ?, last_run_at = datetime('now') WHERE id = ?`
).run(valueStr, queryId);
const dp = getDatapointByQueryId(queryId);
if (dp) {

View File

@ -42,19 +42,18 @@ export function getSourceById(id: number): PgSource | undefined {
export function createSource(data: CreateSourceData): PgSource {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO pg_sources (name, host, port, database_name, username, password, use_tls, enabled)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[
data.name,
data.host,
data.port ?? 5432,
data.database_name,
data.username,
data.password,
data.use_tls ?? 0,
data.enabled ?? 1,
]
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
).run(
data.name,
data.host,
data.port ?? 5432,
data.database_name,
data.username,
data.password,
data.use_tls ?? 0,
data.enabled ?? 1,
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM pg_sources WHERE id = ?').get(id) as PgSource;
@ -77,20 +76,20 @@ export function updateSource(id: number, data: UpdateSourceData): PgSource | und
if (fields.length === 0) return getSourceById(id);
values.push(id);
db.run(`UPDATE pg_sources SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE pg_sources SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return getSourceById(id);
}
export function deleteSource(id: number): boolean {
const db = getDb();
disconnectSource(id);
const result = db.run('DELETE FROM pg_sources WHERE id = ?', [id]);
const result = db.prepare('DELETE FROM pg_sources WHERE id = ?').run(id);
return (result.changes as number) > 0;
}
function setStatus(id: number, status: string): void {
const db = getDb();
db.run('UPDATE pg_sources SET status = ? WHERE id = ?', [status, id]);
db.prepare('UPDATE pg_sources SET status = ? WHERE id = ?').run(status, id);
}
export async function connectSource(id: number): Promise<Pool> {

View File

@ -8,10 +8,9 @@ beforeAll(() => {
const db = initDb(':memory:');
// Create a test user
const hash = bcrypt.hashSync('correctpassword', 10);
db.run(
`INSERT INTO users (username, password_hash) VALUES (?, ?)`,
['testuser', hash]
);
db.prepare(
`INSERT INTO users (username, password_hash) VALUES (?, ?)`
).run('testuser', hash);
});
afterAll(() => {

View File

@ -9,22 +9,22 @@ beforeAll(() => {
const db = initDb(':memory:');
// Create a layout
db.run(
db.prepare(
`INSERT INTO layouts (id, name, type, width, height)
VALUES (1, 'Test Layout', 'free', 800, 480)`
);
).run();
// Display without token
db.run(
db.prepare(
`INSERT INTO displays (id, name, layout_id, refresh_sec)
VALUES (1, 'Open Display', 1, 60)`
);
).run();
// Display with token auth
db.run(
db.prepare(
`INSERT INTO displays (id, name, layout_id, api_token, refresh_sec)
VALUES (2, 'Secure Display', 1, 'secret-token-123', 300)`
);
).run();
});
afterAll(() => {

View File

@ -1,8 +1,8 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { Database } from 'node-sqlite3-wasm';
import type Database from 'better-sqlite3';
import { initDb, closeDb } from '../src/db/index';
let db: Database;
let db: InstanceType<typeof Database>;
beforeAll(() => {
db = initDb(':memory:');
@ -96,10 +96,9 @@ describe('Table creation', () => {
describe('Broker insert and retrieve', () => {
it('should insert and retrieve a broker', () => {
const result = db.run(
`INSERT INTO mqtt_brokers (name, host, port) VALUES (?, ?, ?)`,
['Test Broker', 'mqtt.example.com', 1883]
);
const result = db.prepare(
`INSERT INTO mqtt_brokers (name, host, port) VALUES (?, ?, ?)`
).run('Test Broker', 'mqtt.example.com', 1883);
expect(result.lastInsertRowid).toBeGreaterThan(0);
const broker = db
@ -119,22 +118,22 @@ describe('Broker insert and retrieve', () => {
});
it('should cascade delete topics when broker is deleted', () => {
const broker = db.run(`INSERT INTO mqtt_brokers (name, host) VALUES (?, ?)`, [
const broker = db.prepare(`INSERT INTO mqtt_brokers (name, host) VALUES (?, ?)`).run(
'Cascade Broker',
'cascade.example.com',
]);
);
db.run(`INSERT INTO mqtt_topics (broker_id, topic) VALUES (?, ?)`, [
db.prepare(`INSERT INTO mqtt_topics (broker_id, topic) VALUES (?, ?)`).run(
broker.lastInsertRowid,
'sensors/temp',
]);
);
const topicsBefore = db
.prepare(`SELECT * FROM mqtt_topics WHERE broker_id = ?`)
.all(broker.lastInsertRowid);
expect(topicsBefore.length).toBe(1);
db.run(`DELETE FROM mqtt_brokers WHERE id = ?`, [broker.lastInsertRowid]);
db.prepare(`DELETE FROM mqtt_brokers WHERE id = ?`).run(broker.lastInsertRowid);
const topicsAfter = db
.prepare(`SELECT * FROM mqtt_topics WHERE broker_id = ?`)
@ -144,10 +143,9 @@ describe('Broker insert and retrieve', () => {
it('should reject invalid source_type values', () => {
expect(() => {
db.run(
`INSERT INTO datapoints (name, label, source_type, source_id) VALUES (?, ?, ?, ?)`,
['dp1', 'Datapoint 1', 'invalid_source', 1]
);
db.prepare(
`INSERT INTO datapoints (name, label, source_type, source_id) VALUES (?, ?, ?, ?)`
).run('dp1', 'Datapoint 1', 'invalid_source', 1);
}).toThrow();
});
});

View File

@ -6,48 +6,48 @@ beforeAll(() => {
const db = initDb(':memory:');
// Create a layout
db.run(
db.prepare(
`INSERT INTO layouts (id, name, type, width, height, grid_cols, grid_rows)
VALUES (1, 'Test Layout', 'grid', 800, 480, 2, 3)`
);
).run();
// Create a display with the layout
db.run(
db.prepare(
`INSERT INTO displays (id, name, layout_id, refresh_sec)
VALUES (1, 'Test Display', 1, 60)`
);
).run();
// Create a display without layout
db.run(
db.prepare(
`INSERT INTO displays (id, name, layout_id, refresh_sec)
VALUES (2, 'Empty Display', NULL, 300)`
);
).run();
// Create a broker and topic for datapoint
db.run(
db.prepare(
`INSERT INTO mqtt_brokers (id, name, host) VALUES (1, 'Test Broker', 'localhost')`
);
db.run(
).run();
db.prepare(
`INSERT INTO mqtt_topics (id, broker_id, topic) VALUES (1, 1, 'sensors/temp')`
);
).run();
// Create a datapoint
db.run(
db.prepare(
`INSERT INTO datapoints (id, name, label, unit, value, source_type, source_id, topic_id, enabled)
VALUES (1, 'temperature', 'Temperature', '°C', '22.5', 'mqtt', 1, 1, 1)`
);
).run();
// Create layout elements
// Label element
db.run(
db.prepare(
`INSERT INTO layout_elements (id, layout_id, type, x, y, static_text, sort_order)
VALUES (1, 1, 'label', 0, 0, 'Hello World', 0)`
);
).run();
// Value element linked to datapoint
db.run(
db.prepare(
`INSERT INTO layout_elements (id, layout_id, datapoint_id, type, x, y, font_size, sort_order)
VALUES (2, 1, 1, 'value', 100, 0, 32, 1)`
);
).run();
});
afterAll(() => {