From 3b0c31671e5adbdb9d162a85dbf6a960a85b041a Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Thu, 25 Jun 2026 13:31:29 -0400 Subject: [PATCH 01/21] fixes src/models/Category.test.ts and src/jobs/Derivative.test.ts --- api/package-lock.json | 235 +++++++++++++++++-------------- api/package.json | 8 +- api/src/jobs/Derivative.test.ts | 4 +- api/src/jobs/Derivative.ts | 2 +- api/src/models/AudioFile.ts | 2 +- api/src/models/Category.ts | 2 +- api/src/models/Config.ts | 4 +- api/src/models/ImageFile.ts | 7 +- api/src/models/Job.ts | 18 ++- api/src/models/JobMetadata.ts | 14 +- api/src/models/PageOrder.ts | 2 +- api/src/models/VideoFile.ts | 2 +- api/src/services/QueueManager.ts | 8 +- api/src/services/SolrCache.ts | 4 +- api/tsconfig.json | 4 +- 15 files changed, 176 insertions(+), 140 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index c05c07c4a..a06cd76b2 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -44,8 +44,10 @@ "devDependencies": { "@types/express": "^5.0.6", "@types/express-session": "^1.18.2", - "@types/jest": "^30", - "@types/node": "^25", + "@types/ini": "^4.1.1", + "@types/jest": "^30.0.0", + "@types/node": "^26", + "@types/pdfkit": "^0.17.6", "@typescript-eslint/eslint-plugin": "^8.52", "@typescript-eslint/parser": "^8.52", "eslint": "^10", @@ -57,7 +59,7 @@ "prettier": "^3", "supertest": "^7", "ts-jest": "^29", - "typescript": "^5", + "typescript": "^6", "typescript-eslint": "^8.52" } }, @@ -2305,9 +2307,9 @@ "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", "optional": true, @@ -2441,6 +2443,13 @@ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "license": "MIT" }, + "node_modules/@types/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2499,12 +2508,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", - "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", + "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "undici-types": "~8.3.0" } }, "node_modules/@types/passport": { @@ -2526,6 +2535,16 @@ "@types/passport": "*" } }, + "node_modules/@types/pdfkit": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.17.6.tgz", + "integrity": "sha512-tIwzxk2uWKp0Cq9JIluQXJid77lYhF52EsIOwhsMF4iWLA6YneoBR1xVKYYdAysHuepUB0OX4tdwMiUDdGKmig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.15.1", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", @@ -2606,17 +2625,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", - "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz", + "integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/type-utils": "8.61.1", - "@typescript-eslint/utils": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/scope-manager": "8.62.0", + "@typescript-eslint/type-utils": "8.62.0", + "@typescript-eslint/utils": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2629,22 +2648,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.61.1", + "@typescript-eslint/parser": "^8.62.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", - "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.0.tgz", + "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/scope-manager": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0", "debug": "^4.4.3" }, "engines": { @@ -2660,14 +2679,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", - "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz", + "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.61.1", - "@typescript-eslint/types": "^8.61.1", + "@typescript-eslint/tsconfig-utils": "^8.62.0", + "@typescript-eslint/types": "^8.62.0", "debug": "^4.4.3" }, "engines": { @@ -2682,14 +2701,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", - "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz", + "integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1" + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2700,9 +2719,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", - "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz", + "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==", "dev": true, "license": "MIT", "engines": { @@ -2717,15 +2736,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", - "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz", + "integrity": "sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1", - "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0", + "@typescript-eslint/utils": "8.62.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2742,9 +2761,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", - "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz", + "integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==", "dev": true, "license": "MIT", "engines": { @@ -2756,16 +2775,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", - "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz", + "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.61.1", - "@typescript-eslint/tsconfig-utils": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/project-service": "8.62.0", + "@typescript-eslint/tsconfig-utils": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2784,16 +2803,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", - "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz", + "integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1" + "@typescript-eslint/scope-manager": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2808,13 +2827,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", - "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz", + "integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/types": "8.62.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2839,9 +2858,9 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", - "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", + "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", "dev": true, "license": "ISC" }, @@ -3700,9 +3719,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", "dev": true, "funding": [ { @@ -3720,10 +3739,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -4103,9 +4122,9 @@ } }, "node_modules/bullmq": { - "version": "5.79.0", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.79.0.tgz", - "integrity": "sha512-sg+kYGn7PIDI/AAkSWINPNz0vMp745YaBYMyo3AtcfXk5iCvjEPU+XMbU3yf7rSf1KsU+OfU+GH8FhxUa+WDNA==", + "version": "5.79.1", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.79.1.tgz", + "integrity": "sha512-cteoHRr1FGOTUgzFrnMyBNGtQhNeVR8Ej6nImNSHQDJi4tj6GMD0p9ZG65ZsTnvR9RVf18dhRxWu4kFl634QGA==", "license": "MIT", "dependencies": { "cron-parser": "4.9.0", @@ -4808,9 +4827,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.375", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.375.tgz", - "integrity": "sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==", + "version": "1.5.377", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.377.tgz", + "integrity": "sha512-cH1jZgJHoezfTnKfKwnScpHywTFVnJUNITDPREFdhNjiuD502+QFpG0Qk7G8jhsV/f+CEAFlIrzP1fT+IMb92g==", "dev": true, "license": "ISC" }, @@ -5028,9 +5047,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "63.0.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.0.6.tgz", - "integrity": "sha512-qXMUdwQf+igjSLe/DZqyY1baVR0+snEJ3l/+2odJjeoAG43PrcpA0tSaLw2dC1iHvfKzo1YnZLGvgxrvAViyfQ==", + "version": "63.0.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.0.7.tgz", + "integrity": "sha512-pxrqGO733F7xmVYB5vQOiciiT9uddxqehawnbPjZmW2YaJR6fT5cP3UQd2BNoE85ATspCMtNL8w/a5WDGX3Qwg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -7840,9 +7859,9 @@ } }, "node_modules/n3": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/n3/-/n3-2.0.4.tgz", - "integrity": "sha512-d2Sr4vvqombySfOARjw5jQky0EYMwkEXuQQjYe2glasDdI3hAT3l00aJZbAmR+AA59eFNjJbDkHCq9l5Dm2DIg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/n3/-/n3-2.1.0.tgz", + "integrity": "sha512-+05H/h40wRyROglcVGrNZAwBu0Nc87luKhiTV95aGBGX1YyITtpwftHwyhT5YoZr4soXJWrzxqC1CHPdGqV63g==", "license": "MIT", "dependencies": { "buffer": "^6.0.3", @@ -7865,9 +7884,9 @@ } }, "node_modules/nanoid": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.14.tgz", - "integrity": "sha512-5c8l8kVzqpnDPaicbEop/fV0Q1w16FmbWtVhMqugTozAwYdlIQojWH5a/M7UfziFmGdQRrUdV+EPzc9Xng3VAQ==", + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.15.tgz", + "integrity": "sha512-kBg3RpGtIe+RpTbyXwoI6pk5yD7KUiI3sygUqgeBMRst42KmhB4RZC7eiO9Wa1HIpaCCtpE2DJ6OI4Wi5ebwFw==", "funding": [ { "type": "github", @@ -9077,9 +9096,9 @@ } }, "node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -10162,9 +10181,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -10175,16 +10194,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.1.tgz", - "integrity": "sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==", + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.0.tgz", + "integrity": "sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.61.1", - "@typescript-eslint/parser": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1", - "@typescript-eslint/utils": "8.61.1" + "@typescript-eslint/eslint-plugin": "8.62.0", + "@typescript-eslint/parser": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0", + "@typescript-eslint/utils": "8.62.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10241,9 +10260,9 @@ } }, "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "license": "MIT" }, "node_modules/unicode-properties": { @@ -10688,9 +10707,9 @@ "license": "ISC" }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { diff --git a/api/package.json b/api/package.json index 89593d7cc..e299eb7a8 100644 --- a/api/package.json +++ b/api/package.json @@ -57,7 +57,11 @@ "@types/express": "^5.0.6", "@types/express-session": "^1.18.2", "@types/jest": "^30", - "@types/node": "^25", + "@types/node": "^26", + "@types/ini": "^4.1.1", + "@types/jest": "^30.0.0", + "@types/node": "^26", + "@types/pdfkit": "^0.17.6", "@typescript-eslint/eslint-plugin": "^8.52", "@typescript-eslint/parser": "^8.52", "eslint": "^10", @@ -69,7 +73,7 @@ "prettier": "^3", "supertest": "^7", "ts-jest": "^29", - "typescript": "^5", + "typescript": "^6", "typescript-eslint": "^8.52" } } diff --git a/api/src/jobs/Derivative.test.ts b/api/src/jobs/Derivative.test.ts index 7df537189..e614509ec 100644 --- a/api/src/jobs/Derivative.test.ts +++ b/api/src/jobs/Derivative.test.ts @@ -16,8 +16,8 @@ describe("Derivative", () => { describe("run", () => { let job: Job; - let consoleErrorSpy; - let consoleLogSpy; + let consoleErrorSpy: jest.SpyInstance; + let consoleLogSpy: jest.SpyInstance; beforeEach(() => { job = { data: { diff --git a/api/src/jobs/Derivative.ts b/api/src/jobs/Derivative.ts index 829310b47..ec37f745e 100644 --- a/api/src/jobs/Derivative.ts +++ b/api/src/jobs/Derivative.ts @@ -12,7 +12,7 @@ class Derivative implements QueueJob { // For each page const order = PageOrder.fromJob(job.data); - const generatingPromises = []; + const generatingPromises: Promise[] = []; order.raw.forEach((page) => { // For each size const image = ImageFile.build(`${job.data.dir}/${page.filename}`); diff --git a/api/src/models/AudioFile.ts b/api/src/models/AudioFile.ts index 4d8f8744f..40e26eb3a 100644 --- a/api/src/models/AudioFile.ts +++ b/api/src/models/AudioFile.ts @@ -8,7 +8,7 @@ class AudioFile extends AbstractAVFile { return new AudioFile(filename, dir, Config.getInstance()); } - static fromRaw(raw: Record, config: Config = null): AudioFile { + static fromRaw(raw: Record, config: Config | null = null): AudioFile { return new AudioFile(raw.filename, raw.label, config ?? Config.getInstance()); } } diff --git a/api/src/models/Category.ts b/api/src/models/Category.ts index 96f215a15..a4b9e22ba 100644 --- a/api/src/models/Category.ts +++ b/api/src/models/Category.ts @@ -5,7 +5,7 @@ import path = require("path"); import Job from "./Job"; -export class CategoryRaw { +export interface CategoryRaw { category: string; jobs: Array; } diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index 49f7bc6e0..05b9ea473 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -10,9 +10,9 @@ interface ConfigRecord { class Config { private static instance: Config; - protected ini; + protected ini: Record; - constructor(ini: ConfigRecord) { + constructor(ini: Record) { this.ini = ini; } diff --git a/api/src/models/ImageFile.ts b/api/src/models/ImageFile.ts index 9c59bc700..c05fe9927 100644 --- a/api/src/models/ImageFile.ts +++ b/api/src/models/ImageFile.ts @@ -1,10 +1,7 @@ -import Sharp = require("sharp"); +import sharp from "sharp"; import path = require("path"); - import { execSync } from "child_process"; - import Config from "./Config"; - import fs = require("fs"); class ImageFile { @@ -42,7 +39,7 @@ class ImageFile { return deriv; } - const image = Sharp(this.filename, this.config.sharpOptions); + const image = sharp(this.filename, this.config.sharpOptions); const constraint = this.constraintForSize(size); const metadata = await image.metadata(); diff --git a/api/src/models/Job.ts b/api/src/models/Job.ts index 2d3ea81b7..bc8103329 100644 --- a/api/src/models/Job.ts +++ b/api/src/models/Job.ts @@ -1,5 +1,19 @@ import { createWriteStream, openSync, closeSync, existsSync as fileExists, statSync } from "fs"; import PDFDocument = require("pdfkit"); + +declare global { + namespace PDFKit { + interface PDFKitImage { + width: number; + height: number; + } + interface PDFDocument { + openImage(src: string | Buffer): PDFKitImage; + image(src: PDFKitImage, x?: number, y?: number, options?: Mixins.ImageOption): this; + image(src: PDFKitImage, options?: Mixins.ImageOption): this; + } + } +} import path = require("path"); import Config from "./Config"; @@ -11,7 +25,7 @@ import QueueManager from "../services/QueueManager"; class Job { dir: string; name: string; - _metadata: JobMetadata = null; + _metadata: JobMetadata | null = null; config: Config; queue: QueueManager; @@ -71,7 +85,7 @@ class Job { protected async getLargeJpegs(): Promise> { const pages = this.metadata.order.pages; - const jpegs = []; + const jpegs: string[] = []; for (const i in pages) { const image = ImageFile.build(this.dir + "/" + pages[i].filename); jpegs[i] = await image.derivative("LARGE"); diff --git a/api/src/models/JobMetadata.ts b/api/src/models/JobMetadata.ts index 698405b66..a43d2a535 100644 --- a/api/src/models/JobMetadata.ts +++ b/api/src/models/JobMetadata.ts @@ -16,12 +16,12 @@ interface JobMetadataRaw { class JobMetadata { job: Job; - page: PageRaw; + page: PageRaw | null = null; _filename: string; - _order: PageOrder = null; - _documents: DocumentOrder = null; - _audio: AudioOrder = null; - _video: VideoOrder = null; + _order: PageOrder | null = null; + _documents: DocumentOrder | null = null; + _audio: AudioOrder | null = null; + _video: VideoOrder | null = null; published = false; constructor(job: Job) { @@ -58,7 +58,7 @@ class JobMetadata { return this.job.dir + "/derivatives.lock"; } - get derivativeStatus(): Record { + get derivativeStatus(): { expected: number; processed: number; building: boolean } { const lockfileExists: boolean = fs.existsSync(this.derivativeLockfile); const status = { expected: 0, @@ -121,7 +121,7 @@ class JobMetadata { get ingestInfo(): string { const logfile: string = this.job.dir + "/ingest.log"; - return fs.existsSync(logfile) ? fs.readFileSync(logfile, "utf-8").split("\n").filter(Boolean).pop() : ""; + return fs.existsSync(logfile) ? fs.readFileSync(logfile, "utf-8").split("\n").filter(Boolean).pop() ?? "" : ""; } get order(): PageOrder { diff --git a/api/src/models/PageOrder.ts b/api/src/models/PageOrder.ts index 3f1992734..9fc1bde37 100644 --- a/api/src/models/PageOrder.ts +++ b/api/src/models/PageOrder.ts @@ -30,7 +30,7 @@ class PageOrder { return firstPartResults === 0 ? a.localeCompare(b) : firstPartResults; }); const pages = files.map((file) => { - return new Page(path.basename(file), null); + return new Page(path.basename(file), ""); }); return new PageOrder(pages); } diff --git a/api/src/models/VideoFile.ts b/api/src/models/VideoFile.ts index 713116d47..9bc12b3d2 100644 --- a/api/src/models/VideoFile.ts +++ b/api/src/models/VideoFile.ts @@ -9,7 +9,7 @@ class VideoFile extends AbstractAVFile { return new VideoFile(filename, dir, Config.getInstance()); } - static fromRaw(raw: Record, config: Config = null): VideoFile { + static fromRaw(raw: Record, config: Config | null = null): VideoFile { return new VideoFile(raw.filename, raw.label, config ?? Config.getInstance()); } diff --git a/api/src/services/QueueManager.ts b/api/src/services/QueueManager.ts index 1b8d52b53..8e1f6e5d6 100644 --- a/api/src/services/QueueManager.ts +++ b/api/src/services/QueueManager.ts @@ -29,11 +29,11 @@ class QueueManager { }; } - protected getQueue(queueName: string = null): Queue { + protected getQueue(queueName: string = ""): Queue { return new Queue(queueName ?? this.config.redisDefaultQueueName, this.queueBaseOptions); } - public getWorker(callback: Processor, queueName: string = null): Worker { + public getWorker(callback: Processor, queueName: string = ""): Worker { const options: WorkerOptions = this.queueBaseOptions; options.lockDuration = this.config.redisLockDuration; return new Worker(queueName ?? this.config.redisDefaultQueueName, callback, options); @@ -61,7 +61,7 @@ class QueueManager { return await this.addToQueue("ingest", { dir }); } - public async sendNotification(body: string, channel: string | null = null): Promise { + public async sendNotification(body: string, channel: string = ""): Promise { return await this.addToQueue("notify", { body, channel }); } @@ -69,7 +69,7 @@ class QueueManager { return await this.addToQueue("reindex", { file }); } - public async hasPendingIndexJob(q, queueJob): Promise { + public async hasPendingIndexJob(q: Queue, queueJob: { pid: string; action: string }): Promise { if (this.cache.isEnabled()) { return this.cache.isPidLocked(queueJob.pid, queueJob.action); } diff --git a/api/src/services/SolrCache.ts b/api/src/services/SolrCache.ts index dff416205..05eeef78c 100644 --- a/api/src/services/SolrCache.ts +++ b/api/src/services/SolrCache.ts @@ -159,7 +159,7 @@ export class SolrCache { } let document: Array = []; - let currentBatch: { file: string; size: number } = { file: null, size: 0 }; + let currentBatch: { file: string; size: number } = { file: "", size: 0 }; docs.forEach((file) => { const nextObject = this.readSolrAddDocFromFile(file); if (nextObject?.add?.doc === undefined) { @@ -175,7 +175,7 @@ export class SolrCache { if (currentBatch.size == batchSize) { this.writeFile(currentBatch.file, JSON.stringify(document)); document = []; - currentBatch = { file: null, size: 0 }; + currentBatch = { file: "", size: 0 }; } }); if (currentBatch.size > 0) { diff --git a/api/tsconfig.json b/api/tsconfig.json index 0d5405de2..f139d83b8 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -4,7 +4,9 @@ "allowJs": true, "target": "es5", "outDir": "./dist", - "skipLibCheck": true + "skipLibCheck": true, + "esModuleInterop": true, + "types": ["jest"] }, "include": [ "./src/**/*" From 16e7a3b99f5117b026822a43dd763ff8ed7e0885 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Tue, 7 Jul 2026 15:07:17 -0400 Subject: [PATCH 02/21] fix for FedoraDataCollection.test.ts --- api/package.json | 2 -- api/src/models/FedoraDataCollection.test.ts | 2 +- api/src/models/FedoraDataCollection.ts | 24 ++++++++++----------- api/tsconfig.json | 4 +++- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/api/package.json b/api/package.json index e299eb7a8..a11409c76 100644 --- a/api/package.json +++ b/api/package.json @@ -56,8 +56,6 @@ "devDependencies": { "@types/express": "^5.0.6", "@types/express-session": "^1.18.2", - "@types/jest": "^30", - "@types/node": "^26", "@types/ini": "^4.1.1", "@types/jest": "^30.0.0", "@types/node": "^26", diff --git a/api/src/models/FedoraDataCollection.test.ts b/api/src/models/FedoraDataCollection.test.ts index 55c107f42..d88ab26dd 100644 --- a/api/src/models/FedoraDataCollection.test.ts +++ b/api/src/models/FedoraDataCollection.test.ts @@ -1,7 +1,7 @@ import Config from "./Config"; import FedoraDataCollection from "./FedoraDataCollection"; -let fedoraData; +let fedoraData: FedoraDataCollection; beforeEach(() => { Config.setInstance(new Config({})); fedoraData = FedoraDataCollection.build("foo:123"); diff --git a/api/src/models/FedoraDataCollection.ts b/api/src/models/FedoraDataCollection.ts index b02e4357c..d288f20d1 100644 --- a/api/src/models/FedoraDataCollection.ts +++ b/api/src/models/FedoraDataCollection.ts @@ -42,10 +42,10 @@ class FedoraDataCollection { metadata: Record> = {}, fedoraDetails: Record> = {}, fedoraDatastreams: Array = [], - fedora: Fedora = null, - extractor: MetadataExtractor = null, - tika: TikaExtractor = null, - config: Config = null, + fedora: Fedora | null = null, + extractor: MetadataExtractor | null = null, + tika: TikaExtractor | null = null, + config: Config | null = null, ): FedoraDataCollection { return new FedoraDataCollection( pid, @@ -91,7 +91,7 @@ class FedoraDataCollection { * Create a flattened list of all PIDs "above" the current one. */ getAllParents(): Array { - const results = []; + const results: Array = []; this.parents.forEach((parent) => { const parentPids = [parent.pid].concat(parent.getAllParents()); parentPids.forEach((pid) => { @@ -116,7 +116,7 @@ class FedoraDataCollection { }; } - async getThumbnailHash(type: string): Promise { + async getThumbnailHash(type: string): Promise { const hashes = (await this.datastreamDetails.getThumbnails()).hasMessageDigest ?? []; for (const hash of hashes) { const parts = hash.split(":"); @@ -132,7 +132,7 @@ class FedoraDataCollection { return fitsData[name] ?? []; } - async getFitsValueAsString(name: string): Promise { + async getFitsValueAsString(name: string): Promise { const fitsData = await this.datastreamDetails.getFitsData(); if (typeof fitsData[name] === "undefined") { return null; @@ -140,12 +140,12 @@ class FedoraDataCollection { return fitsData[name][0] ?? null; } - async getFileSize(): Promise { + async getFileSize(): Promise { return await this.getFitsValueAsString("size"); } async getFullText(): Promise> { - let fullText = []; + let fullText: Array = []; const rawFullText = await this.datastreamDetails.getFullText(); for (const current in rawFullText) { fullText = fullText.concat(rawFullText[current]); @@ -156,11 +156,11 @@ class FedoraDataCollection { }); } - async getImageHeight(): Promise { + async getImageHeight(): Promise { return await this.getFitsValueAsString("imageHeight"); } - async getImageWidth(): Promise { + async getImageWidth(): Promise { return await this.getFitsValueAsString("imageWidth"); } @@ -176,7 +176,7 @@ class FedoraDataCollection { get models(): Array { // Separate identifier from URI prefix return (this.fedoraDetails.hasModel ?? []).map((model) => { - return model.split("/").pop(); + return model.split("/").pop() as string; }); } diff --git a/api/tsconfig.json b/api/tsconfig.json index f139d83b8..c1c4c64c3 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -2,7 +2,9 @@ "compilerOptions": { "sourceMap": true, "allowJs": true, - "target": "es5", + "target": "es2022", + "module": "commonjs", + "strict": false, "outDir": "./dist", "skipLibCheck": true, "esModuleInterop": true, From 8e9c6abf075138ef882dec63fa4f02d959f89661 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:10:03 +0000 Subject: [PATCH 03/21] Commit from GitHub Actions (Lint Pull Requests) --- api/src/models/JobMetadata.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/src/models/JobMetadata.ts b/api/src/models/JobMetadata.ts index a43d2a535..c8cf8b4fc 100644 --- a/api/src/models/JobMetadata.ts +++ b/api/src/models/JobMetadata.ts @@ -121,7 +121,9 @@ class JobMetadata { get ingestInfo(): string { const logfile: string = this.job.dir + "/ingest.log"; - return fs.existsSync(logfile) ? fs.readFileSync(logfile, "utf-8").split("\n").filter(Boolean).pop() ?? "" : ""; + return fs.existsSync(logfile) + ? (fs.readFileSync(logfile, "utf-8").split("\n").filter(Boolean).pop() ?? "") + : ""; } get order(): PageOrder { From 8cce52a1f84660b471c8913a44e14d47120c3d95 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Wed, 8 Jul 2026 15:57:31 -0400 Subject: [PATCH 04/21] Updates for auth and index tests. --- api/src/app.ts | 6 +- api/src/models/Config.ts | 126 +++++++++++++++++------------------ api/src/models/Job.ts | 1 + api/src/routes/auth.test.ts | 2 +- api/src/routes/index.test.ts | 2 +- api/src/server.ts | 2 +- 6 files changed, 70 insertions(+), 69 deletions(-) diff --git a/api/src/app.ts b/api/src/app.ts index bc3c8a7d5..cd752cf13 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -1,7 +1,7 @@ -import * as express from "express"; -import * as cookieParser from "cookie-parser"; +import express from "express"; +import cookieParser from "cookie-parser"; import * as path from "path"; -import * as logger from "morgan"; +import logger from "morgan"; import Config from "./models/Config"; const app = express(); diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index 05b9ea473..f0c1a95ff 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -10,9 +10,9 @@ interface ConfigRecord { class Config { private static instance: Config; - protected ini: Record; + protected ini: ConfigRecord; - constructor(ini: Record) { + constructor(ini: ConfigRecord) { this.ini = ini; } @@ -37,117 +37,117 @@ class Config { } get backendUrl(): string { - return this.ini["backend_url"] ?? "http://localhost:9000"; + return (this.ini["backend_url"] as string) ?? "http://localhost:9000"; } get clientUrl(): string { - return this.ini["client_url"]; + return this.ini["client_url"] as string; } get fedoraUsername(): string { - return this.ini["fedora_username"]; + return this.ini["fedora_username"] as string; } get fedoraPassword(): string { - return this.ini["fedora_password"]; + return this.ini["fedora_password"] as string; } get fedoraPidNameSpace(): string { - return this.ini["fedora_pid_namespace"]; + return this.ini["fedora_pid_namespace"] as string; } get ffmpegPath(): string { - return this.ini["ffmpeg_path"]; + return this.ini["ffmpeg_path"] as string; } get fitsCommand(): string { - return this.ini["fits_command"]; + return this.ini["fits_command"] as string; } get sessionKey(): string { - return this.ini["session_key"] ?? "vanilla hot cocoa"; + return (this.ini["session_key"] as string) ?? "vanilla hot cocoa"; } get tesseractPath(): string { - return this.ini["tesseract_path"]; + return this.ini["tesseract_path"] as string; } get tesseractAllowedChars(): string { - return this.ini["tesseract_allowed_characters"]; + return this.ini["tesseract_allowed_characters"] as string; } get vufindUrl(): string { - return this.ini["vufind_url"] ?? ""; + return (this.ini["vufind_url"] as string) ?? ""; } get pdfDirectory(): string { - return this.ini["pdf_directory"]; + return this.ini["pdf_directory"] as string; } get textcleanerPath(): string { - return this.ini["textcleaner_path"]; + return this.ini["textcleaner_path"] as string; } get textcleanerSwitches(): string { - return this.ini["textcleaner_switches"]; + return this.ini["textcleaner_switches"] as string; } get holdingArea(): string { - const holdingArea = this.ini["holding_area_path"]; + const holdingArea = this.ini["holding_area_path"] as string; return holdingArea.endsWith("/") ? holdingArea : holdingArea + "/"; } get ocrmypdfPath(): string { - return this.ini["ocrmypdf_path"]; + return this.ini["ocrmypdf_path"] as string; } get processedAreaPath(): string { - return this.ini["processed_area_path"]; + return this.ini["processed_area_path"] as string; } get restBaseUrl(): string { - return this.ini["base_url"]; + return this.ini["base_url"] as string; } get javaPath(): string { - return this.ini["java_path"] ?? "java"; + return (this.ini["java_path"] as string) ?? "java"; } - get tikaConfigFile(): string { - return this.ini["tika_config_file"] ?? null; + get tikaConfigFile(): string | null { + return (this.ini["tika_config_file"] as string) ?? null; } get tikaPath(): string { - return this.ini["tika_path"]; + return this.ini["tika_path"] as string; } get solrCore(): string { - return this.ini["solr_core"] ?? "biblio"; + return (this.ini["solr_core"] as string) ?? "biblio"; } get solrUrl(): string { - return this.ini["solr_url"] ?? "http://localhost:8983/solr"; + return (this.ini["solr_url"] as string) ?? "http://localhost:8983/solr"; } get solrDocumentCacheDir(): boolean | string { - return this.ini["solr_document_cache_dir"] ?? false; + return (this.ini["solr_document_cache_dir"] as string) ?? false; } get allowedOrigins(): string[] { - return this.ini["allowed_origins"] ?? []; + return (this.ini["allowed_origins"] as string[]) ?? []; } get pidNamespace(): string { - return this.ini["fedora_pid_namespace"] ?? "vudl"; + return (this.ini["fedora_pid_namespace"] as string) ?? "vudl"; } get initialPidValue(): number { - return parseInt(this.ini["fedora_initial_pid"] ?? "0"); + return parseInt((this.ini["fedora_initial_pid"] as string) ?? "0"); } get dataModels(): Record { return ( - this.ini["data_models"] ?? { + (this.ini["data_models"] as Record) ?? { Image: "vudl-system:ImageData", PDF: "vudl-system:PDFData", DOC: "vudl-system:DOCData", @@ -161,7 +161,7 @@ class Config { get collectionModels(): Record { return ( - this.ini["collection_models"] ?? { + (this.ini["collection_models"] as Record) ?? { List: "vudl-system:ListCollection", Resource: "vudl-system:ResourceCollection", Folder: "vudl-system:FolderCollection", @@ -170,27 +170,27 @@ class Config { } get institution(): string { - return this.ini["institution"] ?? "My University"; + return (this.ini["institution"] as string) ?? "My University"; } get collection(): string { - return this.ini["collection"] ?? "Digital Library"; + return (this.ini["collection"] as string) ?? "Digital Library"; } get topLevelPids(): Array { - return this.ini["top_level_pids"] ?? []; + return (this.ini["top_level_pids"] as string[]) ?? []; } get articlesToStrip(): Array { - return this.ini["articles_to_strip"] ?? []; + return (this.ini["articles_to_strip"] as string[]) ?? []; } get trashPid(): string | null { - return this.ini["trash_pid"] ?? null; + return (this.ini["trash_pid"] as string) ?? null; } get favoritePids(): Array { - const favorites = this.ini["favorite_pids"] ?? []; + const favorites = (this.ini["favorite_pids"] as string[]) ?? []; const trash = this.trashPid; if (trash && !favorites.includes(trash)) { favorites.push(trash); @@ -199,19 +199,19 @@ class Config { } get languageMap(): Record { - return this.ini["LanguageMap"] ?? {}; + return (this.ini["LanguageMap"] as Record) ?? {}; } get minimumValidYear(): number { - return parseInt(this.ini["minimum_valid_year"] ?? 1000); + return parseInt((this.ini["minimum_valid_year"] as string) ?? "1000"); } get models(): Record { - return this.ini["models"] || {}; + return (this.ini["models"] as unknown as Record) || {}; } get databaseSettings(): ConfigRecord { - return this.ini["Database"] ?? {}; + return (this.ini["Database"] as ConfigRecord) ?? {}; } get databaseClient(): string { @@ -223,7 +223,7 @@ class Config { } get authenticationSettings(): ConfigRecord { - return this.ini["Authentication"] ?? []; + return (this.ini["Authentication"] as ConfigRecord) ?? {}; } get authenticationStrategy(): string { @@ -263,88 +263,88 @@ class Config { } get licenses(): Record { - return this.ini["licenses"] ?? {}; + return (this.ini["licenses"] as unknown as Record) ?? {}; } get agentDefaults(): Record { - return this.ini?.["agent"]?.["defaults"] ?? {}; + return ((this.ini["agent"] as ConfigRecord)?.["defaults"] as Record) ?? {}; } get agentRoles(): Array { - return this.ini?.["agent"]?.["roles"] ?? []; + return ((this.ini["agent"] as ConfigRecord)?.["roles"] as string[]) ?? []; } get agentTypes(): Array { - return this.ini?.["agent"]?.["types"] ?? []; + return ((this.ini["agent"] as ConfigRecord)?.["types"] as string[]) ?? []; } get dublinCoreFields(): Record>> { - return this.ini?.["dublin_core"] ?? {}; + return (this.ini["dublin_core"] as Record>>) ?? {}; } get redisConnectionSettings(): Record { - return this.ini?.["queue"]?.["connection"] ?? {}; + return ((this.ini["queue"] as ConfigRecord)?.["connection"] as Record) ?? {}; } get redisDefaultQueueName(): string { - return this.ini?.["queue"]?.["defaultQueueName"] ?? "vudl"; + return ((this.ini["queue"] as ConfigRecord)?.["defaultQueueName"] as string) ?? "vudl"; } get redisQueueJobMap(): Record { - return this.ini?.["queue"]?.["jobMap"] ?? {}; + return ((this.ini["queue"] as ConfigRecord)?.["jobMap"] as Record) ?? {}; } get redisLockDuration(): number { - return parseInt(this.ini?.["queue"]?.["lockDuration"] ?? "30000"); + return parseInt(((this.ini["queue"] as ConfigRecord)?.["lockDuration"] as string) ?? "30000"); } get processMetadataDefaults(): Record { - return this.ini?.["process_metadata_defaults"] ?? {}; + return (this.ini["process_metadata_defaults"] as Record) ?? {}; } get toolPresets(): Array> { - return this.ini?.["tool_presets"] ?? []; + return (this.ini["tool_presets"] as unknown as Array>) ?? []; } get sharpOptions(): Record { - const pixelLimit = this.ini?.["sharp"]?.["limitInputPixels"] ?? "268402689"; + const pixelLimit = ((this.ini["sharp"] as ConfigRecord)?.["limitInputPixels"] as string) ?? "268402689"; return { limitInputPixels: parseInt(pixelLimit), }; } get max409Retries(): number { - return this.ini["max_409_retries"] ?? 3; + return (this.ini["max_409_retries"] as unknown as number) ?? 3; } get maxUploadSize(): number { - return this.ini?.["upload"]?.["sizeLimit"] ?? 200 * 1024 * 1024; + return ((this.ini["upload"] as ConfigRecord)?.["sizeLimit"] as unknown as number) ?? 200 * 1024 * 1024; } get notifyMethod(): string { - return this.ini?.["notify"]?.["method"] ?? "ntfy"; + return ((this.ini["notify"] as ConfigRecord)?.["method"] as string) ?? "ntfy"; } get ntfyConfig(): Record { return { - defaultChannel: this.ini?.["notify"]?.["ntfy_defaultChannel"] ?? "vudl-ntfy", + defaultChannel: ((this.ini["notify"] as ConfigRecord)?.["ntfy_defaultChannel"] as string) ?? "vudl-ntfy", }; } get indexerLockRetries(): number { - return parseInt(this.ini?.["indexer"]?.["lockRetries"] ?? 60); + return parseInt(((this.ini["indexer"] as ConfigRecord)?.["lockRetries"] as string) ?? "60"); } get indexerLockWaitMs(): number { - return parseInt(this.ini?.["indexer"]?.["lockWaitMs"] ?? 1000); + return parseInt(((this.ini["indexer"] as ConfigRecord)?.["lockWaitMs"] as string) ?? "1000"); } get indexerExceptionRetries(): number { - return parseInt(this.ini?.["indexer"]?.["exceptionRetries"] ?? 10); + return parseInt(((this.ini["indexer"] as ConfigRecord)?.["exceptionRetries"] as string) ?? "10"); } get indexerExceptionWaitMs(): number { - return parseInt(this.ini?.["indexer"]?.["exceptionWaitMs"] ?? 500); + return parseInt(((this.ini["indexer"] as ConfigRecord)?.["exceptionWaitMs"] as string) ?? "500"); } } diff --git a/api/src/models/Job.ts b/api/src/models/Job.ts index bc8103329..b2733cdd9 100644 --- a/api/src/models/Job.ts +++ b/api/src/models/Job.ts @@ -2,6 +2,7 @@ import { createWriteStream, openSync, closeSync, existsSync as fileExists, statS import PDFDocument = require("pdfkit"); declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace -- augmenting @types/pdfkit's ambient PDFKit namespace, not organizing our own code namespace PDFKit { interface PDFKitImage { width: number; diff --git a/api/src/routes/auth.test.ts b/api/src/routes/auth.test.ts index 6890ce9a7..e0a1172d4 100644 --- a/api/src/routes/auth.test.ts +++ b/api/src/routes/auth.test.ts @@ -1,5 +1,5 @@ import * as request from "supertest"; -import * as session from "express-session"; +import session from "express-session"; import { StatusCodes } from "http-status-codes"; import app from "../app"; import { getAuthRouter } from "./auth"; diff --git a/api/src/routes/index.test.ts b/api/src/routes/index.test.ts index 9b2ba0bf5..a848f2c0e 100644 --- a/api/src/routes/index.test.ts +++ b/api/src/routes/index.test.ts @@ -1,4 +1,4 @@ -import * as request from "supertest"; +import request from "supertest"; import { StatusCodes } from "http-status-codes"; import app from "../app"; import index from "./index"; diff --git a/api/src/server.ts b/api/src/server.ts index b9ccab462..e3618e448 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -4,7 +4,7 @@ import * as http from "http"; import * as passport from "passport"; -import * as session from "express-session"; +import session from "express-session"; import debug from "debug"; import app from "./app"; From dd8139d222c5907946c9449be98b8e4208703a82 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Thu, 9 Jul 2026 10:43:33 -0400 Subject: [PATCH 05/21] Changed typsescript version to 5 and implemented suggested changes to config.ts --- api/package-lock.json | 8 ++++---- api/package.json | 2 +- api/src/models/Config.ts | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index a06cd76b2..e9b6a7bf1 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -59,7 +59,7 @@ "prettier": "^3", "supertest": "^7", "ts-jest": "^29", - "typescript": "^6", + "typescript": "^5", "typescript-eslint": "^8.52" } }, @@ -10181,9 +10181,9 @@ } }, "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/api/package.json b/api/package.json index a11409c76..6e1f0166d 100644 --- a/api/package.json +++ b/api/package.json @@ -71,7 +71,7 @@ "prettier": "^3", "supertest": "^7", "ts-jest": "^29", - "typescript": "^6", + "typescript": "^5", "typescript-eslint": "^8.52" } } diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index f0c1a95ff..a83283399 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -37,7 +37,7 @@ class Config { } get backendUrl(): string { - return (this.ini["backend_url"] as string) ?? "http://localhost:9000"; + return (this.ini["backend_url"] ?? "http://localhost:9000") as string; } get clientUrl(): string { @@ -114,7 +114,7 @@ class Config { } get tikaConfigFile(): string | null { - return (this.ini["tika_config_file"] as string) ?? null; + return (this.ini["tika_config_file"] ?? null) as string | null; } get tikaPath(): string { @@ -207,7 +207,7 @@ class Config { } get models(): Record { - return (this.ini["models"] as unknown as Record) || {}; + return (this.ini["models"] as Record) || {}; } get databaseSettings(): ConfigRecord { @@ -267,7 +267,7 @@ class Config { } get agentDefaults(): Record { - return ((this.ini["agent"] as ConfigRecord)?.["defaults"] as Record) ?? {}; + return ((this.ini["agent"] as ConfigRecord | undefined)?.["defaults"] as Record) ?? {}; } get agentRoles(): Array { From efc42b1ca144bd9e31042a05d72ea60245198244 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Thu, 9 Jul 2026 11:01:27 -0400 Subject: [PATCH 06/21] 1 line fixes for messenger, edit, and auth tests --- api/src/routes/auth.test.ts | 2 +- api/src/routes/edit.test.ts | 2 +- api/src/routes/messenger.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/routes/auth.test.ts b/api/src/routes/auth.test.ts index e0a1172d4..31457428e 100644 --- a/api/src/routes/auth.test.ts +++ b/api/src/routes/auth.test.ts @@ -1,4 +1,4 @@ -import * as request from "supertest"; +import request from "supertest"; import session from "express-session"; import { StatusCodes } from "http-status-codes"; import app from "../app"; diff --git a/api/src/routes/edit.test.ts b/api/src/routes/edit.test.ts index a67617fdf..f8e28d30b 100644 --- a/api/src/routes/edit.test.ts +++ b/api/src/routes/edit.test.ts @@ -1,4 +1,4 @@ -import * as request from "supertest"; +import request from "supertest"; import { StatusCodes } from "http-status-codes"; import app from "../app"; import edit from "./edit"; diff --git a/api/src/routes/messenger.test.ts b/api/src/routes/messenger.test.ts index 819bec02e..1939b1fcb 100644 --- a/api/src/routes/messenger.test.ts +++ b/api/src/routes/messenger.test.ts @@ -1,4 +1,4 @@ -import * as request from "supertest"; +import request from "supertest"; import { StatusCodes } from "http-status-codes"; import app from "../app"; import messenger from "./messenger"; From 2a4bc1fe228195936fb8a08d4272eecefe70e7a6 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Thu, 9 Jul 2026 12:09:29 -0400 Subject: [PATCH 07/21] fix for src/services/JobQueue.test.ts, jest was not accounting for esModuleInterop setting in tsconfig --- api/src/services/JobQueue.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/src/services/JobQueue.test.ts b/api/src/services/JobQueue.test.ts index 5531bb9c9..28f737f99 100644 --- a/api/src/services/JobQueue.test.ts +++ b/api/src/services/JobQueue.test.ts @@ -5,19 +5,19 @@ const mockDerivative = { run: jest.fn(), }; jest.mock("../jobs/Derivative", () => { - return { default: jest.fn().mockImplementation(() => mockDerivative) }; + return { __esModule: true, default: jest.fn().mockImplementation(() => mockDerivative) }; }); jest.mock("../jobs/GeneratePdf", () => { - return { default: jest.fn() }; + return { __esModule: true, default: jest.fn() }; }); jest.mock("../jobs/Index", () => { - return { default: jest.fn() }; + return { __esModule: true, default: jest.fn() }; }); jest.mock("../jobs/Ingest", () => { - return { default: jest.fn() }; + return { __esModule: true, default: jest.fn() }; }); jest.mock("../jobs/Metadata", () => { - return { default: jest.fn() }; + return { __esModule: true, default: jest.fn() }; }); describe("JobQueue", () => { From 30a40d80f0fc61e3ea312362043b5370cddf5a98 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Mon, 13 Jul 2026 11:54:35 -0400 Subject: [PATCH 08/21] fixed Solr test --- api/src/services/Solr.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/services/Solr.test.ts b/api/src/services/Solr.test.ts index 261831655..5cd2ee3af 100644 --- a/api/src/services/Solr.test.ts +++ b/api/src/services/Solr.test.ts @@ -1,6 +1,6 @@ import Solr from "./Solr"; import SolrCache from "./SolrCache"; -import * as fs from "fs"; +import fs from "fs"; describe("Solr", () => { let solr; From e383a4d7457fd25bd817426b1ecfca9b1a523065 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Mon, 13 Jul 2026 12:17:29 -0400 Subject: [PATCH 09/21] fix for QueueManager.test.ts --- api/src/services/QueueManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/services/QueueManager.ts b/api/src/services/QueueManager.ts index 8e1f6e5d6..501a0e941 100644 --- a/api/src/services/QueueManager.ts +++ b/api/src/services/QueueManager.ts @@ -30,13 +30,13 @@ class QueueManager { } protected getQueue(queueName: string = ""): Queue { - return new Queue(queueName ?? this.config.redisDefaultQueueName, this.queueBaseOptions); + return new Queue(queueName || this.config.redisDefaultQueueName, this.queueBaseOptions); } public getWorker(callback: Processor, queueName: string = ""): Worker { const options: WorkerOptions = this.queueBaseOptions; options.lockDuration = this.config.redisLockDuration; - return new Worker(queueName ?? this.config.redisDefaultQueueName, callback, options); + return new Worker(queueName || this.config.redisDefaultQueueName, callback, options); } protected getQueueNameForJob(jobName: string): string { From ca88fb25caf20333bf37e12ff7a5d5ef005c7c48 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Mon, 13 Jul 2026 12:48:22 -0400 Subject: [PATCH 10/21] fix for SolrCache test --- api/src/services/SolrCache.test.ts | 2 +- api/src/services/SolrCache.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/services/SolrCache.test.ts b/api/src/services/SolrCache.test.ts index 89e465c47..46f80581c 100644 --- a/api/src/services/SolrCache.test.ts +++ b/api/src/services/SolrCache.test.ts @@ -1,5 +1,5 @@ import { SolrAddDoc, SolrCache } from "./SolrCache"; -import * as fs from "fs"; +import fs from "fs"; import glob = require("glob"); describe("SolrCache", () => { diff --git a/api/src/services/SolrCache.ts b/api/src/services/SolrCache.ts index 05eeef78c..2ecbbeca9 100644 --- a/api/src/services/SolrCache.ts +++ b/api/src/services/SolrCache.ts @@ -167,7 +167,7 @@ export class SolrCache { return; } document.push(nextObject.add.doc); - if (currentBatch.file === null) { + if (currentBatch.file === "") { currentBatch.file = file.replace(new RegExp("^" + this.cacheDir), targetDir); console.log(`Starting batch ${currentBatch.file}`); } From 895a9b08daf89c0734228c070947b9e13fd2a8bf Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Mon, 13 Jul 2026 13:13:14 -0400 Subject: [PATCH 11/21] updates to Config.ts --- api/src/models/Config.ts | 56 ++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index a83283399..2d379d96a 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -65,7 +65,7 @@ class Config { } get sessionKey(): string { - return (this.ini["session_key"] as string) ?? "vanilla hot cocoa"; + return (this.ini["session_key"] ?? "vanilla hot cocoa") as string; } get tesseractPath(): string { @@ -77,7 +77,7 @@ class Config { } get vufindUrl(): string { - return (this.ini["vufind_url"] as string) ?? ""; + return (this.ini["vufind_url"] ?? "") as string; } get pdfDirectory(): string { @@ -110,7 +110,7 @@ class Config { } get javaPath(): string { - return (this.ini["java_path"] as string) ?? "java"; + return (this.ini["java_path"] ?? "java") as string; } get tikaConfigFile(): string | null { @@ -122,15 +122,15 @@ class Config { } get solrCore(): string { - return (this.ini["solr_core"] as string) ?? "biblio"; + return (this.ini["solr_core"] ?? "biblio") as string; } get solrUrl(): string { - return (this.ini["solr_url"] as string) ?? "http://localhost:8983/solr"; + return (this.ini["solr_url"] ?? "http://localhost:8983/solr") as string; } get solrDocumentCacheDir(): boolean | string { - return (this.ini["solr_document_cache_dir"] as string) ?? false; + return (this.ini["solr_document_cache_dir"] ?? false) as string; } get allowedOrigins(): string[] { @@ -138,11 +138,11 @@ class Config { } get pidNamespace(): string { - return (this.ini["fedora_pid_namespace"] as string) ?? "vudl"; + return (this.ini["fedora_pid_namespace"] ?? "vudl") as string; } get initialPidValue(): number { - return parseInt((this.ini["fedora_initial_pid"] as string) ?? "0"); + return parseInt((this.ini["fedora_initial_pid"] ?? "0") as string); } get dataModels(): Record { @@ -170,11 +170,11 @@ class Config { } get institution(): string { - return (this.ini["institution"] as string) ?? "My University"; + return (this.ini["institution"] ?? "My University") as string; } get collection(): string { - return (this.ini["collection"] as string) ?? "Digital Library"; + return (this.ini["collection"] ?? "Digital Library") as string; } get topLevelPids(): Array { @@ -186,7 +186,7 @@ class Config { } get trashPid(): string | null { - return (this.ini["trash_pid"] as string) ?? null; + return (this.ini["trash_pid"] ?? null) as string; } get favoritePids(): Array { @@ -203,7 +203,7 @@ class Config { } get minimumValidYear(): number { - return parseInt((this.ini["minimum_valid_year"] as string) ?? "1000"); + return parseInt((this.ini["minimum_valid_year"] ?? "1000") as string); } get models(): Record { @@ -215,7 +215,7 @@ class Config { } get databaseClient(): string { - return (this.databaseSettings["client"] as string) ?? "sqlite3"; + return (this.databaseSettings["client"] ?? "sqlite3") as string; } get databaseConnectionSettings(): ConfigRecord { @@ -227,11 +227,11 @@ class Config { } get authenticationStrategy(): string { - return (this.authenticationSettings["strategy"] as string) ?? "local"; + return (this.authenticationSettings["strategy"] ?? "local") as string; } get authenticationHashAlgorithm(): string { - return (this.authenticationSettings["hash_algorithm"] as string) ?? "sha1"; + return (this.authenticationSettings["hash_algorithm"] ?? "sha1") as string; } get authenticationLegalUsernames(): Array { @@ -242,12 +242,12 @@ class Config { if (typeof this.authenticationSettings["require_passwords"] === "boolean") { return this.authenticationSettings["require_passwords"]; } - const stringValue = (this.authenticationSettings["require_passwords"] as string) ?? "true"; + const stringValue = (this.authenticationSettings["require_passwords"] ?? "true") as string; return stringValue.trim().toLowerCase() !== "false"; } get authenticationSalt(): string { - return (this.authenticationSettings["salt"] as string) ?? "VuDLSaltValue"; + return (this.authenticationSettings["salt"] ?? "VuDLSaltValue") as string; } get databaseInitialUsers(): Record { @@ -255,11 +255,11 @@ class Config { } get samlCertificate(): string { - return (this.authenticationSettings["saml_certificate"] as string) ?? ""; + return (this.authenticationSettings["saml_certificate"] ?? "") as string; } get samlEntryPoint(): string { - return (this.authenticationSettings["saml_entry_point"] as string) ?? ""; + return (this.authenticationSettings["saml_entry_point"] ?? "") as string; } get licenses(): Record { @@ -287,7 +287,7 @@ class Config { } get redisDefaultQueueName(): string { - return ((this.ini["queue"] as ConfigRecord)?.["defaultQueueName"] as string) ?? "vudl"; + return ((this.ini["queue"] as ConfigRecord)?.["defaultQueueName"] ?? "vudl") as string; } get redisQueueJobMap(): Record { @@ -295,7 +295,7 @@ class Config { } get redisLockDuration(): number { - return parseInt(((this.ini["queue"] as ConfigRecord)?.["lockDuration"] as string) ?? "30000"); + return parseInt(((this.ini["queue"] as ConfigRecord)?.["lockDuration"] ?? "30000") as string); } get processMetadataDefaults(): Record { @@ -307,7 +307,7 @@ class Config { } get sharpOptions(): Record { - const pixelLimit = ((this.ini["sharp"] as ConfigRecord)?.["limitInputPixels"] as string) ?? "268402689"; + const pixelLimit = ((this.ini["sharp"] as ConfigRecord)?.["limitInputPixels"] ?? "268402689") as string; return { limitInputPixels: parseInt(pixelLimit), }; @@ -322,29 +322,29 @@ class Config { } get notifyMethod(): string { - return ((this.ini["notify"] as ConfigRecord)?.["method"] as string) ?? "ntfy"; + return ((this.ini["notify"] as ConfigRecord)?.["method"] ?? "ntfy") as string; } get ntfyConfig(): Record { return { - defaultChannel: ((this.ini["notify"] as ConfigRecord)?.["ntfy_defaultChannel"] as string) ?? "vudl-ntfy", + defaultChannel: ((this.ini["notify"] as ConfigRecord)?.["ntfy_defaultChannel"] ?? "vudl-ntfy") as string, }; } get indexerLockRetries(): number { - return parseInt(((this.ini["indexer"] as ConfigRecord)?.["lockRetries"] as string) ?? "60"); + return parseInt(((this.ini["indexer"] as ConfigRecord)?.["lockRetries"] ?? "60") as string); } get indexerLockWaitMs(): number { - return parseInt(((this.ini["indexer"] as ConfigRecord)?.["lockWaitMs"] as string) ?? "1000"); + return parseInt(((this.ini["indexer"] as ConfigRecord)?.["lockWaitMs"] ?? "1000") as string); } get indexerExceptionRetries(): number { - return parseInt(((this.ini["indexer"] as ConfigRecord)?.["exceptionRetries"] as string) ?? "10"); + return parseInt(((this.ini["indexer"] as ConfigRecord)?.["exceptionRetries"] ?? "10") as string); } get indexerExceptionWaitMs(): number { - return parseInt(((this.ini["indexer"] as ConfigRecord)?.["exceptionWaitMs"] as string) ?? "500"); + return parseInt(((this.ini["indexer"] as ConfigRecord)?.["exceptionWaitMs"] ?? "500") as string); } } From 80511e0f855bf2a7d82d24cee1e9353559ffe270 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Mon, 13 Jul 2026 13:30:46 -0400 Subject: [PATCH 12/21] fix for server.ts, builds and passes all tests --- api/src/server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/server.ts b/api/src/server.ts index e3618e448..3567b51bb 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -2,8 +2,8 @@ * Module dependencies. */ -import * as http from "http"; -import * as passport from "passport"; +import http from "http"; +import passport from "passport"; import session from "express-session"; import debug from "debug"; From facdbdcff6a0234ddecc88c63af40cd46df34831 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Mon, 20 Jul 2026 14:48:07 -0400 Subject: [PATCH 13/21] updates for config.ts --- api/src/models/Config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index 2d379d96a..fbd1f4bb2 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -134,7 +134,7 @@ class Config { } get allowedOrigins(): string[] { - return (this.ini["allowed_origins"] as string[]) ?? []; + return (this.ini["allowed_origins"] ?? []) as string[]; } get pidNamespace(): string { @@ -314,7 +314,7 @@ class Config { } get max409Retries(): number { - return (this.ini["max_409_retries"] as unknown as number) ?? 3; + return (this.ini["max_409_retries"] ?? 3) as number; } get maxUploadSize(): number { From 6c7b343988779959d6281e9ecb028a1e993bf9fb Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Mon, 20 Jul 2026 15:45:32 -0400 Subject: [PATCH 14/21] Fixes for config.ts and jobs.ts --- api/src/models/Config.ts | 4 ++-- api/src/models/Job.ts | 14 -------------- api/src/types/pdfkit-augment.d.ts | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 16 deletions(-) create mode 100644 api/src/types/pdfkit-augment.d.ts diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index fbd1f4bb2..5955f69f4 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -2,7 +2,7 @@ import fs = require("fs"); import ini = require("ini"); import { FedoraModel, License } from "../services/FedoraCatalog"; -type ConfigValue = string | string[] | ConfigRecord; +type ConfigValue = string | string[] | ConfigRecord | ConfigRecord[]; interface ConfigRecord { [key: string]: ConfigValue; } @@ -303,7 +303,7 @@ class Config { } get toolPresets(): Array> { - return (this.ini["tool_presets"] as unknown as Array>) ?? []; + return (this.ini["tool_presets"] ?? []) as Array>; } get sharpOptions(): Record { diff --git a/api/src/models/Job.ts b/api/src/models/Job.ts index b2733cdd9..2f1b575db 100644 --- a/api/src/models/Job.ts +++ b/api/src/models/Job.ts @@ -1,20 +1,6 @@ import { createWriteStream, openSync, closeSync, existsSync as fileExists, statSync } from "fs"; import PDFDocument = require("pdfkit"); -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace -- augmenting @types/pdfkit's ambient PDFKit namespace, not organizing our own code - namespace PDFKit { - interface PDFKitImage { - width: number; - height: number; - } - interface PDFDocument { - openImage(src: string | Buffer): PDFKitImage; - image(src: PDFKitImage, x?: number, y?: number, options?: Mixins.ImageOption): this; - image(src: PDFKitImage, options?: Mixins.ImageOption): this; - } - } -} import path = require("path"); import Config from "./Config"; diff --git a/api/src/types/pdfkit-augment.d.ts b/api/src/types/pdfkit-augment.d.ts new file mode 100644 index 000000000..a25f4124a --- /dev/null +++ b/api/src/types/pdfkit-augment.d.ts @@ -0,0 +1,15 @@ +// Work around for a flaw in @types/pdfkit, which omits `openImage()` from the type definitions. +// even though pdfkit implements and exports it (lib/mixins/images.js). + +declare namespace PDFKit.Mixins { + interface OpenedImage { + width: number; + height: number; + } + + interface PDFImage { + openImage(src: string | Buffer): OpenedImage; + image(src: OpenedImage, x?: number, y?: number, options?: ImageOption): this; + image(src: OpenedImage, options?: ImageOption): this; + } +} From 03c004e5fcd89ed39e5e852509c955cab7e7d65c Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Wed, 22 Jul 2026 12:08:29 -0400 Subject: [PATCH 15/21] more tweaks for config.ts --- api/src/models/Config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index 5955f69f4..048261156 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -279,7 +279,7 @@ class Config { } get dublinCoreFields(): Record>> { - return (this.ini["dublin_core"] as Record>>) ?? {}; + return (this.ini["dublin_core"] ?? {}) as Record>>; } get redisConnectionSettings(): Record { @@ -299,7 +299,7 @@ class Config { } get processMetadataDefaults(): Record { - return (this.ini["process_metadata_defaults"] as Record) ?? {}; + return (this.ini["process_metadata_defaults"] ?? {}) as Record; } get toolPresets(): Array> { From e32f6d664a4008d40025073b21f587680455a4cd Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Wed, 22 Jul 2026 12:09:01 -0400 Subject: [PATCH 16/21] job.ts fix for the fix --- api/eslint.config.mjs | 5 +++-- api/src/models/Job.ts | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/eslint.config.mjs b/api/eslint.config.mjs index 6bc17acc6..8a7f28e07 100644 --- a/api/eslint.config.mjs +++ b/api/eslint.config.mjs @@ -20,8 +20,9 @@ export default [ languageOptions: { parser: tsParser, }, - rules: { - "@typescript-eslint/no-require-imports": "off" + rules: { + "@typescript-eslint/no-require-imports": "off", + "@typescript-eslint/triple-slash-reference": "off" }, } ]; diff --git a/api/src/models/Job.ts b/api/src/models/Job.ts index 2f1b575db..9f4df8287 100644 --- a/api/src/models/Job.ts +++ b/api/src/models/Job.ts @@ -1,3 +1,4 @@ +/// import { createWriteStream, openSync, closeSync, existsSync as fileExists, statSync } from "fs"; import PDFDocument = require("pdfkit"); From 6901bcec98a287c78e986452b8f926baf002d42e Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Wed, 29 Jul 2026 11:09:02 -0400 Subject: [PATCH 17/21] more tweaks to config.ts --- api/src/models/Config.ts | 53 +++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index 048261156..8bc42d6c5 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -130,7 +130,7 @@ class Config { } get solrDocumentCacheDir(): boolean | string { - return (this.ini["solr_document_cache_dir"] ?? false) as string; + return (this.ini["solr_document_cache_dir"] ?? false) as boolean | string; } get allowedOrigins(): string[] { @@ -147,7 +147,7 @@ class Config { get dataModels(): Record { return ( - (this.ini["data_models"] as Record) ?? { + (this.ini["data_models"] as Record | undefined) ?? { Image: "vudl-system:ImageData", PDF: "vudl-system:PDFData", DOC: "vudl-system:DOCData", @@ -161,7 +161,7 @@ class Config { get collectionModels(): Record { return ( - (this.ini["collection_models"] as Record) ?? { + (this.ini["collection_models"] as Record | undefined) ?? { List: "vudl-system:ListCollection", Resource: "vudl-system:ResourceCollection", Folder: "vudl-system:FolderCollection", @@ -178,19 +178,19 @@ class Config { } get topLevelPids(): Array { - return (this.ini["top_level_pids"] as string[]) ?? []; + return (this.ini["top_level_pids"] ?? []) as string[]; } get articlesToStrip(): Array { - return (this.ini["articles_to_strip"] as string[]) ?? []; + return (this.ini["articles_to_strip"] ?? []) as string[]; } get trashPid(): string | null { - return (this.ini["trash_pid"] ?? null) as string; + return (this.ini["trash_pid"] ?? null) as string | null; } get favoritePids(): Array { - const favorites = (this.ini["favorite_pids"] as string[]) ?? []; + const favorites = (this.ini["favorite_pids"] ?? []) as string[]; const trash = this.trashPid; if (trash && !favorites.includes(trash)) { favorites.push(trash); @@ -199,7 +199,7 @@ class Config { } get languageMap(): Record { - return (this.ini["LanguageMap"] as Record) ?? {}; + return (this.ini["LanguageMap"] ?? {}) as Record; } get minimumValidYear(): number { @@ -207,11 +207,11 @@ class Config { } get models(): Record { - return (this.ini["models"] as Record) || {}; + return (this.ini["models"] || {}) as Record; } get databaseSettings(): ConfigRecord { - return (this.ini["Database"] as ConfigRecord) ?? {}; + return (this.ini["Database"] ?? {}) as ConfigRecord; } get databaseClient(): string { @@ -223,7 +223,7 @@ class Config { } get authenticationSettings(): ConfigRecord { - return (this.ini["Authentication"] as ConfigRecord) ?? {}; + return (this.ini["Authentication"] ?? {}) as ConfigRecord; } get authenticationStrategy(): string { @@ -267,15 +267,15 @@ class Config { } get agentDefaults(): Record { - return ((this.ini["agent"] as ConfigRecord | undefined)?.["defaults"] as Record) ?? {}; + return ((this.ini["agent"] as ConfigRecord | undefined)?.["defaults"] ?? {}) as Record; } get agentRoles(): Array { - return ((this.ini["agent"] as ConfigRecord)?.["roles"] as string[]) ?? []; + return ((this.ini["agent"] as ConfigRecord)?.["roles"] ?? []) as string[]; } get agentTypes(): Array { - return ((this.ini["agent"] as ConfigRecord)?.["types"] as string[]) ?? []; + return ((this.ini["agent"] as ConfigRecord)?.["types"] ?? []) as string[]; } get dublinCoreFields(): Record>> { @@ -287,15 +287,15 @@ class Config { } get redisDefaultQueueName(): string { - return ((this.ini["queue"] as ConfigRecord)?.["defaultQueueName"] ?? "vudl") as string; + return ((this.ini["queue"] as ConfigRecord | undefined)?.["defaultQueueName"] ?? "vudl") as string; } get redisQueueJobMap(): Record { - return ((this.ini["queue"] as ConfigRecord)?.["jobMap"] as Record) ?? {}; + return ((this.ini["queue"] as ConfigRecord | undefined)?.["jobMap"] ?? {}) as Record; } get redisLockDuration(): number { - return parseInt(((this.ini["queue"] as ConfigRecord)?.["lockDuration"] ?? "30000") as string); + return parseInt(((this.ini["queue"] as ConfigRecord | undefined)?.["lockDuration"] ?? "30000") as string); } get processMetadataDefaults(): Record { @@ -307,7 +307,8 @@ class Config { } get sharpOptions(): Record { - const pixelLimit = ((this.ini["sharp"] as ConfigRecord)?.["limitInputPixels"] ?? "268402689") as string; + const pixelLimit = ((this.ini["sharp"] as ConfigRecord | undefined)?.["limitInputPixels"] ?? + "268402689") as string; return { limitInputPixels: parseInt(pixelLimit), }; @@ -318,33 +319,35 @@ class Config { } get maxUploadSize(): number { - return ((this.ini["upload"] as ConfigRecord)?.["sizeLimit"] as unknown as number) ?? 200 * 1024 * 1024; + // Default to 200MB (1024 * 1024 * 200 = 209715200) + return parseInt(((this.ini["upload"] as ConfigRecord | undefined)?.["sizeLimit"] ?? "209715200") as string); } get notifyMethod(): string { - return ((this.ini["notify"] as ConfigRecord)?.["method"] ?? "ntfy") as string; + return ((this.ini["notify"] as ConfigRecord | undefined)?.["method"] ?? "ntfy") as string; } get ntfyConfig(): Record { return { - defaultChannel: ((this.ini["notify"] as ConfigRecord)?.["ntfy_defaultChannel"] ?? "vudl-ntfy") as string, + defaultChannel: ((this.ini["notify"] as ConfigRecord | undefined)?.["ntfy_defaultChannel"] ?? + "vudl-ntfy") as string, }; } get indexerLockRetries(): number { - return parseInt(((this.ini["indexer"] as ConfigRecord)?.["lockRetries"] ?? "60") as string); + return parseInt(((this.ini["indexer"] as ConfigRecord | undefined)?.["lockRetries"] ?? "60") as string); } get indexerLockWaitMs(): number { - return parseInt(((this.ini["indexer"] as ConfigRecord)?.["lockWaitMs"] ?? "1000") as string); + return parseInt(((this.ini["indexer"] as ConfigRecord | undefined)?.["lockWaitMs"] ?? "1000") as string); } get indexerExceptionRetries(): number { - return parseInt(((this.ini["indexer"] as ConfigRecord)?.["exceptionRetries"] ?? "10") as string); + return parseInt(((this.ini["indexer"] as ConfigRecord | undefined)?.["exceptionRetries"] ?? "10") as string); } get indexerExceptionWaitMs(): number { - return parseInt(((this.ini["indexer"] as ConfigRecord)?.["exceptionWaitMs"] ?? "500") as string); + return parseInt(((this.ini["indexer"] as ConfigRecord | undefined)?.["exceptionWaitMs"] ?? "500") as string); } } From 2a1e38781322ade8a622c696780e06541113c359 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Wed, 29 Jul 2026 14:49:35 -0400 Subject: [PATCH 18/21] last (?) changes for config.ts. --- api/src/models/Config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index 8bc42d6c5..152e61547 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -263,7 +263,7 @@ class Config { } get licenses(): Record { - return (this.ini["licenses"] as unknown as Record) ?? {}; + return (this.ini["licenses"] ?? {}) as unknown as Record; } get agentDefaults(): Record { @@ -283,7 +283,7 @@ class Config { } get redisConnectionSettings(): Record { - return ((this.ini["queue"] as ConfigRecord)?.["connection"] as Record) ?? {}; + return ((this.ini["queue"] as ConfigRecord | undefined)?.["connection"] ?? {}) as Record; } get redisDefaultQueueName(): string { From f1f53634110a5ec64401248cf21d57c5bdce31dc Mon Sep 17 00:00:00 2001 From: Demian Katz Date: Wed, 29 Jul 2026 15:17:47 -0400 Subject: [PATCH 19/21] Eliminate unknown. --- api/src/models/Config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index 152e61547..a1cd568b2 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -2,7 +2,7 @@ import fs = require("fs"); import ini = require("ini"); import { FedoraModel, License } from "../services/FedoraCatalog"; -type ConfigValue = string | string[] | ConfigRecord | ConfigRecord[]; +type ConfigValue = string | string[] | ConfigRecord | ConfigRecord[] | License; interface ConfigRecord { [key: string]: ConfigValue; } @@ -263,7 +263,7 @@ class Config { } get licenses(): Record { - return (this.ini["licenses"] ?? {}) as unknown as Record; + return (this.ini["licenses"] ?? {}) as Record; } get agentDefaults(): Record { From caaf7b994e3c0bd0ef575d9690e0e0b201730d58 Mon Sep 17 00:00:00 2001 From: Jason Benson Date: Thu, 30 Jul 2026 13:37:20 -0400 Subject: [PATCH 20/21] config.ts tweaks. --- api/src/models/Config.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index a1cd568b2..15299de9f 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -271,11 +271,11 @@ class Config { } get agentRoles(): Array { - return ((this.ini["agent"] as ConfigRecord)?.["roles"] ?? []) as string[]; + return ((this.ini["agent"] as ConfigRecord | undefined)?.["roles"] ?? []) as string[]; } get agentTypes(): Array { - return ((this.ini["agent"] as ConfigRecord)?.["types"] ?? []) as string[]; + return ((this.ini["agent"] as ConfigRecord | undefined)?.["types"] ?? []) as string[]; } get dublinCoreFields(): Record>> { @@ -315,7 +315,8 @@ class Config { } get max409Retries(): number { - return (this.ini["max_409_retries"] ?? 3) as number; + // return (this.ini["max_409_retries"] ?? 3) as number; + return parseInt((this.ini["max_409_retries"] ?? "3") as string); } get maxUploadSize(): number { From 150d182aa3f0bb8cbaea901573420b24c7b03de4 Mon Sep 17 00:00:00 2001 From: Jason-Benson Date: Thu, 30 Jul 2026 16:00:57 -0400 Subject: [PATCH 21/21] Update api/src/models/Config.ts Co-authored-by: Demian Katz --- api/src/models/Config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index 15299de9f..438981467 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -315,7 +315,6 @@ class Config { } get max409Retries(): number { - // return (this.ini["max_409_retries"] ?? 3) as number; return parseInt((this.ini["max_409_retries"] ?? "3") as string); }