update dependencies and devDependencies, address lint issues
authorJustin Wind <justin.wind+git@gmail.com>
Mon, 13 May 2024 16:26:31 +0000 (09:26 -0700)
committerJustin Wind <justin.wind+git@gmail.com>
Mon, 13 May 2024 18:06:16 +0000 (11:06 -0700)
34 files changed:
eslint.config.js
package-lock.json
package.json
src/chores.js
src/common.js
src/db/abstract.js
src/db/schema-version-helper.js
src/db/sqlite/index.js
src/logger/data-sanitizers.js
src/manager.js
src/service.js
src/template/admin-html.js
src/template/admin-maintenance-html.js
src/template/admin-ticket-html.js
src/template/authorization-error-html.js
src/template/authorization-request-html.js
src/template/root-html.js
src/template/template-helper.js
test/src/chores.js
test/src/db/abstract.js
test/src/db/factory.js
test/src/db/integration.js
test/src/db/postgres.js
test/src/db/schema-version-helper.js
test/src/db/sqlite.js
test/src/logger.js
test/src/manager.js
test/src/service.js
test/src/template/admin-html.js
test/src/template/admin-ticket-html.js
test/src/template/authorization-error-html.js
test/src/template/authorization-request-html.js
test/stub-db.js
test/stub-logger.js

index e02ca72a25c7177260f3a913a201a430a13395c4..fcda56fad771e74e97647ffc52041dcb7b732e28 100644 (file)
@@ -1,111 +1,7 @@
 'use strict';
-const globals = require('globals');
-const js = require('@eslint/js');
-const node = require('eslint-plugin-n');
-const security = require('eslint-plugin-security');
-const sonarjs = require('eslint-plugin-sonarjs');
 
-const { FlatCompat } = require('@eslint/eslintrc');
-const compat = new FlatCompat();
+const squeepConfig = require('@squeep/eslint-config');
 
 module.exports = [
-  js.configs.recommended,
-  ...compat.config(node.configs.recommended),
-  security.configs.recommended,
-  ...compat.config(sonarjs.configs.recommended),
-  {
-    files: [ '**/*.js' ],
-    plugins: {
-      node,
-      security,
-      sonarjs,
-    },
-    languageOptions: {
-      ecmaVersion: 2023,
-      sourceType: 'script',
-    },
-    rules: {
-      'array-element-newline': [
-        'error',
-        'consistent',
-      ],
-      'arrow-parens': [
-        'error',
-        'always',
-      ],
-      'arrow-spacing': [
-        'error',
-        {
-          'after': true,
-          'before': true,
-        },
-      ],
-      'block-scoped-var': 'error',
-      'block-spacing': 'error',
-      'brace-style': 'error',
-      'callback-return': 'error',
-      'camelcase': 'error',
-      'class-methods-use-this': 'error',
-      'comma-dangle': [
-        'error',
-        'always-multiline',
-      ],
-      'comma-spacing': [
-        'error',
-        {
-          'after': true,
-          'before': false,
-        },
-      ],
-      'comma-style': [
-        'error',
-        'last',
-      ],
-      'indent': [
-        'warn',
-        2,
-        {
-          'SwitchCase': 1,
-        },
-      ],
-      'sonarjs/cognitive-complexity': 'warn',
-      'sonarjs/no-duplicate-string': 'warn',
-      'keyword-spacing': 'error',
-      'linebreak-style': [
-        'error',
-        'unix',
-      ],
-      'no-unused-vars': [
-        'error', {
-          'varsIgnorePattern': '^_',
-        },
-      ],
-      'object-curly-spacing': [
-        'error',
-        'always',
-      ],
-      'prefer-const': 'error',
-      'quotes': [
-        'error',
-        'single',
-      ],
-      'semi': [
-        'error',
-        'always',
-      ],
-      'strict': 'error',
-      'vars-on-top': 'error',
-    },
-  },
-  {
-    files: ['test/**'],
-    languageOptions: {
-      globals: {
-        ...globals.mocha,
-      },
-    },
-    rules: {
-      "n/no-unpublished-require": "off",
-    },
-  },
+  ...squeepConfig,
 ];
index b28df973c4def59ea8364b8835ad4c3436060995..74f36d6e86dfb903ccecb4b653e911b781aa73dd 100644 (file)
       "license": "ISC",
       "dependencies": {
         "@squeep/amqp-helper": "git+https://git.squeep.com/squeep-amqp-helper#v1.0.0",
-        "@squeep/api-dingus": "^2.1.0",
-        "@squeep/authentication-module": "git+https://git.squeep.com/squeep-authentication-module/#v1.4.0",
+        "@squeep/api-dingus": "^2",
+        "@squeep/authentication-module": "git+https://git.squeep.com/squeep-authentication-module/#v1.5.0",
         "@squeep/chores": "git+https://git.squeep.com/squeep-chores/#v1.0.1",
-        "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.6.0",
-        "@squeep/indieauth-helper": "^1.4.1",
-        "@squeep/logger-json-console": "^3.0.2",
-        "@squeep/mystery-box": "^2.0.2",
-        "@squeep/resource-authentication-module": "git+https://git.squeep.com/squeep-resource-authentication-module#v1.0.1",
-        "@squeep/roman": "^1.0.1",
-        "@squeep/web-linking": "^1.0.8",
-        "better-sqlite3": "^9.4.5",
-        "pg-promise": "^11.6.0",
-        "uuid": "^9.0.1"
+        "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.6.1",
+        "@squeep/indieauth-helper": "^1.4",
+        "@squeep/logger-json-console": "^3",
+        "@squeep/mystery-box": "^2",
+        "@squeep/roman": "^1",
+        "@squeep/web-linking": "^1",
+        "better-sqlite3": "^9",
+        "pg-promise": "^11",
+        "uuid": "^9"
       },
       "devDependencies": {
+        "@squeep/eslint-config": "^1",
         "@squeep/test-helper": "git+https://git.squeep.com/squeep-test-helper#v1.0.1",
-        "eslint": "^8.57.0",
-        "eslint-plugin-n": "^16.6.2",
-        "eslint-plugin-promise": "^6.1.1",
-        "eslint-plugin-security": "^2.1.1",
-        "eslint-plugin-sonarjs": "^0.25.1",
-        "html-validate": "^8.18.1",
-        "mocha": "^10.4.0",
-        "mocha-steps": "^1.3.0",
-        "nyc": "^15.1.0",
-        "pre-commit": "^1.2.2",
-        "sinon": "^17.0.1"
+        "eslint": "^9",
+        "html-validate": "^8",
+        "mocha": "^10",
+        "mocha-steps": "^1",
+        "nyc": "^15",
+        "pre-commit": "^1",
+        "sinon": "^17"
       },
       "engines": {
-        "node": "^14 >=14.18 || >=15.7"
-      }
-    },
-    "node_modules/@aashutoshrathi/word-wrap": {
-      "version": "1.2.6",
-      "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
-      "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
+        "node": ">=16.9"
       }
     },
     "node_modules/@acuminous/bitsyntax": {
       }
     },
     "node_modules/@babel/core": {
-      "version": "7.24.4",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz",
-      "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==",
+      "version": "7.24.5",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz",
+      "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==",
       "dev": true,
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
         "@babel/code-frame": "^7.24.2",
-        "@babel/generator": "^7.24.4",
+        "@babel/generator": "^7.24.5",
         "@babel/helper-compilation-targets": "^7.23.6",
-        "@babel/helper-module-transforms": "^7.23.3",
-        "@babel/helpers": "^7.24.4",
-        "@babel/parser": "^7.24.4",
+        "@babel/helper-module-transforms": "^7.24.5",
+        "@babel/helpers": "^7.24.5",
+        "@babel/parser": "^7.24.5",
         "@babel/template": "^7.24.0",
-        "@babel/traverse": "^7.24.1",
-        "@babel/types": "^7.24.0",
+        "@babel/traverse": "^7.24.5",
+        "@babel/types": "^7.24.5",
         "convert-source-map": "^2.0.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
       }
     },
     "node_modules/@babel/generator": {
-      "version": "7.24.4",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz",
-      "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==",
+      "version": "7.24.5",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz",
+      "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.24.0",
+        "@babel/types": "^7.24.5",
         "@jridgewell/gen-mapping": "^0.3.5",
         "@jridgewell/trace-mapping": "^0.3.25",
         "jsesc": "^2.5.1"
       }
     },
     "node_modules/@babel/helper-module-transforms": {
-      "version": "7.23.3",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
-      "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
+      "version": "7.24.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz",
+      "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==",
       "dev": true,
       "dependencies": {
         "@babel/helper-environment-visitor": "^7.22.20",
-        "@babel/helper-module-imports": "^7.22.15",
-        "@babel/helper-simple-access": "^7.22.5",
-        "@babel/helper-split-export-declaration": "^7.22.6",
-        "@babel/helper-validator-identifier": "^7.22.20"
+        "@babel/helper-module-imports": "^7.24.3",
+        "@babel/helper-simple-access": "^7.24.5",
+        "@babel/helper-split-export-declaration": "^7.24.5",
+        "@babel/helper-validator-identifier": "^7.24.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-simple-access": {
-      "version": "7.22.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
-      "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
+      "version": "7.24.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz",
+      "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.22.5"
+        "@babel/types": "^7.24.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-split-export-declaration": {
-      "version": "7.22.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
-      "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
+      "version": "7.24.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz",
+      "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.22.5"
+        "@babel/types": "^7.24.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-validator-identifier": {
-      "version": "7.22.20",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
-      "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+      "version": "7.24.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
+      "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.24.4",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz",
-      "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==",
+      "version": "7.24.5",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz",
+      "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==",
       "dev": true,
       "dependencies": {
         "@babel/template": "^7.24.0",
-        "@babel/traverse": "^7.24.1",
-        "@babel/types": "^7.24.0"
+        "@babel/traverse": "^7.24.5",
+        "@babel/types": "^7.24.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/highlight": {
-      "version": "7.24.2",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz",
-      "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==",
+      "version": "7.24.5",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz",
+      "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-validator-identifier": "^7.22.20",
+        "@babel/helper-validator-identifier": "^7.24.5",
         "chalk": "^2.4.2",
         "js-tokens": "^4.0.0",
         "picocolors": "^1.0.0"
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.24.4",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz",
-      "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==",
+      "version": "7.24.5",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
+      "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
       "dev": true,
       "bin": {
         "parser": "bin/babel-parser.js"
       }
     },
     "node_modules/@babel/traverse": {
-      "version": "7.24.1",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz",
-      "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==",
+      "version": "7.24.5",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz",
+      "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==",
       "dev": true,
       "dependencies": {
-        "@babel/code-frame": "^7.24.1",
-        "@babel/generator": "^7.24.1",
+        "@babel/code-frame": "^7.24.2",
+        "@babel/generator": "^7.24.5",
         "@babel/helper-environment-visitor": "^7.22.20",
         "@babel/helper-function-name": "^7.23.0",
         "@babel/helper-hoist-variables": "^7.22.5",
-        "@babel/helper-split-export-declaration": "^7.22.6",
-        "@babel/parser": "^7.24.1",
-        "@babel/types": "^7.24.0",
+        "@babel/helper-split-export-declaration": "^7.24.5",
+        "@babel/parser": "^7.24.5",
+        "@babel/types": "^7.24.5",
         "debug": "^4.3.1",
         "globals": "^11.1.0"
       },
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.24.0",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
-      "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
+      "version": "7.24.5",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
+      "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-string-parser": "^7.23.4",
-        "@babel/helper-validator-identifier": "^7.22.20",
+        "@babel/helper-string-parser": "^7.24.1",
+        "@babel/helper-validator-identifier": "^7.24.5",
         "to-fast-properties": "^2.0.0"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@es-joy/jsdoccomment": {
+      "version": "0.43.0",
+      "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.0.tgz",
+      "integrity": "sha512-Q1CnsQrytI3TlCB1IVWXWeqUIPGVEKGaE7IbVdt13Nq/3i0JESAkQQERrfiQkmlpijl+++qyqPgaS31Bvc1jRQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/eslint": "^8.56.5",
+        "@types/estree": "^1.0.5",
+        "@typescript-eslint/types": "^7.2.0",
+        "comment-parser": "1.4.1",
+        "esquery": "^1.5.0",
+        "jsdoc-type-pratt-parser": "~4.0.0"
+      },
+      "engines": {
+        "node": ">=16"
+      }
+    },
     "node_modules/@eslint-community/eslint-utils": {
       "version": "4.4.0",
       "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
         "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
       }
     },
+    "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
     "node_modules/@eslint-community/regexpp": {
       "version": "4.10.0",
       "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
       }
     },
     "node_modules/@eslint/eslintrc": {
-      "version": "2.1.4",
-      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
-      "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.0.2.tgz",
+      "integrity": "sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==",
       "dev": true,
       "dependencies": {
         "ajv": "^6.12.4",
         "debug": "^4.3.2",
-        "espree": "^9.6.0",
-        "globals": "^13.19.0",
+        "espree": "^10.0.1",
+        "globals": "^14.0.0",
         "ignore": "^5.2.0",
         "import-fresh": "^3.2.1",
         "js-yaml": "^4.1.0",
         "strip-json-comments": "^3.1.1"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "url": "https://opencollective.com/eslint"
       }
     },
+    "node_modules/@eslint/eslintrc/node_modules/globals": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+      "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/@eslint/js": {
-      "version": "8.57.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
-      "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+      "version": "9.2.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.2.0.tgz",
+      "integrity": "sha512-ESiIudvhoYni+MdsI8oD7skpprZ89qKocwRM2KEvhhBJ9nl5MRh7BXU5GTod7Mdygq+AUl+QzId6iWJKR/wABA==",
       "dev": true,
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       }
     },
     "node_modules/@html-validate/stylish": {
       }
     },
     "node_modules/@humanwhocodes/config-array": {
-      "version": "0.11.14",
-      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
-      "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+      "version": "0.13.0",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+      "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
       "dev": true,
       "dependencies": {
-        "@humanwhocodes/object-schema": "^2.0.2",
+        "@humanwhocodes/object-schema": "^2.0.3",
         "debug": "^4.3.1",
         "minimatch": "^3.0.5"
       },
       "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
       "dev": true
     },
+    "node_modules/@humanwhocodes/retry": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.2.4.tgz",
+      "integrity": "sha512-Ttl/jHpxfS3st5sxwICYfk4pOH0WrLI1SpW283GgQL7sCWU7EHIOhX4b4fkIxr3tkfzwg8+FNojtzsIEE7Ecgg==",
+      "dev": true,
+      "engines": {
+        "node": ">=18.18"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/nzakas"
+      }
+    },
     "node_modules/@isaacs/cliui": {
       "version": "8.0.2",
       "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
         "node-pre-gyp": "bin/node-pre-gyp"
       }
     },
+    "node_modules/@mapbox/node-pre-gyp/node_modules/detect-libc": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+      "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+      "optional": true,
+      "bin": {
+        "detect-libc": "bin/detect-libc.js"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
       }
     },
     "node_modules/@sindresorhus/is": {
-      "version": "5.6.0",
-      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
-      "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==",
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-6.3.0.tgz",
+      "integrity": "sha512-bOSPck7aIJjASXIg1qvXSIjXhVBpIEKdl2Wxg4pVqoTRPL8wWExKBrnGIh6CEnhkFQHfc36k7APhO3uXV4g5xg==",
       "engines": {
-        "node": ">=14.16"
+        "node": ">=16"
       },
       "funding": {
         "url": "https://github.com/sindresorhus/is?sponsor=1"
     "node_modules/@squeep/amqp-helper": {
       "version": "1.0.0",
       "resolved": "git+https://git.squeep.com/squeep-amqp-helper#174280d3f44ba13dac0b26d42d968189a4f4fa93",
-      "license": "ISC",
       "dependencies": {
         "amqplib": "^0.10.3"
       },
       }
     },
     "node_modules/@squeep/api-dingus": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@squeep/api-dingus/-/api-dingus-2.1.0.tgz",
-      "integrity": "sha512-SCLPHbSHTz5en5XO8IMyTLr6R+0jIDpL3dSxS3e4XLC3521LzorNywGteMdnZKhwXwahjf4XngxNzmO0kFp6Kw==",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@squeep/api-dingus/-/api-dingus-2.1.1.tgz",
+      "integrity": "sha512-iae4nc0mZ2sBR0iHIOu/loqaXSFmsxGdFOVjE0wfzGY4QzvHqLeVgM8VnAczFtCOM8bOqe8RtcvVkN7pDM7TWw==",
       "dependencies": {
-        "@squeep/log-helper": "^1.0.0",
+        "@squeep/log-helper": "^1",
         "mime-db": "^1.52.0",
         "uuid": "^9.0.1"
       },
       "engines": {
-        "node": ">=14"
+        "node": ">=14.13.1"
       }
     },
     "node_modules/@squeep/authentication-module": {
-      "version": "1.4.0",
-      "resolved": "git+https://git.squeep.com/squeep-authentication-module/#9c604adfcde56e35767e3eba70890308ec2d3110",
-      "license": "ISC",
+      "version": "1.5.0",
+      "resolved": "git+https://git.squeep.com/squeep-authentication-module/#5ea2ffe571a74618eef073c58c5fef06e1cf06a7",
       "dependencies": {
-        "@squeep/api-dingus": "^2.1.0",
-        "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.6.0",
-        "@squeep/indieauth-helper": "^1.4.1",
-        "@squeep/mystery-box": "^2.0.2",
-        "@squeep/totp": "^1.1.4"
+        "@squeep/api-dingus": "^2",
+        "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.6.1",
+        "@squeep/indieauth-helper": "^1",
+        "@squeep/mystery-box": "^2",
+        "@squeep/totp": "^1",
+        "uuid": "^9"
       },
       "engines": {
-        "node": "^18"
+        "node": ">=18"
       },
       "optionalDependencies": {
         "argon2": "^0.40.1",
     "node_modules/@squeep/chores": {
       "version": "1.0.1",
       "resolved": "git+https://git.squeep.com/squeep-chores/#a77e8814cbba0ad751e249850d1f7b144da6446b",
-      "license": "ISC",
       "dependencies": {
         "@squeep/log-helper": "^1.0.0"
       }
     },
+    "node_modules/@squeep/eslint-config": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@squeep/eslint-config/-/eslint-config-1.0.0.tgz",
+      "integrity": "sha512-kGuzqWqQekblecm4egJvY/2Wa0wxEDUlFPxwZ4gu8Eglmf9m/2rs2rr85DvtTxt43fvNcZ7IDuspTOv+Ei9BSg==",
+      "dev": true,
+      "dependencies": {
+        "@eslint/js": "^9",
+        "eslint-plugin-jsdoc": "^48",
+        "eslint-plugin-n": "^17",
+        "eslint-plugin-security": "^3",
+        "eslint-plugin-sonarjs": "^1",
+        "globals": "^15"
+      },
+      "engines": {
+        "node": ">=20"
+      },
+      "peerDependencies": {
+        "eslint": ">= 9"
+      }
+    },
     "node_modules/@squeep/html-template-helper": {
-      "version": "1.6.0",
-      "resolved": "git+https://git.squeep.com/squeep-html-template-helper#2d0ba72a2ea35f45c1ab1ac81fce3d0cbe7db419",
-      "license": "ISC",
+      "version": "1.6.1",
+      "resolved": "git+https://git.squeep.com/squeep-html-template-helper#93d1b030d6b3c6ea93c36a46f4940181a1acaca0",
       "dependencies": {
-        "@squeep/lazy-property": "^1.1.2"
+        "@squeep/lazy-property": "^1"
       },
       "engines": {
-        "node": ">=14"
+        "node": ">=14.13.1"
       }
     },
     "node_modules/@squeep/indieauth-helper": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/@squeep/indieauth-helper/-/indieauth-helper-1.4.1.tgz",
-      "integrity": "sha512-x/yqrjrbp0vTdvIW8e9CKsr5+YwmDp/x4nK4H5LInt07rk4nthxv7e7qIgy97OFtvFnoNee+N9gyi3TzIIiGoQ==",
+      "version": "1.4.2",
+      "resolved": "https://registry.npmjs.org/@squeep/indieauth-helper/-/indieauth-helper-1.4.2.tgz",
+      "integrity": "sha512-PKOr2Mx8e1RwdJ6ab22gWAlbzktDNORIt4xzGZ0iXbvbTdqNLdpAKBHi8Il0vYgZU0HAO3utNqMX5grSfKaDrw==",
       "dependencies": {
-        "@squeep/log-helper": "^1.0.0",
-        "@squeep/web-linking": "^1.0.8",
-        "got": "^13.0.0",
-        "iconv": "^3.0.1",
-        "ip-address": "^9.0.5",
-        "microformats-parser": "^2.0.2"
+        "@squeep/log-helper": "^1",
+        "@squeep/web-linking": "^1",
+        "got": "^14",
+        "iconv": "^3",
+        "ip-address": "^9",
+        "microformats-parser": "^2"
       },
       "engines": {
-        "node": "^14 >=14.18 || >=15.7"
+        "node": ">=20"
       }
     },
     "node_modules/@squeep/lazy-property": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/@squeep/lazy-property/-/lazy-property-1.1.2.tgz",
-      "integrity": "sha512-wRdR4IOqWXoDMArx0HPo5MtM2Wk5wemAULbZ6PabVw1ylSQekkzKfoAUuupxsKuzjcRPjZvbpGDv+i04hBMnQw==",
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@squeep/lazy-property/-/lazy-property-1.1.3.tgz",
+      "integrity": "sha512-GzQ/bSE6MZ6YeRjLMKK18I0ajvOnhrgacZs6EpLERXPPlQZWkOcmjCcjyqY71lqd5MzsPbSwzsj6Uonpqea6nw==",
       "engines": {
         "node": ">=14"
       }
     },
     "node_modules/@squeep/log-helper": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/@squeep/log-helper/-/log-helper-1.0.0.tgz",
-      "integrity": "sha512-i61ECZLWQI2rhkXj9pDzH1Md5ICghL9zvh5QFVo0BTayuSrdS9SWkJ6gV1qWki/Xz6SuE0y0y145NyHlvOuVaw==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@squeep/log-helper/-/log-helper-1.0.1.tgz",
+      "integrity": "sha512-T+QTxSNoZ9wzyyK1jS8ac9puOikRv14MfkE3uXSBOp70T8eEqCjSwfE+VHtOb0riU4FhYpDsBNSMIskK4UDRZA==",
       "engines": {
-        "node": ">=14"
+        "node": ">=14.13.1"
       }
     },
     "node_modules/@squeep/logger-json-console": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/@squeep/logger-json-console/-/logger-json-console-3.0.2.tgz",
-      "integrity": "sha512-Qz2QMwhyyRB5sZFB/S6eUt7TnmKCPB6oUqa8SW2gGGOLTcvf+0nbFgqqzlFwtUEwF3KwCmMnOt7lwq5PLrm24Q==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@squeep/logger-json-console/-/logger-json-console-3.0.3.tgz",
+      "integrity": "sha512-W8BDrV0fHKABbFXOedJKd8hmIgKOtTCM0lLGmaGsDfV8+OAdEg4kdS0F8GI7PLF9mOzp/z0uRv+CCyv5tS81kA==",
       "engines": {
         "node": ">=17"
       }
     },
     "node_modules/@squeep/mystery-box": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/@squeep/mystery-box/-/mystery-box-2.0.2.tgz",
-      "integrity": "sha512-YoVx9F/ZFOdgPrt5ey3Vg+ttK4nsnfeSjzVDBOCB1L5q2H1V2wZ4DV0/l7mkqPgHHojGeJqncGs/KhB6Lu916g==",
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/@squeep/mystery-box/-/mystery-box-2.0.3.tgz",
+      "integrity": "sha512-OFPVVyYrxZU/8iwJGBRpyh+z9AyEqryWyh8p45LtaHw62hwOb8k2J8An4jFmYU0fiMRR+b3xR94qt0/V+ansyA==",
       "engines": {
         "node": "^14 >=14.18.0 || >=15.7.0"
       }
     },
-    "node_modules/@squeep/resource-authentication-module": {
-      "version": "1.0.1",
-      "resolved": "git+https://git.squeep.com/squeep-resource-authentication-module#0dce6b122b90dc9d2fd3b7191cdef4d41e256ae3",
-      "license": "ISC",
-      "dependencies": {
-        "@squeep/api-dingus": "v2.0.0",
-        "uuid": "^9.0.0"
-      },
-      "engines": {
-        "node": "^14 >=14.18 || >=15.7"
-      }
-    },
-    "node_modules/@squeep/resource-authentication-module/node_modules/@squeep/api-dingus": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/@squeep/api-dingus/-/api-dingus-2.0.0.tgz",
-      "integrity": "sha512-HKz/yB1KNmEcHB92KIvrNvwMph5fSdJBrxKSgERYfyQkLFl2vSwDV+IlFvi68DYmMBP3lWKzQcTXWBMYlW3c0g==",
-      "dependencies": {
-        "mime-db": "^1.52.0",
-        "uuid": "^9.0.0"
-      },
-      "engines": {
-        "node": ">=14"
-      }
-    },
     "node_modules/@squeep/roman": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/@squeep/roman/-/roman-1.0.1.tgz",
       "version": "1.0.1",
       "resolved": "git+https://git.squeep.com/squeep-test-helper#cc0f69b40de9ae3342f1b7a1784d37769e7f1e84",
       "dev": true,
-      "license": "ISC",
       "dependencies": {
         "eslint": "^8.53.0",
         "eslint-plugin-node": "^11.1.0",
         "sinon": "^17.0.1"
       }
     },
+    "node_modules/@squeep/test-helper/node_modules/@eslint/eslintrc": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+      "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+      "dev": true,
+      "dependencies": {
+        "ajv": "^6.12.4",
+        "debug": "^4.3.2",
+        "espree": "^9.6.0",
+        "globals": "^13.19.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.0",
+        "minimatch": "^3.1.2",
+        "strip-json-comments": "^3.1.1"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@squeep/test-helper/node_modules/@eslint/js": {
+      "version": "8.57.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+      "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      }
+    },
+    "node_modules/@squeep/test-helper/node_modules/@humanwhocodes/config-array": {
+      "version": "0.11.14",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
+      "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+      "dev": true,
+      "dependencies": {
+        "@humanwhocodes/object-schema": "^2.0.2",
+        "debug": "^4.3.1",
+        "minimatch": "^3.0.5"
+      },
+      "engines": {
+        "node": ">=10.10.0"
+      }
+    },
+    "node_modules/@squeep/test-helper/node_modules/eslint": {
+      "version": "8.57.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
+      "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
+      "dev": true,
+      "dependencies": {
+        "@eslint-community/eslint-utils": "^4.2.0",
+        "@eslint-community/regexpp": "^4.6.1",
+        "@eslint/eslintrc": "^2.1.4",
+        "@eslint/js": "8.57.0",
+        "@humanwhocodes/config-array": "^0.11.14",
+        "@humanwhocodes/module-importer": "^1.0.1",
+        "@nodelib/fs.walk": "^1.2.8",
+        "@ungap/structured-clone": "^1.2.0",
+        "ajv": "^6.12.4",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.2",
+        "debug": "^4.3.2",
+        "doctrine": "^3.0.0",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^7.2.2",
+        "eslint-visitor-keys": "^3.4.3",
+        "espree": "^9.6.1",
+        "esquery": "^1.4.2",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^6.0.1",
+        "find-up": "^5.0.0",
+        "glob-parent": "^6.0.2",
+        "globals": "^13.19.0",
+        "graphemer": "^1.4.0",
+        "ignore": "^5.2.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "is-path-inside": "^3.0.3",
+        "js-yaml": "^4.1.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.4.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.2",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.3",
+        "strip-ansi": "^6.0.1",
+        "text-table": "^0.2.0"
+      },
+      "bin": {
+        "eslint": "bin/eslint.js"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
     "node_modules/@squeep/test-helper/node_modules/eslint-plugin-security": {
       "version": "1.7.1",
       "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.7.1.tgz",
         "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
       }
     },
+    "node_modules/@squeep/test-helper/node_modules/eslint-scope": {
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+      "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+      "dev": true,
+      "dependencies": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^5.2.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@squeep/test-helper/node_modules/eslint-visitor-keys": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@squeep/test-helper/node_modules/espree": {
+      "version": "9.6.1",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+      "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+      "dev": true,
+      "dependencies": {
+        "acorn": "^8.9.0",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^3.4.1"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
+    "node_modules/@squeep/test-helper/node_modules/file-entry-cache": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+      "dev": true,
+      "dependencies": {
+        "flat-cache": "^3.0.4"
+      },
+      "engines": {
+        "node": "^10.12.0 || >=12.0.0"
+      }
+    },
+    "node_modules/@squeep/test-helper/node_modules/flat-cache": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+      "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+      "dev": true,
+      "dependencies": {
+        "flatted": "^3.2.9",
+        "keyv": "^4.5.3",
+        "rimraf": "^3.0.2"
+      },
+      "engines": {
+        "node": "^10.12.0 || >=12.0.0"
+      }
+    },
+    "node_modules/@squeep/test-helper/node_modules/globals": {
+      "version": "13.24.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+      "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+      "dev": true,
+      "dependencies": {
+        "type-fest": "^0.20.2"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@squeep/test-helper/node_modules/type-fest": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/@squeep/totp": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/@squeep/totp/-/totp-1.1.4.tgz",
-      "integrity": "sha512-cMoicNB5xIDMdcOtTfkzWZ0eQCepatTsFoWXtQ8Ja4FfvAA3ZWwIMfKV4K7zbx1MjYGF/Ufikxa6CaPS6yd5mw==",
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/@squeep/totp/-/totp-1.1.5.tgz",
+      "integrity": "sha512-keqggH2NrHs8hqzyov31zIA4XTLUxwXBn+VfUFlCdzZY2omoWbgm4742Ht8j3W48FLtIX1q4Zrm1ncObi9RfMA==",
       "dependencies": {
         "base32.js": "^0.1.0",
         "qrcode-svg": "^1.1.0"
       }
     },
     "node_modules/@squeep/web-linking": {
-      "version": "1.0.8",
-      "resolved": "https://registry.npmjs.org/@squeep/web-linking/-/web-linking-1.0.8.tgz",
-      "integrity": "sha512-+0nCl/IXY8RHBWNr5mJMkU+U62in9xYDigIMkMKBp0bWnah/gjXv3NxAa4+LkxCMZU0STt/uBjuVM7SpvuQHSg==",
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/@squeep/web-linking/-/web-linking-1.0.9.tgz",
+      "integrity": "sha512-NHI2sWiL7PduRBYhM54MqG40xnI867uRyjKDWBCudsel4DmLy3Jh1CIvYORbPt8ScvvL0PV7kCsmowPdufAOkA==",
       "engines": {
         "node": ">=14.0"
       }
         "node": ">=14.16"
       }
     },
+    "node_modules/@types/eslint": {
+      "version": "8.56.10",
+      "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz",
+      "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==",
+      "dev": true,
+      "dependencies": {
+        "@types/estree": "*",
+        "@types/json-schema": "*"
+      }
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+      "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+      "dev": true
+    },
     "node_modules/@types/http-cache-semantics": {
       "version": "4.0.4",
       "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
       "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA=="
     },
+    "node_modules/@types/json-schema": {
+      "version": "7.0.15",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+      "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+      "dev": true
+    },
+    "node_modules/@typescript-eslint/types": {
+      "version": "7.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz",
+      "integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==",
+      "dev": true,
+      "engines": {
+        "node": "^18.18.0 || >=20.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
     "node_modules/@ungap/structured-clone": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
       }
     },
     "node_modules/amqplib": {
-      "version": "0.10.3",
-      "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz",
-      "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==",
+      "version": "0.10.4",
+      "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.4.tgz",
+      "integrity": "sha512-DMZ4eCEjAVdX1II2TfIUpJhfKAuoCeDIo/YyETbfAqehHTXxxs7WOOd+N1Xxr4cKhx12y23zk8/os98FxlZHrw==",
       "dependencies": {
         "@acuminous/bitsyntax": "^0.1.2",
         "buffer-more-ints": "~1.0.0",
       "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==",
       "dev": true
     },
+    "node_modules/are-docs-informative": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz",
+      "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==",
+      "dev": true,
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/are-we-there-yet": {
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
       ]
     },
     "node_modules/better-sqlite3": {
-      "version": "9.4.5",
-      "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.4.5.tgz",
-      "integrity": "sha512-uFVyoyZR9BNcjSca+cp3MWCv6upAv+tbMC4SWM51NIMhoQOm4tjIkyxFO/ZsYdGAF61WJBgdzyJcz4OokJi0gQ==",
+      "version": "9.6.0",
+      "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz",
+      "integrity": "sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==",
       "hasInstallScript": true,
       "dependencies": {
         "bindings": "^1.5.0",
       "dev": true,
       "engines": {
         "node": ">=6"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/builtins": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
-      "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
-      "dev": true,
-      "dependencies": {
-        "semver": "^7.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/cacheable-lookup": {
         "node": ">=14.16"
       }
     },
+    "node_modules/cacheable-request/node_modules/get-stream": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+      "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/caching-transform": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz",
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001606",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001606.tgz",
-      "integrity": "sha512-LPbwnW4vfpJId225pwjZJOgX1m9sGfbw/RKJvw/t0QhYOOaTXHvkjVGFGPpvwEzufrjvTlsULnVTxdy4/6cqkg==",
+      "version": "1.0.30001617",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz",
+      "integrity": "sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==",
       "dev": true,
       "funding": [
         {
       }
     },
     "node_modules/chownr": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
-      "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
-      "optional": true,
-      "engines": {
-        "node": ">=10"
-      }
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
     },
     "node_modules/clean-stack": {
       "version": "2.2.0",
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
       "devOptional": true
     },
+    "node_modules/comment-parser": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz",
+      "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 12.0.0"
+      }
+    },
     "node_modules/commondir": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
       "optional": true
     },
     "node_modules/detect-libc": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
-      "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
-      "optional": true,
-      "bin": {
-        "detect-libc": "bin/detect-libc.js"
-      },
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
+      "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
       "engines": {
-        "node": ">=0.10"
+        "node": ">=8"
       }
     },
     "node_modules/diff": {
       "dev": true
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.729",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.729.tgz",
-      "integrity": "sha512-bx7+5Saea/qu14kmPTDHQxkp2UnziG3iajUQu3BxFvCOnpAJdDbMV4rSl+EqFDkkpNNVUFlR1kDfpL59xfy1HA==",
+      "version": "1.4.763",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.763.tgz",
+      "integrity": "sha512-k4J8NrtJ9QrvHLRo8Q18OncqBCB7tIUyqxRcJnlonQ0ioHKYB988GcDFF3ZePmnb8eHEopDs/wPHR/iGAFgoUQ==",
       "dev": true
     },
     "node_modules/emoji-regex": {
         "once": "^1.4.0"
       }
     },
+    "node_modules/enhanced-resolve": {
+      "version": "5.16.1",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz",
+      "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==",
+      "dev": true,
+      "dependencies": {
+        "graceful-fs": "^4.2.4",
+        "tapable": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
     "node_modules/entities": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
       }
     },
     "node_modules/eslint": {
-      "version": "8.57.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
-      "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
+      "version": "9.2.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.2.0.tgz",
+      "integrity": "sha512-0n/I88vZpCOzO+PQpt0lbsqmn9AsnsJAQseIqhZFI8ibQT0U1AkEKRxA3EVMos0BoHSXDQvCXY25TUjB5tr8Og==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
-        "@eslint/eslintrc": "^2.1.4",
-        "@eslint/js": "8.57.0",
-        "@humanwhocodes/config-array": "^0.11.14",
+        "@eslint/eslintrc": "^3.0.2",
+        "@eslint/js": "9.2.0",
+        "@humanwhocodes/config-array": "^0.13.0",
         "@humanwhocodes/module-importer": "^1.0.1",
+        "@humanwhocodes/retry": "^0.2.3",
         "@nodelib/fs.walk": "^1.2.8",
-        "@ungap/structured-clone": "^1.2.0",
         "ajv": "^6.12.4",
         "chalk": "^4.0.0",
         "cross-spawn": "^7.0.2",
         "debug": "^4.3.2",
-        "doctrine": "^3.0.0",
         "escape-string-regexp": "^4.0.0",
-        "eslint-scope": "^7.2.2",
-        "eslint-visitor-keys": "^3.4.3",
-        "espree": "^9.6.1",
+        "eslint-scope": "^8.0.1",
+        "eslint-visitor-keys": "^4.0.0",
+        "espree": "^10.0.1",
         "esquery": "^1.4.2",
         "esutils": "^2.0.2",
         "fast-deep-equal": "^3.1.3",
-        "file-entry-cache": "^6.0.1",
+        "file-entry-cache": "^8.0.0",
         "find-up": "^5.0.0",
         "glob-parent": "^6.0.2",
-        "globals": "^13.19.0",
-        "graphemer": "^1.4.0",
         "ignore": "^5.2.0",
         "imurmurhash": "^0.1.4",
         "is-glob": "^4.0.0",
         "is-path-inside": "^3.0.3",
-        "js-yaml": "^4.1.0",
         "json-stable-stringify-without-jsonify": "^1.0.1",
         "levn": "^0.4.1",
         "lodash.merge": "^4.6.2",
         "eslint": "bin/eslint.js"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "url": "https://opencollective.com/eslint"
         "eslint": ">=8"
       }
     },
+    "node_modules/eslint-plugin-jsdoc": {
+      "version": "48.2.4",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.2.4.tgz",
+      "integrity": "sha512-3ebvVgCJFy06gpmuS2ynz13uh9iFSzZ1C1dDkgcSAqVVg82zlORKMk2fvjq708pAO6bwfs5YLttknFEbaoDiGw==",
+      "dev": true,
+      "dependencies": {
+        "@es-joy/jsdoccomment": "~0.43.0",
+        "are-docs-informative": "^0.0.2",
+        "comment-parser": "1.4.1",
+        "debug": "^4.3.4",
+        "escape-string-regexp": "^4.0.0",
+        "esquery": "^1.5.0",
+        "is-builtin-module": "^3.2.1",
+        "semver": "^7.6.0",
+        "spdx-expression-parse": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0"
+      }
+    },
     "node_modules/eslint-plugin-n": {
-      "version": "16.6.2",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.6.2.tgz",
-      "integrity": "sha512-6TyDmZ1HXoFQXnhCTUjVFULReoBPOAjpuiKELMkeP40yffI/1ZRO+d9ug/VC6fqISo2WkuIBk3cvuRPALaWlOQ==",
+      "version": "17.6.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.6.0.tgz",
+      "integrity": "sha512-Y73o88ROwbCtVCCmZjYlYcPYkOG7mIzxxVK1XFRSa2epbKWtAPsmYpAD0pqxg/ZwlcWxMDceQPKHYQi4VIHz7w==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "builtins": "^5.0.1",
+        "enhanced-resolve": "^5.15.0",
         "eslint-plugin-es-x": "^7.5.0",
         "get-tsconfig": "^4.7.0",
-        "globals": "^13.24.0",
+        "globals": "^15.0.0",
         "ignore": "^5.2.4",
-        "is-builtin-module": "^3.2.1",
-        "is-core-module": "^2.12.1",
-        "minimatch": "^3.1.2",
-        "resolve": "^1.22.2",
+        "minimatch": "^9.0.0",
         "semver": "^7.5.3"
       },
       "engines": {
-        "node": ">=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
-        "url": "https://github.com/sponsors/mysticatea"
+        "url": "https://opencollective.com/eslint"
       },
       "peerDependencies": {
-        "eslint": ">=7.0.0"
+        "eslint": ">=8.23.0"
+      }
+    },
+    "node_modules/eslint-plugin-n/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dev": true,
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-n/node_modules/minimatch": {
+      "version": "9.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
+      "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
+      "dev": true,
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
       }
     },
     "node_modules/eslint-plugin-node": {
         "semver": "bin/semver.js"
       }
     },
-    "node_modules/eslint-plugin-promise": {
-      "version": "6.1.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz",
-      "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==",
-      "dev": true,
-      "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-      },
-      "peerDependencies": {
-        "eslint": "^7.0.0 || ^8.0.0"
-      }
-    },
     "node_modules/eslint-plugin-security": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-2.1.1.tgz",
-      "integrity": "sha512-7cspIGj7WTfR3EhaILzAPcfCo5R9FbeWvbgsPYWivSurTBKW88VQxtP3c4aWMG9Hz/GfJlJVdXEJ3c8LqS+u2w==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-3.0.0.tgz",
+      "integrity": "sha512-2Ij7PkmXIF2cKwoVkEgemwoXbOnxg5UfdhdcpNxZwJxC/10dbsdhHISrTyJ/n8DUkt3yiN6P1ywEgcMGjIwHIw==",
       "dev": true,
       "dependencies": {
         "safe-regex": "^2.1.1"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       }
     },
     "node_modules/eslint-plugin-sonarjs": {
-      "version": "0.25.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-0.25.1.tgz",
-      "integrity": "sha512-5IOKvj/GMBNqjxBdItfotfRHo7w48496GOu1hxdeXuD0mB1JBlDCViiLHETDTfA8pDAVSBimBEQoetRXYceQEw==",
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-1.0.3.tgz",
+      "integrity": "sha512-6s41HLPYPyDrp+5+7Db5yFYbod6h9pC7yx+xfcNwHRcLe1EZwbbQT/tdOAkR7ekVUkNGEvN3GmYakIoQUX7dEg==",
       "dev": true,
       "engines": {
         "node": ">=16"
       },
       "peerDependencies": {
-        "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
+        "eslint": "^8.0.0 || ^9.0.0"
       }
     },
     "node_modules/eslint-scope": {
-      "version": "7.2.2",
-      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
-      "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz",
+      "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==",
       "dev": true,
       "dependencies": {
         "esrecurse": "^4.3.0",
         "estraverse": "^5.2.0"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "url": "https://opencollective.com/eslint"
       }
     },
     "node_modules/eslint-visitor-keys": {
-      "version": "3.4.3",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
-      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
+      "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
       "dev": true,
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "url": "https://opencollective.com/eslint"
       }
     },
     "node_modules/espree": {
-      "version": "9.6.1",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
-      "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz",
+      "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==",
       "dev": true,
       "dependencies": {
-        "acorn": "^8.9.0",
+        "acorn": "^8.11.3",
         "acorn-jsx": "^5.3.2",
-        "eslint-visitor-keys": "^3.4.1"
+        "eslint-visitor-keys": "^4.0.0"
       },
       "engines": {
-        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
       "funding": {
         "url": "https://opencollective.com/eslint"
       }
     },
     "node_modules/file-entry-cache": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
-      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+      "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
       "dev": true,
       "dependencies": {
-        "flat-cache": "^3.0.4"
+        "flat-cache": "^4.0.0"
       },
       "engines": {
-        "node": "^10.12.0 || >=12.0.0"
+        "node": ">=16.0.0"
       }
     },
     "node_modules/file-uri-to-path": {
       }
     },
     "node_modules/flat-cache": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
-      "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+      "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
       "dev": true,
       "dependencies": {
         "flatted": "^3.2.9",
-        "keyv": "^4.5.3",
-        "rimraf": "^3.0.2"
+        "keyv": "^4.5.4"
       },
       "engines": {
-        "node": "^10.12.0 || >=12.0.0"
+        "node": ">=16"
       }
     },
     "node_modules/flatted": {
       }
     },
     "node_modules/form-data-encoder": {
-      "version": "2.1.4",
-      "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
-      "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==",
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz",
+      "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==",
       "engines": {
-        "node": ">= 14.17"
+        "node": ">= 18"
       }
     },
     "node_modules/fromentries": {
       }
     },
     "node_modules/get-stream": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
-      "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+      "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
       "engines": {
-        "node": ">=10"
+        "node": ">=16"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/get-tsconfig": {
-      "version": "4.7.3",
-      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz",
-      "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==",
+      "version": "4.7.5",
+      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz",
+      "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==",
       "dev": true,
       "dependencies": {
         "resolve-pkg-maps": "^1.0.0"
       "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
     },
     "node_modules/glob": {
-      "version": "10.3.12",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
-      "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
+      "version": "10.3.15",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz",
+      "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==",
       "dev": true,
       "dependencies": {
         "foreground-child": "^3.1.0",
         "jackspeak": "^2.3.6",
         "minimatch": "^9.0.1",
         "minipass": "^7.0.4",
-        "path-scurry": "^1.10.2"
+        "path-scurry": "^1.11.0"
       },
       "bin": {
         "glob": "dist/esm/bin.mjs"
       },
       "engines": {
-        "node": ">=16 || 14 >=14.17"
+        "node": ">=16 || 14 >=14.18"
       },
       "funding": {
         "url": "https://github.com/sponsors/isaacs"
       }
     },
     "node_modules/globals": {
-      "version": "13.24.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
-      "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+      "version": "15.2.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-15.2.0.tgz",
+      "integrity": "sha512-FQ5YwCHZM3nCmtb5FzEWwdUc9K5d3V/w9mzcz8iGD1gC/aOTHc6PouYu0kkKipNJqHAT7m51sqzQjEjIP+cK0A==",
       "dev": true,
-      "dependencies": {
-        "type-fest": "^0.20.2"
-      },
       "engines": {
-        "node": ">=8"
+        "node": ">=18"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/got": {
-      "version": "13.0.0",
-      "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz",
-      "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==",
+      "version": "14.2.1",
+      "resolved": "https://registry.npmjs.org/got/-/got-14.2.1.tgz",
+      "integrity": "sha512-KOaPMremmsvx6l9BLC04LYE6ZFW4x7e4HkTe3LwBmtuYYQwpeS4XKqzhubTIkaQ1Nr+eXxeori0zuwupXMovBQ==",
       "dependencies": {
-        "@sindresorhus/is": "^5.2.0",
+        "@sindresorhus/is": "^6.1.0",
         "@szmarczak/http-timer": "^5.0.1",
         "cacheable-lookup": "^7.0.0",
-        "cacheable-request": "^10.2.8",
+        "cacheable-request": "^10.2.14",
         "decompress-response": "^6.0.0",
-        "form-data-encoder": "^2.1.2",
-        "get-stream": "^6.0.1",
-        "http2-wrapper": "^2.1.10",
+        "form-data-encoder": "^4.0.2",
+        "get-stream": "^8.0.1",
+        "http2-wrapper": "^2.2.1",
         "lowercase-keys": "^3.0.0",
-        "p-cancelable": "^3.0.0",
+        "p-cancelable": "^4.0.1",
         "responselike": "^3.0.0"
       },
       "engines": {
-        "node": ">=16"
+        "node": ">=20"
       },
       "funding": {
         "url": "https://github.com/sindresorhus/got?sponsor=1"
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/hasha/node_modules/type-fest": {
-      "version": "0.8.1",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
-      "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/hasown": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
       "dev": true
     },
     "node_modules/html-validate": {
-      "version": "8.18.1",
-      "resolved": "https://registry.npmjs.org/html-validate/-/html-validate-8.18.1.tgz",
-      "integrity": "sha512-6NYRciFBQhVZH29fwDQxofPil0qm7MMSEDzDpZWu2U23Fnmv1WTuompP7fbzHYj6+CIJ4T/Q4hyWRCe0CCrsMg==",
+      "version": "8.18.2",
+      "resolved": "https://registry.npmjs.org/html-validate/-/html-validate-8.18.2.tgz",
+      "integrity": "sha512-X/ahjDnTZe1VjoPz+w7zGe5I5/os8PLQ+c5C9ak6etV0nNK9gPbwbGaP9BQoSQuQx0mszw5khDjP1ffnXx+1OA==",
       "dev": true,
       "funding": [
         {
           "url": "https://github.com/sponsors/html-validate"
         }
       ],
+      "workspaces": [
+        "docs",
+        "tests/vitest"
+      ],
       "dependencies": {
         "@babel/code-frame": "^7.10.0",
         "@html-validate/stylish": "^4.1.0",
       }
     },
     "node_modules/html-validate/node_modules/ajv": {
-      "version": "8.12.0",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
-      "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+      "version": "8.13.0",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz",
+      "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
       "dev": true,
       "dependencies": {
-        "fast-deep-equal": "^3.1.1",
+        "fast-deep-equal": "^3.1.3",
         "json-schema-traverse": "^1.0.0",
         "require-from-string": "^2.0.2",
-        "uri-js": "^4.2.2"
+        "uri-js": "^4.4.1"
       },
       "funding": {
         "type": "github",
       "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
       "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
     },
+    "node_modules/jsdoc-type-pratt-parser": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz",
+      "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
     "node_modules/jsesc": {
       "version": "2.5.2",
       "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
       }
     },
     "node_modules/minipass": {
-      "version": "7.0.4",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
-      "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz",
+      "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==",
       "dev": true,
       "engines": {
         "node": ">=16 || 14 >=14.17"
       }
     },
     "node_modules/node-abi": {
-      "version": "3.57.0",
-      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.57.0.tgz",
-      "integrity": "sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==",
+      "version": "3.62.0",
+      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz",
+      "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==",
       "dependencies": {
         "semver": "^7.3.5"
       },
       }
     },
     "node_modules/node-gyp-build": {
-      "version": "4.8.0",
-      "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
-      "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==",
+      "version": "4.8.1",
+      "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
+      "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
       "optional": true,
       "bin": {
         "node-gyp-build": "bin.js",
       }
     },
     "node_modules/optionator": {
-      "version": "0.9.3",
-      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
-      "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+      "version": "0.9.4",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+      "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
       "dev": true,
       "dependencies": {
-        "@aashutoshrathi/word-wrap": "^1.2.3",
         "deep-is": "^0.1.3",
         "fast-levenshtein": "^2.0.6",
         "levn": "^0.4.1",
         "prelude-ls": "^1.2.1",
-        "type-check": "^0.4.0"
+        "type-check": "^0.4.0",
+        "word-wrap": "^1.2.5"
       },
       "engines": {
         "node": ">= 0.8.0"
       }
     },
     "node_modules/p-cancelable": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
-      "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz",
+      "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==",
       "engines": {
-        "node": ">=12.20"
+        "node": ">=14.16"
       }
     },
     "node_modules/p-limit": {
       "dev": true
     },
     "node_modules/path-scurry": {
-      "version": "1.10.2",
-      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
-      "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
+      "version": "1.11.1",
+      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+      "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
       "dev": true,
       "dependencies": {
         "lru-cache": "^10.2.0",
         "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
       },
       "engines": {
-        "node": ">=16 || 14 >=14.17"
+        "node": ">=16 || 14 >=14.18"
       },
       "funding": {
         "url": "https://github.com/sponsors/isaacs"
       }
     },
     "node_modules/path-scurry/node_modules/lru-cache": {
-      "version": "10.2.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
-      "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
+      "version": "10.2.2",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
+      "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
       "dev": true,
       "engines": {
         "node": "14 || >=16.14"
       }
     },
     "node_modules/path-to-regexp": {
-      "version": "6.2.1",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
-      "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==",
+      "version": "6.2.2",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz",
+      "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==",
       "dev": true
     },
     "node_modules/pg": {
         "node": ">=10"
       }
     },
-    "node_modules/prebuild-install/node_modules/detect-libc": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
-      "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/prelude-ls": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
       }
     },
     "node_modules/semver": {
-      "version": "7.6.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
-      "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
+      "version": "7.6.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
+      "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
       "bin": {
         "semver": "bin/semver.js"
       },
         "node": ">=10"
       }
     },
-    "node_modules/semver/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/semver/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
-    },
     "node_modules/serialize-javascript": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
       }
     },
     "node_modules/sinon": {
-      "version": "17.0.1",
-      "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz",
-      "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==",
+      "version": "17.0.2",
+      "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.2.tgz",
+      "integrity": "sha512-uihLiaB9FhzesElPDFZA7hDcNABzsVHwr3YfmM9sBllVwab3l0ltGlRV1XhpNfIacNDLGD1QRZNLs5nU5+hTuA==",
       "dev": true,
       "dependencies": {
-        "@sinonjs/commons": "^3.0.0",
+        "@sinonjs/commons": "^3.0.1",
         "@sinonjs/fake-timers": "^11.2.2",
         "@sinonjs/samsam": "^8.0.0",
-        "diff": "^5.1.0",
-        "nise": "^5.1.5",
-        "supports-color": "^7.2.0"
+        "diff": "^5.2.0",
+        "nise": "^5.1.9",
+        "supports-color": "^7"
       },
       "funding": {
         "type": "opencollective",
       "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
       "dev": true
     },
+    "node_modules/spdx-exceptions": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
+      "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
+      "dev": true
+    },
+    "node_modules/spdx-expression-parse": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz",
+      "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==",
+      "dev": true,
+      "dependencies": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "node_modules/spdx-license-ids": {
+      "version": "3.0.17",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
+      "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==",
+      "dev": true
+    },
     "node_modules/spex": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/spex/-/spex-3.3.0.tgz",
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/tapable": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+      "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/tar": {
       "version": "6.2.1",
       "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
         "tar-stream": "^2.1.4"
       }
     },
-    "node_modules/tar-fs/node_modules/chownr": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
-      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
-    },
     "node_modules/tar-stream": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
         "safe-buffer": "~5.2.0"
       }
     },
+    "node_modules/tar/node_modules/chownr": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+      "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+      "optional": true,
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/tar/node_modules/minipass": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
       }
     },
     "node_modules/type-fest": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
-      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+      "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
       "dev": true,
       "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
+        "node": ">=8"
       }
     },
     "node_modules/typedarray": {
       }
     },
     "node_modules/update-browserslist-db": {
-      "version": "1.0.13",
-      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
-      "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+      "version": "1.0.15",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz",
+      "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==",
       "dev": true,
       "funding": [
         {
         }
       ],
       "dependencies": {
-        "escalade": "^3.1.1",
+        "escalade": "^3.1.2",
         "picocolors": "^1.0.0"
       },
       "bin": {
         "node": ">=8"
       }
     },
+    "node_modules/word-wrap": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/workerpool": {
       "version": "6.2.1",
       "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
index b0f36f3b41da892bf8269866c695c0e25935649f..85a53ed8150caa35a19eea85badb10dc520bb639 100644 (file)
   ],
   "main": "server.js",
   "scripts": {
+    "audit": "npm audit",
     "coverage": "nyc npm test",
     "coverage-check": "nyc check-coverage",
     "eslint": "eslint server.js src",
     "test": "mocha --recursive"
   },
   "pre-commit": [
+    "audit",
     "eslint",
     "coverage",
     "coverage-check"
   ],
   "engines": {
-    "node": "^14 >=14.18 || >=15.7"
+    "node": ">=16.9"
   },
   "repository": {
     "type": "git",
   "license": "ISC",
   "dependencies": {
     "@squeep/amqp-helper": "git+https://git.squeep.com/squeep-amqp-helper#v1.0.0",
-    "@squeep/api-dingus": "^2.1.0",
-    "@squeep/authentication-module": "git+https://git.squeep.com/squeep-authentication-module/#v1.4.0",
+    "@squeep/api-dingus": "^2",
+    "@squeep/authentication-module": "git+https://git.squeep.com/squeep-authentication-module/#v1.5.0",
     "@squeep/chores": "git+https://git.squeep.com/squeep-chores/#v1.0.1",
-    "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.6.0",
-    "@squeep/indieauth-helper": "^1.4.1",
-    "@squeep/logger-json-console": "^3.0.2",
-    "@squeep/mystery-box": "^2.0.2",
-    "@squeep/resource-authentication-module": "git+https://git.squeep.com/squeep-resource-authentication-module#v1.0.1",
-    "@squeep/roman": "^1.0.1",
-    "@squeep/web-linking": "^1.0.8",
-    "better-sqlite3": "^9.4.5",
-    "pg-promise": "^11.6.0",
-    "uuid": "^9.0.1"
+    "@squeep/html-template-helper": "git+https://git.squeep.com/squeep-html-template-helper#v1.6.1",
+    "@squeep/indieauth-helper": "^1.4",
+    "@squeep/logger-json-console": "^3",
+    "@squeep/mystery-box": "^2",
+    "@squeep/roman": "^1",
+    "@squeep/web-linking": "^1",
+    "better-sqlite3": "^9",
+    "pg-promise": "^11",
+    "uuid": "^9"
   },
   "devDependencies": {
+    "@squeep/eslint-config": "^1",
     "@squeep/test-helper": "git+https://git.squeep.com/squeep-test-helper#v1.0.1",
-    "eslint": "^8.57.0",
-    "eslint-plugin-n": "^16.6.2",
-    "eslint-plugin-promise": "^6.1.1",
-    "eslint-plugin-security": "^2.1.1",
-    "eslint-plugin-sonarjs": "^0.25.1",
-    "html-validate": "^8.18.1",
-    "mocha": "^10.4.0",
-    "mocha-steps": "^1.3.0",
-    "nyc": "^15.1.0",
-    "pre-commit": "^1.2.2",
-    "sinon": "^17.0.1"
+    "eslint": "^9",
+    "html-validate": "^8",
+    "mocha": "^10",
+    "mocha-steps": "^1",
+    "nyc": "^15",
+    "pre-commit": "^1",
+    "sinon": "^17"
   }
 }
index 40021580ce1d665eeb1e7cffe9341e1ef7a28328..bd21f6f4a70009628aedc237634cc82ecaaf35b4 100644 (file)
@@ -23,7 +23,7 @@ class Chores extends BaseChores {
 
   /**
    * Attempt to remove tokens which are expired or otherwise no longer valid.
-   * @param {Number} atLeastMsSinceLast
+   * @param {number} atLeastMsSinceLast minimum clean period
    */
   async cleanTokens(atLeastMsSinceLast = this.options?.chores?.tokenCleanupMs || 0) {
     const _scope = _fileScope('cleanTokens');
@@ -47,7 +47,7 @@ class Chores extends BaseChores {
 
   /**
    * Attempt to remove ephemeral scopes which are no longer referenced by tokens.
-   * @param {Number} atLeastMsSinceLast
+   * @param {number} atLeastMsSinceLast minimum clean period
    */
   async cleanScopes(atLeastMsSinceLast = this.options?.chores?.scopeCleanupMs || 0) {
     const _scope = _fileScope('cleanScopes');
index a9427c2e1f5b392d653c265487fa5bbf2ea64a65..0855720ee2a1766e46e92dad2a853edce22288a9 100644 (file)
@@ -8,9 +8,9 @@ const randomBytesAsync = promisify(randomBytes);
 
 /**
  * Limit length of string to keep logs sane
- * @param {String} str
- * @param {Number} len
- * @returns {String}
+ * @param {string} str str
+ * @param {number} len len
+ * @returns {string} str
  */
 const logTruncate = (str, len) => {
   if (typeof str !== 'string' || str.toString().length <= len) {
@@ -21,9 +21,9 @@ const logTruncate = (str, len) => {
 
 /**
  * Turn a snake into a camel.
- * @param {String} snakeCase
- * @param {String|RegExp} delimiter
- * @returns {String}
+ * @param {string} snakeCase snake case
+ * @param {string | RegExp} delimiter delimiter
+ * @returns {string} camel case
  */
 const camelfy = (snakeCase, delimiter = '_') => {
   if (!snakeCase || typeof snakeCase.split !== 'function') {
@@ -38,7 +38,8 @@ const camelfy = (snakeCase, delimiter = '_') => {
 
 /**
  * Return an array containing x if x is not an array.
- * @param {*} x
+ * @param {*} x x
+ * @returns {any[]} x[]
  */
 const ensureArray = (x) => {
   if (x === undefined) {
@@ -52,8 +53,8 @@ const ensureArray = (x) => {
 
 /**
  * Recursively freeze an object.
- * @param {Object} o 
- * @returns {Object}
+ * @param {object} o obj
+ * @returns {object} frozen obj
  */
 const freezeDeep = (o) => {
   Object.freeze(o);
@@ -68,9 +69,13 @@ const freezeDeep = (o) => {
 };
 
 
-/** Oauth2.1 Â§3.2.3.1
+/**
+ * Oauth2.1 Â§3.2.3.1
  * %x20-21 / %x23-5B / %x5D-7E
- * @param {String} char
+ * ' '-'!' / '#'-'[' / ']'-'~'
+ * not allowed: control characters, '"', '\'
+ * @param {string} char character
+ * @returns {boolean} is valid
  */
 const validErrorChar = (char) => {
   const value = char.charCodeAt(0);
@@ -82,8 +87,8 @@ const validErrorChar = (char) => {
 
 /**
  * Determine if an OAuth error message is valid.
- * @param {String} error
- * @returns {Boolean}
+ * @param {string} error error
+ * @returns {boolean} is valid
  */
 const validError = (error) => {
   return error && error.split('').filter((c) => !validErrorChar(c)).length === 0 || false;
@@ -93,7 +98,8 @@ const validError = (error) => {
 /**
  * OAuth2.1 Â§3.2.2.1
  * scope-token = 1*( %x21 / %x23-5B / %x5D-7E )
- * @param {String} char
+ * @param {string} char char
+ * @returns {boolean} is valid
  */
 const validScopeChar = (char) => {
   const value = char.charCodeAt(0);
@@ -105,8 +111,8 @@ const validScopeChar = (char) => {
 
 /**
  * Determine if a scope has a valid name.
- * @param {String} scope
- * @returns {Boolean}
+ * @param {string} scope scope
+ * @returns {boolean} is valid
  */
 const validScope = (scope) => {
   return scope && scope.split('').filter((c) => !validScopeChar(c)).length === 0 || false;
@@ -115,7 +121,8 @@ const validScope = (scope) => {
 
 /**
  * 
- * @param {Number} bytes
+ * @param {number} bytes bytes
+ * @returns {string} base64 random string
  */
 const newSecret = async (bytes = 64) => {
   return (await randomBytesAsync(bytes * 3 / 4)).toString('base64');
@@ -124,8 +131,8 @@ const newSecret = async (bytes = 64) => {
 
 /**
  * Convert a Date object to epoch seconds.
- * @param {Date=} date
- * @returns {Number}
+ * @param {Date=} date date
+ * @returns {number} epoch
  */
 const dateToEpoch = (date) => {
   const dateMs = date ? date.getTime() : Date.now();
@@ -138,10 +145,16 @@ const omit = (o, props) => {
 };
 
 
+/**
+ * @typedef {object} ConsoleLike
+ * @property {Function} debug log debug
+ */
+
 /**
  * Log Mystery Box statistics events.
- * @param {Console} logger
- * @param {String} scope
+ * @param {ConsoleLike} logger logger instance
+ * @param {string} scope scope
+ * @returns {Function} stat logger
  */
 const mysteryBoxLogger = (logger, scope) => {
   return (s) => {
index 953034dba67d06d8063275e04a50bb01aa5a3ec1..a127c3978e8e8347c38e9e4d031ed9e9ed79199b 100644 (file)
@@ -21,7 +21,7 @@ class Database {
    * At the minimum, this will validate a compatible schema is present and usable.
    * Some engines will also perform other initializations or async actions which
    * are easier handled outside the constructor.
-  */
+   */
   async initialize() {
     const _scope = _fileScope('initialize');
 
@@ -39,15 +39,15 @@ class Database {
 
 
   /**
-   * @typedef {Object} SchemaVersionObject
-   * @property {Number} major
-   * @property {Number} minor
-   * @property {Number} patch
+   * @typedef {object} SchemaVersionObject
+   * @property {number} major major
+   * @property {number} minor minor
+   * @property {number} patch patch
    */
   /**
    * Query the current schema version.
    * This is a standalone query function, as it is called before statements are loaded.
-   * @returns {Promise<SchemaVersionObject>}
+   * @returns {Promise<SchemaVersionObject>} schema version
    */
   async _currentSchema() {
     this._notImplemented('_currentSchema', arguments);
@@ -73,7 +73,7 @@ class Database {
 
   /**
    * Wrap a function call in a transaction context.
-   * @param {*} dbCtx
+   * @param {*} dbCtx db context
    * @param {Function} fn fn(txCtx)
    */
   async transaction(dbCtx, fn) {
@@ -101,9 +101,9 @@ class Database {
    * - infinites
    * - null
    * - uuid
-   * @param {Object} object
-   * @param {String[]} properties
-   * @param {String[]} types
+   * @param {object} object object
+   * @param {string[]} properties properties
+   * @param {string[]} types types
    */
   _ensureTypes(object, properties, types) {
     const _scope = _fileScope('_ensureTypes');
@@ -157,14 +157,14 @@ class Database {
 
 
   /**
-   * @typedef {Object} Authentication
-   * @property {String} identifier
-   * @property {String=} credential
-   * @property {Date} created
-   * @property {Date=} lastAuthentication
+   * @typedef {object} Authentication
+   * @property {string} identifier identifier
+   * @property {string=} credential credential
+   * @property {Date} created created
+   * @property {Date=} lastAuthentication last authentication
    */
   /**
-   * @param {Authentication} authentication 
+   * @param {Authentication} authentication authentication
    */
   _validateAuthentication(authentication) {
     [
@@ -177,14 +177,14 @@ class Database {
 
 
   /**
-   * @typedef {Object} Resource
-   * @property {String} resourceId - uuid
-   * @property {String} secret
-   * @property {String} description
-   * @property {Date} created
+   * @typedef {object} Resource
+   * @property {string} resourceId uuid
+   * @property {string} secret secret
+   * @property {string} description description
+   * @property {Date} created created at
    */
   /**
-   * @param {Resource} resource
+   * @param {Resource} resource resource
    */
   _validateResource(resource) {
     [
@@ -196,24 +196,24 @@ class Database {
 
 
   /**
-   * @typedef {Object} Token
-   * @property {String} codeId - uuid
-   * @property {String} profile
-   * @property {Date} created
-   * @property {Date=} expires
-   * @property {Date=} refreshExpires
-   * @property {Date=} refreshed
-   * @property {*=} duration
-   * @property {*=} refreshDuration
-   * @property {Number|BigInt=} refresh_count
-   * @property {Boolean} is_revoked
-   * @property {Boolean} is_token
-   * @property {String} client_id
-   * @property {String[]} scopes
-   * @property {Object=} profileData
+   * @typedef {object} Token
+   * @property {string} codeId uuid
+   * @property {string} profile profile
+   * @property {Date} created created at
+   * @property {Date=} expires expires at
+   * @property {Date=} refreshExpires refresh expires at
+   * @property {Date=} refreshed refreshed at
+   * @property {*=} duration duration
+   * @property {*=} refreshDuration refresh duration
+   * @property {number | bigint=} refresh_count refresh count
+   * @property {boolean} is_revoked is revoked
+   * @property {boolean} is_token is token
+   * @property {string} client_id client id
+   * @property {string[]} scopes scopes
+   * @property {object=} profileData profile data
    */
   /**
-   * @param {Token} token
+   * @param {Token} token token
    */
   _validateToken(token) {
     [
@@ -233,8 +233,8 @@ class Database {
    * Interface methods need implementations.  Ensure the db-interaction
    * methods on the base class call this, so they may be overridden by
    * implementation classes.
-   * @param {String} method
-   * @param {arguments} args
+   * @param {string} method method
+   * @param {arguments} args args
    */
   _notImplemented(method, args) {
     this.logger.error(_fileScope(method), 'abstract method called', Array.from(args));
@@ -244,7 +244,7 @@ class Database {
 
   /**
    * Get all the almanac entries.
-   * @param {*} dbCtx
+   * @param {*} dbCtx db context
    */
   async almanacGetAll(dbCtx) {
     this._notImplemented('almanacGetAll', arguments);
@@ -253,9 +253,9 @@ class Database {
 
   /**
    * Insert or update an almanac entry.
-   * @param {*} dbCtx
-   * @param {String} event
-   * @param {Date=} date
+   * @param {*} dbCtx db context
+   * @param {string} event event
+   * @param {Date=} date date
    */
   async almanacUpsert(dbCtx, event, date) {
     this._notImplemented('almanacUpsert', arguments);
@@ -264,9 +264,9 @@ class Database {
 
   /**
    * Fetch the authentication record for an identifier.
-   * @param {*} dbCtx
-   * @param {String} identifier
-   * @returns {Promise<Authentication>}
+   * @param {*} dbCtx db context
+   * @param {string} identifier identifier
+   * @returns {Promise<Authentication>} authentication
    */
   async authenticationGet(dbCtx, identifier) {
     this._notImplemented('authenticationGet', arguments);
@@ -276,8 +276,8 @@ class Database {
   /**
    * Update the authentication record for the identifier that
    * correct credentials have been supplied.
-   * @param {*} dbCtx
-   * @param {String} identifier
+   * @param {*} dbCtx db context
+   * @param {string} identifier identifier
    * @returns {Promise<void>}
    */
   async authenticationSuccess(dbCtx, identifier) {
@@ -287,10 +287,10 @@ class Database {
 
   /**
    * Insert or update the credential for an identifier.
-   * @param {*} dbCtx
-   * @param {String} identifier
-   * @param {String} credential
-   * @param {String=} otpKey
+   * @param {*} dbCtx db context
+   * @param {string} identifier identifier
+   * @param {string} credential credential
+   * @param {string=} otpKey otp key
    * @returns {Promise<void>}
    */
   async authenticationUpsert(dbCtx, identifier, credential, otpKey) {
@@ -300,9 +300,9 @@ class Database {
 
   /**
    * Update the otpKey for an identifier.
-   * @param {*} dbCtx
-   * @param {String} identifier
-   * @param {String=} otpKey
+   * @param {*} dbCtx db context
+   * @param {string} identifier identifier
+   * @param {string=} otpKey otp key
    * @returns {Promise<void>}
    */
   async authenticationUpdateOTPKey(dbCtx, identifier, otpKey) {
@@ -312,9 +312,9 @@ class Database {
 
   /**
    * Update the credential for an identifier.
-   * @param {*} dbCtx
-   * @param {String} identifier
-   * @param {String} credential
+   * @param {*} dbCtx db context
+   * @param {string} identifier identifier
+   * @param {string} credential credential
    * @returns {Promise<void>}
    */
   async authenticationUpdateCredential(dbCtx, identifier, credential) {
@@ -324,9 +324,9 @@ class Database {
 
   /**
    * Determine if profile url is known to this service.
-   * @param {*} dbCtx
-   * @param {String} profile
-   * @returns {Promise<Boolean>}
+   * @param {*} dbCtx db context
+   * @param {string} profile profile
+   * @returns {Promise<boolean>} is valid
    */
   async profileIsValid(dbCtx, profile) {
     this._notImplemented('profileGet', arguments);
@@ -336,9 +336,9 @@ class Database {
   /**
    * Insert a new relationship between a profile endpoint and
    * an authenticated identifier.
-   * @param {*} dbCtx
-   * @param {String} profile
-   * @param {String} identifier
+   * @param {*} dbCtx db context
+   * @param {string} profile profile
+   * @param {string} identifier identifier
    * @returns {Promise<void>}
    */
   async profileIdentifierInsert(dbCtx, profile, identifier) {
@@ -348,9 +348,9 @@ class Database {
 
   /**
    * Adds a scope to be available for a profile to include on any authorization request.
-   * @param {*} dbCtx
-   * @param {String} profile
-   * @param {String} scope
+   * @param {*} dbCtx db context
+   * @param {string} profile profile
+   * @param {string} scope scope
    * @returns {Promise<void>}
    */
   async profileScopeInsert(dbCtx, profile, scope) {
@@ -359,24 +359,26 @@ class Database {
 
 
   /**
-   * @typedef {Object} ScopeDetails
-   * @property {String} description
-   * @property {String[]=} profiles
+   * @typedef {object} ScopeDetails
+   * @property {string} description description
+   * @property {string[]=} profiles profiles
+   */
+  /**
+   * @typedef {object} Profile
+   * @property {ScopeDetails} scope scope
    */
   /**
-   * @typedef {Object.<String, Object>} ProfileScopes
-   * @property {Object.<String, Object>} profile
-   * @property {Object.<String, ScopeDetails>} profile.scope
+   * @typedef {{[profile: string]: Profile}} ProfileScopes
    */
   /**
-   * @typedef {Object.<String, Object>} ScopeIndex
-   * @property {ScopeDetails} scope
+   * @typedef {{[scope: string]: ScopeDetails}} ScopeIndex
+   * @property {ScopeDetails} scope scope details
    */
   /**
-   * @typedef {Object} ProfilesScopesReturn
-   * @property {ProfileScopes} profileScopes
-   * @property {ScopeIndex} scopeIndex
-   * @property {String[]} profiles
+   * @typedef {object} ProfilesScopesReturn
+   * @property {ProfileScopes} profileScopes profile scopes
+   * @property {ScopeIndex} scopeIndex scope index
+   * @property {string[]} profiles profiles
    */
   /**
    * Returns an object containing:
@@ -384,9 +386,9 @@ class Database {
    *   which each contain a description of the scope and a list of profiles offering it
    * - an object with scopes as keys to the same scope objects
    * - a list of profiles
-   * @param {*} dbCtx
-   * @param {String} identifier
-   * @returns {Promise<ProfileScopesReturn>}
+   * @param {*} dbCtx db context
+   * @param {string} identifier identifier
+   * @returns {Promise<ProfilesScopesReturn>} profiles scopes
    */
   async profilesScopesByIdentifier(dbCtx, identifier) {
     this._notImplemented('profilesScopesByIdentifier', arguments);
@@ -395,18 +397,18 @@ class Database {
 
   /**
    * @typedef ProfileScopesRow
-   * @property profile
-   * @property scope
-   * @property description
-   * @property application
-   * @property isPermanent
-   * @property isManuallyAdded
+   * @property {string} profile profile
+   * @property {string} scope scope
+   * @property {string} description description
+   * @property {string} application application
+   * @property {boolean} isPermanent avoid cleanup
+   * @property {boolean} isManuallyAdded avoid cleanup
    */
   /**
    * Convert db row data into associative structures.
    * Same behavior is shared by multiple engines.
-   * @param {ProfileScopesRow[]} profileScopesRows
-   * @returns {ProfileScopesReturn}
+   * @param {ProfileScopesRow[]} profileScopesRows profile scopes row
+   * @returns {ProfilesScopesReturn} profiles scopes
    */
   static _profilesScopesBuilder(profileScopesRows) {
     const scopeIndex = {};
@@ -445,9 +447,9 @@ class Database {
 
   /**
    * Sets list of additional scopes available to profile.
-   * @param {*} dbCtx
-   * @param {String} profile
-   * @param {String[]} scopes
+   * @param {*} dbCtx db context
+   * @param {string} profile profile
+   * @param {string[]} scopes scopes
    * @returns {Promise<void>}
    */
   async profileScopesSetAll(dbCtx, profile, scopes) {
@@ -457,20 +459,19 @@ class Database {
 
   /**
    * Create (or revoke a duplicate) code as a token entry.
-   * @param {*} dbCtx
-   * @param {Object} data
-   * @param {String} data.codeId
-   * @param {Date} data.created
-   * @param {Boolean} data.isToken
-   * @param {String} data.clientId
-   * @param {String} data.profile - profile uri
-   * @param {String} data.identifier
-   * @param {String[]} data.scopes
-   * @param {Number|Null} data.lifespanSeconds - null sets expiration to Infinity
-   * @param {Number|Null} data.refreshLifespanSeconds - null sets refresh to none
-   * @param {String|Null} data.resource
-   * @param {Object|Null} data.profileData - profile data from profile uri
-   * @returns {Promise<Boolean>} whether redemption was successful
+   * @param {*} dbCtx db context
+   * @param {object} data data
+   * @param {string} data.codeId code id
+   * @param {Date} data.created created at
+   * @param {boolean} data.isToken is token
+   * @param {string} data.clientId client id
+   * @param {string} data.profile profile uri
+   * @param {string} data.identifier identifier
+   * @param {string[]} data.scopes scopesx
+   * @param {number | null} data.lifespanSeconds null sets expiration to Infinity
+   * @param {number | null} data.refreshLifespanSeconds null sets refresh to none
+   * @param {object | null} data.profileData profile data from profile uri
+   * @returns {Promise<boolean>} whether redemption was successful
    */
   async redeemCode(dbCtx, { codeId, created, isToken, clientId, profile, identifier, scopes, lifespanSeconds, refreshLifespanSeconds, profileData } = {}) {
     this._notImplemented('redeemCode', arguments);
@@ -478,18 +479,18 @@ class Database {
 
 
   /**
-   * @typedef {Object} RefreshedToken
-   * @property {Date} expires
-   * @property {Date} refreshExpires
-   * @property {String[]=} scopes if scopes were reduced
+   * @typedef {object} RefreshedToken
+   * @property {Date} expires expires at
+   * @property {Date} refreshExpires refresh expires at
+   * @property {string[]=} scopes if scopes were reduced
    */
   /**
    * Redeem a refresh token to renew token codeId.
-   * @param {*} dbCtx
-   * @param {String} codeId
-   * @param {Date} refreshed
-   * @param {String[]} removeScopes
-   * @returns {Promise<RefreshedToken>}
+   * @param {*} dbCtx db context
+   * @param {string} codeId code id
+   * @param {Date} refreshed refreshed at
+   * @param {string[]} removeScopes remove scopes
+   * @returns {Promise<RefreshedToken>} refreshed token
    */
   async refreshCode(dbCtx, codeId, refreshed, removeScopes) {
     this._notImplemented('refreshCode', arguments);
@@ -498,9 +499,9 @@ class Database {
 
   /**
    * Fetch a resource server record.
-   * @param {*} dbCtx
-   * @param {String} identifier uuid
-   * @returns {Promise<Resource>}
+   * @param {*} dbCtx db context
+   * @param {string} resourceId uuid
+   * @returns {Promise<Resource>} resource
    */
   async resourceGet(dbCtx, resourceId) {
     this._notImplemented('resourceGet', arguments);
@@ -509,10 +510,10 @@ class Database {
 
   /**
    * Create, or update description of, a resourceId.
-   * @param {*} dbCtx
-   * @param {String=} resourceId uuid
-   * @param {String=} secret
-   * @param {String=} description
+   * @param {*} dbCtx db context
+   * @param {string=} resourceId uuid
+   * @param {string=} secret secret
+   * @param {string=} description description
    * @returns {Promise<void>}
    */
   async resourceUpsert(dbCtx, resourceId, secret, description) {
@@ -522,10 +523,11 @@ class Database {
 
   /**
    * Register a scope and its description.
-   * @param {*} dbCtx
-   * @param {String} scope
-   * @param {String} application
-   * @param {String} description
+   * @param {*} dbCtx db context
+   * @param {string} scope scope
+   * @param {string} application application
+   * @param {string} description description
+   * @param {boolean} manuallyAdded is manually added
    * @returns {Promise<void>}
    */
   async scopeUpsert(dbCtx, scope, application, description, manuallyAdded = false) {
@@ -535,9 +537,9 @@ class Database {
 
   /**
    * Remove a non-permanent scope if it is not currently in use.
-   * @param {*} dbCtx
-   * @param {String} scope
-   * @returns {Promise<Boolean>}
+   * @param {*} dbCtx db context
+   * @param {string} scope scope
+   * @returns {Promise<boolean>} deleted
    */
   async scopeDelete(dbCtx, scope) {
     this._notImplemented('scopeDelete', arguments);
@@ -545,16 +547,16 @@ class Database {
 
 
   /**
-   * @typedef {Number|BigInt} CleanupResult
+   * @typedef {number | bigint} CleanupResult
    */
   /**
-   * @typedef {Object} CleanupResult
+   * @alias {object} CleanupResult
    */
   /**
    * Remove any non-permanent and non-manually-created scopes not currently in use.
-   * @param {*} dbCtx
-   * @param {Number} atLeastMsSinceLast skip cleanup if already executed this recently
-   * @returns {Promise<CleanupResult>}
+   * @param {*} dbCtx db context
+   * @param {number} atLeastMsSinceLast skip cleanup if already executed this recently
+   * @returns {Promise<CleanupResult>} cleanup result
    */
   async scopeCleanup(dbCtx, atLeastMsSinceLast) {
     this._notImplemented('scopeClean', arguments);
@@ -563,10 +565,10 @@ class Database {
 
   /**
    * Forget tokens after they have expired, and redeemed codes after they have expired.
-   * @param {*} dbCtx
-   * @param {Number} codeLifespanSeconds
-   * @param {Number} atLeastMsSinceLast skip cleanup if already executed this recently
-   * @returns {Promise<CleanupResult>}
+   * @param {*} dbCtx db context
+   * @param {number} codeLifespanSeconds code lifespan seconds
+   * @param {number} atLeastMsSinceLast skip cleanup if already executed this recently
+   * @returns {Promise<CleanupResult>} cleanup result
    */
   async tokenCleanup(dbCtx, codeLifespanSeconds, atLeastMsSinceLast) {
     this._notImplemented('tokenCleanup', arguments);
@@ -575,9 +577,9 @@ class Database {
 
   /**
    * Look up a redeemed token by code_id.
-   * @param {*} dbCtx
-   * @param {String} codeId
-   * @returns {Promise<Token>}
+   * @param {*} dbCtx db context
+   * @param {string} codeId code id
+   * @returns {Promise<Token>} token
    */
   async tokenGetByCodeId(dbCtx, codeId) {
     this._notImplemented('tokenGetByCodeId', arguments);
@@ -586,8 +588,8 @@ class Database {
 
   /**
    * Sets a redeemed token as revoked.
-   * @param {*} dbCtx
-   * @param {String} codeId - uuid
+   * @param {*} dbCtx db context
+   * @param {string} codeId - uuid
    * @returns {Promise<void>}
    */
   async tokenRevokeByCodeId(dbCtx, codeId) {
@@ -597,8 +599,8 @@ class Database {
 
   /**
    * Revoke the refreshability of a codeId.
-   * @param {*} dbCtx
-   * @param {String} codeId - uuid
+   * @param {*} dbCtx db context
+   * @param {string} codeId - uuid
    * @returns {Promise<void>}
    */
   async tokenRefreshRevokeByCodeId(dbCtx, codeId) {
@@ -608,26 +610,27 @@ class Database {
 
   /**
    * Get all tokens assigned to identifier.
-   * @param {*} dbCtx
-   * @param {String} identifier
-   * @returns {Promise<Tokens[]>}
+   * @param {*} dbCtx db context
+   * @param {string} identifier identifier
+   * @returns {Promise<Token[]>} token
    */
   async tokensGetByIdentifier(dbCtx, identifier) {
     this._notImplemented('tokensGetByIdentifier', arguments);
   }
 
 
-  /** @typedef {Object} RedeemedTicketData
-   * @property {String} subject
-   * @property {String} resource
-   * @property {String=} iss
-   * @property {String} ticket
-   * @property {String} token
+  /**
+   * @typedef {object} RedeemedTicketData
+   * @property {string} subject subject
+   * @property {string} resource resource
+   * @property {string=} iss issuer
+   * @property {string} ticket ticket
+   * @property {string} token token
    */
   /**
    * Persist details of a redeemed ticket.
-   * @param {*} dbCtx
-   * @param {RedeemedTicketData} redeemedData
+   * @param {*} dbCtx db context
+   * @param {RedeemedTicketData} redeemedData redeemed data
    * @returns {Promise<void>}
    */
   async ticketRedeemed(dbCtx, redeemedData) {
@@ -637,18 +640,20 @@ class Database {
 
   /**
    * Update details of a redeemed ticket that it has been published.
-   * @param {*} dbCtx
-   * @param {RedeemedTicketData} redeemedData
+   * @param {*} dbCtx db context
+   * @param {RedeemedTicketData} redeemedData redeemed data
    * @returns {Promise<void>}
    */
   async ticketTokenPublished(dbCtx, redeemedData) {
     this._notImplemented('ticketTokenPublished', arguments);
   }
 
+
   /**
    * Retrieve redeemed tokens which have not yet been published to queue.
-   * @param {Number} limit
-   * @returns {Promise<RedeemedData[]>}
+   * @param {*} dbCtx db context
+   * @param {number} limit limit
+   * @returns {Promise<RedeemedTicketData[]>} redeemed but not published
    */
   async ticketTokenGetUnpublished(dbCtx, limit) {
     this._notImplemented('ticketTokenGetUnpublished', arguments);
index 65a1e39f2c3cb64ceea5483f9dabf5606b35d736..4582a872a1ff5def85ed100f38573a636ede9280 100644 (file)
@@ -10,17 +10,17 @@ const path = require('path');
  */
 
 /**
- * @typedef {Object} SchemaVersionObject
- * @property {Number} major
- * @property {Number} minor
- * @property {Number} patch
+ * @typedef {object} SchemaVersionObject
+ * @property {number} major major
+ * @property {number} minor minor
+ * @property {number} patch patch
  */
 
 
 /**
  * Split a dotted version string into parts.
- * @param {String} v
- * @returns {SchemaVersionObject}
+ * @param {string} v version string
+ * @returns {SchemaVersionObject} version object
  */
 function schemaVersionStringToObject(v) {
   const [ major, minor, patch ] = v.split('.', 3).map((x) => parseInt(x, 10));
@@ -30,8 +30,9 @@ function schemaVersionStringToObject(v) {
 
 /**
  * Render a version object numerically.
- * @param {SchemaVersionObject} v
- * @returns {Number}
+ * Assumes no part will be greater than 1000.
+ * @param {SchemaVersionObject} v version object
+ * @returns {number} number
  */
 function schemaVersionObjectToNumber(v) {
   const vScale = 1000;
@@ -41,8 +42,8 @@ function schemaVersionObjectToNumber(v) {
 
 /**
  * Convert dotted version string into number.
- * @param {String} v
- * @returns {Number}
+ * @param {string} v version string
+ * @returns {number} number
  */
 function schemaVersionStringToNumber(v) {
   return schemaVersionObjectToNumber(schemaVersionStringToObject(v));
@@ -51,9 +52,9 @@ function schemaVersionStringToNumber(v) {
 
 /**
  * Version string comparison, for sorting.
- * @param {String} a
- * @param {String} b
- * @returns {Number}
+ * @param {string} a version string
+ * @param {string} b version string
+ * @returns {number} cmp
  */
 function schemaVersionStringCmp(a, b) {
   return schemaVersionStringToNumber(a) - schemaVersionStringToNumber(b);
@@ -62,9 +63,10 @@ function schemaVersionStringCmp(a, b) {
 
 /**
  * Check if an entry in a directory is a directory containing a migration file.
- * @param {String} schemaDir
- * @param {String} name
- * @returns {Boolean}
+ * @param {string} schemaDir schema dir
+ * @param {string} name name
+ * @param {string} migrationFile migration file
+ * @returns {boolean} is
  */
 function isSchemaMigrationDirectory(schemaDir, name, migrationFile = 'apply.sql') {
   // eslint-disable-next-line security/detect-non-literal-fs-filename
@@ -75,7 +77,7 @@ function isSchemaMigrationDirectory(schemaDir, name, migrationFile = 'apply.sql'
       // eslint-disable-next-line security/detect-non-literal-fs-filename
       applyStat = fs.statSync(path.join(schemaDir, name, migrationFile));
       return applyStat.isFile();
-    } catch (e) {
+    } catch (e) { // eslint-disable-line no-unused-vars
       return false;
     }
   }
@@ -86,8 +88,8 @@ function isSchemaMigrationDirectory(schemaDir, name, migrationFile = 'apply.sql'
 /**
  * Return an array of schema migration directory names within engineDir,
  * sorted in increasing order.
- * @param {String} engineDir
- * @returns {String[]}
+ * @param {string} engineDir path to implementation
+ * @returns {string[]} versions
  */
 function allSchemaVersions(engineDir) {
   const schemaDir = path.join(engineDir, 'sql', 'schema');
@@ -101,12 +103,12 @@ function allSchemaVersions(engineDir) {
 /**
  * Return an array of schema migration directory names within engineDir,
  * which are within supported range, and are greater than the current
- * @param {String} engineDir
- * @param {SchemaVersionObject} current
- * @param {Object} supported
- * @param {SchemaVersionObject} supported.min
- * @param {SchemaVersionObject} supported.max
- * @returns {String[]}
+ * @param {string} engineDir path to implementation
+ * @param {SchemaVersionObject} current version
+ * @param {object} supported range of supported versions
+ * @param {SchemaVersionObject} supported.min minimum version
+ * @param {SchemaVersionObject} supported.max maximum version
+ * @returns {string[]} applicable versions
  */
 function unappliedSchemaVersions(engineDir, current, supported) {
   const min = schemaVersionObjectToNumber(supported.min);
@@ -128,4 +130,4 @@ module.exports = {
   isSchemaMigrationDirectory,
   allSchemaVersions,
   unappliedSchemaVersions,
-};
\ No newline at end of file
+};
index a618b9abde14fdfff4adfdf7a49bb652baedddc8..2dd75b2214f1962abdb18a541a4628bfeb4d7866 100644 (file)
@@ -60,8 +60,8 @@ class DatabaseSQLite extends Database {
 
   /**
    * Boolean to 0/1 representation for SQLite params.
-   * @param {Boolean} bool
-   * @returns {Number}
+   * @param {boolean} bool boolean
+   * @returns {number} number
    */
   static _booleanToNumeric(bool) {
     // eslint-disable-next-line security/detect-object-injection
@@ -84,7 +84,7 @@ class DatabaseSQLite extends Database {
     let metaExists = tableExists.get();
     if (metaExists === undefined) {
       const fPath = path.join(__dirname, 'sql', 'schema', 'init.sql');
-      // eslint-disable-next-line security/detect-non-literal-fs-filename
+       
       const fSql = fs.readFileSync(fPath, { encoding: 'utf8' });
       this.db.exec(fSql);
       metaExists = tableExists.get();
@@ -139,7 +139,7 @@ class DatabaseSQLite extends Database {
       };
     };
 
-    // eslint-disable-next-line security/detect-non-literal-fs-filename
+     
     for (const f of fs.readdirSync(sqlDir)) {
       const fPath = path.join(sqlDir, f);
       const { name: fName, ext: fExt } = path.parse(f);
index 5494d275d8e4fb8af173412354497c3e80eacbd7..df653d63dbbbd97203a1f60bdbf1b1ed1e0804ed 100644 (file)
@@ -2,9 +2,9 @@
 
 /**
  * Scrub credential from POST login body data.
- * @param {Object} data
- * @param {Boolean} sanitize
- * @returns {Boolean}
+ * @param {object} data data
+ * @param {boolean} sanitize do sanitize
+ * @returns {boolean} did/would sanitize
  */
 function sanitizePostCredential(data, sanitize = true) {
   let unclean = false;
@@ -29,9 +29,9 @@ function sanitizePostCredential(data, sanitize = true) {
 
 /**
  * Scrub sensitive data from context.
- * @param {Object} data
- * @param {Boolean} sanitize
- * @returns {Boolean}
+ * @param {object} data data
+ * @param {boolean} sanitize do sanitize
+ * @returns {boolean} did/would sanitize
  */
 function sanitizeContext(data, sanitize = true) {
   let unclean = false;
@@ -78,8 +78,9 @@ function sanitizeContext(data, sanitize = true) {
  * Reduce logged data about scopes from profilesScopes.
  * For all referenced scopes, only include profiles list.
  * Remove scopes without profile references from scopeIndex.
- * @param {Object} data
- * @param {Boolean} sanitize
+ * @param {object} data data
+ * @param {boolean} sanitize do sanitize
+ * @returns {boolean} did/would sanitize
  */
 function reduceScopeVerbosity(data, sanitize = true) {
   let unclean = false;
@@ -114,8 +115,8 @@ function reduceScopeVerbosity(data, sanitize = true) {
 
 /**
  * Return any scope entries on an object, and whether sanitization is needed.
- * @param {Object=} obj
- * @returns {Object}
+ * @param {object=} obj obj
+ * @returns {object} obj
  */
 const _scopesFrom = (obj) => {
   const scopesEntries = Object.entries(obj?.scopeIndex || {});
@@ -130,21 +131,21 @@ const _scopesFrom = (obj) => {
 
 
 /**
- * @typedef {[String, Object]} ScopeEntry
+ * @typedef {[string, object]} ScopeEntry
  */
 /**
  * Return new list of entries with scrubbed scopeDetails.
- * @param {ScopeEntry[]} entries
- * @returns {ScopeEntry[]}
+ * @param {ScopeEntry[]} entries entries
+ * @returns {ScopeEntry[]} entries
  */
 const _scopeEntriesScrubber = (entries) => entries.map(([scopeName, scopeDetails]) => ([scopeName, { profiles: scopeDetails.profiles }]));
 
 
 /**
  * Create a new profilesScopes type object with scrubbed scope details.
- * @param {ScopeEntry[]} scopesEntries
- * @param {ScopeEntry[]} profilesEntries
- * @returns {Object}
+ * @param {ScopeEntry[]} scopesEntries entries
+ * @param {ScopeEntry[]} profilesEntries entries
+ * @returns {object} profilesScopes
  */
 const _sanitizeProfilesScopes = (scopesEntries, profilesEntries) => {
   const referencedScopesEntries = scopesEntries.filter(([_scopeName, scopeDetails]) => scopeDetails?.profiles?.length); // eslint-disable-line no-unused-vars
index 398b40921397e5f5de8da9efd51228349c4e704f..ce544b949a90603ef09b8d7448410aa264fcb9e0 100644 (file)
@@ -1,3 +1,4 @@
+/* eslint-disable sonarjs/no-duplicate-string */
 'use strict';
 
 const common = require('./common');
@@ -18,6 +19,10 @@ const scopeSplitRE = / +/;
 
 const supportedCodeChallengeMethods = ['S256', 'SHA256'];
 
+/**
+ * @typedef {import('node:http')} http
+ */
+
 class Manager {
   constructor(logger, db, options) {
     this.options = options;
@@ -64,12 +69,12 @@ class Manager {
   /**
    * Add an error to a session, keeping only the most-severe code, but all descriptions.
    * This error is sent along on the redirection back to client endpoint.
-   * @param {Object} ctx
-   * @param {Object} ctx.session
-   * @param {String[]=} ctx.session.errorDescriptions
-   * @param {String=} ctx.session.error
-   * @param {String} error
-   * @param {String} errorDescription
+   * @param {object} ctx context
+   * @param {object} ctx.session session
+   * @param {string[]=} ctx.session.errorDescriptions errors
+   * @param {string=} ctx.session.error error
+   * @param {string} error error
+   * @param {string} errorDescription error
    */
   static _setError(ctx, error, errorDescription) {
     const errorPrecedence = [ // By increasing severity
@@ -109,7 +114,7 @@ class Manager {
    * The authorization server MUST include the HTTP Cache-Control response
    * header field with a value of no-store in any response
    * containing tokens, credentials, or other sensitive information.
-   * @param {http.ServerResponse} res
+   * @param {http.ServerResponse} res response
    */
   static _sensitiveResponse(res) {
     Object.entries({
@@ -121,8 +126,8 @@ class Manager {
 
   /**
    * Sets params entries as url search parameters.
-   * @param {URL} url
-   * @param {Object} params
+   * @param {URL} url url
+   * @param {object} params parameters
    */
   static _setSearchParams(url, params) {
     Object.entries(params).forEach((param) => url.searchParams.set(...param));
@@ -131,9 +136,8 @@ class Manager {
 
   /**
    * Serve the informational root page.
-   * @param {http.ClientRequest} req 
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async getRoot(res, ctx) {
     const _scope = _fileScope('getRoot');
@@ -146,8 +150,8 @@ class Manager {
 
   /**
    * Serve the metadata for this service.
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async getMeta(res, ctx) {
     const _scope = _fileScope('getMeta');
@@ -187,8 +191,8 @@ class Manager {
    * Process an authorization request from a client.
    * User has authenticated, check if user matches profile,
    * present user with consent form.
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async getAuthorization(res, ctx) {
     const _scope = _fileScope('getAuthorization');
@@ -306,7 +310,7 @@ class Manager {
 
   /**
    * Validates, fetches, and parses client_id url, populating clientIdentifier with client h-app data.
-   * @param {Object} ctx
+   * @param {object} ctx context
    */
   async _clientIdRequired(ctx) {
     if (ctx.queryParams['client_id']) {
@@ -332,7 +336,7 @@ class Manager {
 
   /**
    * Ensure redirect_uri exists and is corroborated by clientIdentifier data.
-   * @param {Object} ctx 
+   * @param {object} ctx context
    */
   static _redirectURIRequired(ctx) {
     if (ctx.queryParams['redirect_uri']) {
@@ -355,7 +359,7 @@ class Manager {
             }
           }
         }
-      } catch (e) {
+      } catch (e) { // eslint-disable-line no-unused-vars
         Manager._setError(ctx, 'invalid_request', 'invalid value for parameter \'redirect_uri\'');
       }
     } else {
@@ -366,7 +370,7 @@ class Manager {
 
   /**
    * response_type must be valid
-   * @param {Object} ctx
+   * @param {object} ctx context
    */
   static _responseTypeRequired(ctx) {
     ctx.session.responseType = ctx.queryParams['response_type'];
@@ -383,7 +387,7 @@ class Manager {
 
   /**
    * A state parameter must be present
-   * @param {Object} ctx 
+   * @param {object} ctx context
    */
   static _stateRequired(ctx) {
     ctx.session.state = ctx.queryParams['state'];
@@ -397,7 +401,7 @@ class Manager {
 
   /**
    * A code_challenge_method must be present and valid
-   * @param {Object} ctx
+   * @param {object} ctx context
    */
   _codeChallengeMethodRequired(ctx) {
     ctx.session.codeChallengeMethod = ctx.queryParams['code_challenge_method'];
@@ -416,7 +420,7 @@ class Manager {
 
   /**
    * A code_challenge must be present
-   * @param {Object} ctx
+   * @param {object} ctx context
    */
   _codeChallengeRequired(ctx) {
     ctx.session.codeChallenge = ctx.queryParams['code_challenge'];
@@ -435,7 +439,7 @@ class Manager {
 
   /**
    * Scopes may be present, with one known combination limitation
-   * @param {Object} ctx
+   * @param {object} ctx context
    */
   _scopeOptional(ctx) {
     const _scope = _fileScope('_scopeOptional');
@@ -460,14 +464,14 @@ class Manager {
 
   /**
    * Parses me, if provided
-   * @param {Object} ctx
+   * @param {object} ctx context
    */
   async _meOptional(ctx) {
     const me = ctx.queryParams['me'];
     if (me) {
       try {
         ctx.session.me = await this.communication.validateProfile(me);
-      } catch (e) {
+      } catch (e) { // eslint-disable-line no-unused-vars
         ctx.session.me = undefined;
       }
     }
@@ -476,8 +480,8 @@ class Manager {
 
   /**
    * Ensure authenticated identifier matches profile.
-   * @param {Object} ctx
-   * @returns {Boolean}
+   * @param {object} ctx context
+   * @returns {boolean} is valid
    */
   _profileValidForIdentifier(ctx) {
     const _scope = _fileScope('_profileValidForIdentifier');
@@ -493,10 +497,10 @@ class Manager {
 
   /**
    * Get numeric value from form field data.
-   * @param {*} ctx
-   * @param {String} field
-   * @param {String} customField
-   * @returns {Number=}
+   * @param {*} ctx context
+   * @param {string} field field
+   * @param {string} customField custom field
+   * @returns {number=} lifespan
    */
   _parseLifespan(ctx, field, customField) {
     const _scope = _fileScope('_parseLifespan');
@@ -528,8 +532,8 @@ class Manager {
 
   /**
    * Validate any accepted scopes, ensure uniqueness, return as array.
-   * @param {Object} ctx
-   * @returns {String=}
+   * @param {object} ctx context
+   * @returns {string[]} scopes
    */
   _parseConsentScopes(ctx) {
     const _scope = _fileScope('_ingestConsentScopes');
@@ -561,8 +565,8 @@ class Manager {
 
   /**
    * Parse and validate selected me is a valid profile option.
-   * @param {Object} ctx
-   * @returns {URL}
+   * @param {object} ctx context
+   * @returns {URL} url
    */
   _parseConsentMe(ctx) {
     const _scope = _fileScope('_parseConsentMe');
@@ -585,8 +589,8 @@ class Manager {
 
   /**
    * Get up-to-date profile data from selected profile endpoint.
-   * @param {Object} ctx
-   * @returns {Promise<Object>}
+   * @param {object} ctx context
+   * @returns {Promise<object>} profile data
    */
   async _fetchConsentProfileData(ctx) {
     const _scope = _fileScope('_fetchConsentProfileData');
@@ -624,8 +628,8 @@ class Manager {
    *   expires-seconds - optional custom lifespan
    *   refresh - optional refresh lifespan
    *   refresh-seconds - optional custom refresh lifespan
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async postConsent(res, ctx) {
     const _scope = _fileScope('postConsent');
@@ -728,8 +732,8 @@ class Manager {
 
   /**
    * Redeem a code for a profile url, and maybe more profile info.
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async postAuthorization(res, ctx) {
     const _scope = _fileScope('postAuthorization');
@@ -786,8 +790,8 @@ class Manager {
   /**
    * Ingest an incoming authorization redemption request, parsing fields
    * onto a new session object on the context.
-   * @param {*} dbCtx
-   * @param {Object} ctx
+   * @param {object} ctx context
+   * @returns {Promise<void>}
    */
   async _ingestPostAuthorizationRequest(ctx) {
     const _scope = _fileScope('_ingestPostAuthorizationRequest');
@@ -825,7 +829,7 @@ class Manager {
 
   /**
    * Unpack the session data from provided code overtop of context session ..
-   * @param {Object} ctx
+   * @param {object} ctx context
    */
   async _restoreSessionFromCode(ctx) {
     const _scope = _fileScope('_restoreSessionFromCode');
@@ -873,7 +877,7 @@ class Manager {
 
   /**
    * Ensure provided client_id matches session clientId.
-   * @param {Object} ctx
+   * @param {object} ctx context
    */
   _checkSessionMatchingClientId(ctx) {
     const _scope = _fileScope('_checkSessionMatchingClientId');
@@ -883,7 +887,7 @@ class Manager {
       try {
         clientId = new URL(clientId);
         ctx.session.clientId = new URL(ctx.session.clientId);
-      } catch (e) {
+      } catch (e) { // eslint-disable-line no-unused-vars
         this.logger.debug(_scope, 'un-parsable client_id url', { ctx });
         delete ctx.session.clientId;
         Manager._setError(ctx, 'invalid_request', 'malformed client_id');
@@ -901,7 +905,7 @@ class Manager {
 
 
   /**
-   * @param {Object} ctx
+   * @param {object} ctx context
    */
   _checkSessionMatchingRedirectUri(ctx) {
     const _scope = _fileScope('_checkSessionMatchingClientId');
@@ -911,7 +915,7 @@ class Manager {
       try {
         redirectUri = new URL(redirectUri);
         ctx.session.redirectUri = new URL(ctx.session.redirectUri);
-      } catch (e) {
+      } catch (e) { // eslint-disable-line no-unused-vars
         this.logger.debug(_scope, 'un-parsable redirect_uri url', { ctx });
         delete ctx.session.redirectUri;
         Manager._setError(ctx, 'invalid_request', 'malformed redirect_url');
@@ -930,9 +934,9 @@ class Manager {
 
   /**
    * Validate grant_type, either persist on session or set error.
-   * @param {Object} ctx
-   * @param {String[]} validGrantTypes
-   * @param {Boolean} treatEmptyAs
+   * @param {object} ctx context
+   * @param {string[]} validGrantTypes grant types
+   * @param {string=} treatEmptyAs grant type
    */
   _checkGrantType(ctx, validGrantTypes = ['authorization_code'], treatEmptyAs = 'authorization_code') {
     const _scope = _fileScope('_checkGrantType');
@@ -950,7 +954,7 @@ class Manager {
 
 
   /**
-   * @param {Object} ctx
+   * @param {object} ctx context
    */
   _checkSessionMatchingCodeVerifier(ctx) {
     const _scope = _fileScope('_checkSessionMatchingCodeVerifier');
@@ -981,9 +985,9 @@ class Manager {
 
   /**
    * Attempt to revoke a token.
-   * @param {*} dbCtx
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {*} dbCtx db context
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async _revokeToken(dbCtx, res, ctx) {
     const _scope = _fileScope('_revokeToken');
@@ -1039,10 +1043,10 @@ class Manager {
 
   /**
    * Legacy token validation flow.
-   * @param {*} dbCtx
-   * @param {http.ClientRequest} req 
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {*} dbCtx db context
+   * @param {http.ClientRequest} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async _validateToken(dbCtx, req, res, ctx) {
     const _scope = _fileScope('_validateToken');
@@ -1075,9 +1079,9 @@ class Manager {
   /**
    * Given a list of newly-requested scopes, return a list of scopes
    * from previousScopes which are not in requestedScopes.
-   * @param {String[]} previousScopes
-   * @param {String[]} requestedScopes
-   * @returns {String[]}
+   * @param {string[]} previousScopes scopes
+   * @param {string[]} requestedScopes scopes
+   * @returns {string[]} scopes
    */
   static _scopeDifference(previousScopes, requestedScopes) {
     const scopesToRemove = [];
@@ -1095,10 +1099,10 @@ class Manager {
 
   /**
    * Redeem a refresh token for a new token.
-   * @param {*} dbCtx
-   * @param {http.ClientRequest} req 
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {*} dbCtx db context
+   * @param {http.ClientRequest} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async _refreshToken(dbCtx, req, res, ctx) {
     const _scope = _fileScope('_refreshToken');
@@ -1192,15 +1196,15 @@ class Manager {
 
   /**
    * Generate a new ticket for later redemption.
-   * @param {Object} payload
-   * @param {} payload.subject deliver ticket to this endpoint
-   * @param {} payload.resource url the redeemed ticket is valid for accessing
-   * @param {String[]} payload.scopes list of scopes assigned to ticket
-   * @param {String} payload.identifier user generating ticket
-   * @param {} payload.profile profile of user generating ticket
-   * @param {Number} payload.ticketLifespanSeconds ticket redeemable for this long
-   * @returns {Promise<String>}
-  */
+   * @param {object} payload payload
+   * @param {string} payload.subject deliver ticket to this endpoint
+   * @param {string} payload.resource url the redeemed ticket is valid for accessing
+   * @param {string[]} payload.scopes list of scopes assigned to ticket
+   * @param {string} payload.identifier user generating ticket
+   * @param {string} payload.profile profile of user generating ticket
+   * @param {number} payload.ticketLifespanSeconds ticket redeemable for this long
+   * @returns {Promise<string>} ticket
+   */
   async _mintTicket({ subject, resource, scopes, identifier, profile, ticketLifespanSeconds }) {
     const _scope = _fileScope('_mintTicket');
     this.logger.debug(_scope, 'called', { subject, resource, scopes, identifier, profile, ticketLifespanSeconds });
@@ -1221,19 +1225,19 @@ class Manager {
 
   /**
    * @typedef Ticket
-   * @property {String} codeId
-   * @property {Date} issued
-   * @property {Date} expires
-   * @property {URL} subject
-   * @property {URL} resource
-   * @property {String[]} scopes
-   * @property {String} identifier
-   * @property {URL} profile
+   * @property {string} codeId code id
+   * @property {Date} issued issued at
+   * @property {Date} expires expires at
+   * @property {URL} subject subject
+   * @property {URL} resource resource
+   * @property {string[]} scopes scopes
+   * @property {string} identifier identifier
+   * @property {URL} profile profile
    */
   /**
    * 
-   * @param {String} ticket
-   * @returns {Promise<Ticket>}
+   * @param {string} ticket ticket
+   * @returns {Promise<Ticket>} ticket object
    */
   async _unpackTicket(ticket) {
     const ticketObj = await this.mysteryBox.unpack(ticket);
@@ -1252,10 +1256,11 @@ class Manager {
 
   /**
    * Redeem a ticket for a token.
-   * @param {*} dbCtx
-   * @param {http.ClientRequest} req 
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {*} dbCtx db context
+   * @param {http.ClientRequest} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
+   * @returns {Promise<void>} 
    */
   async _ticketAuthToken(dbCtx, req, res, ctx) {
     const _scope = _fileScope('_ticketAuthToken');
@@ -1310,10 +1315,10 @@ class Manager {
 
   /**
    * Redeem a code for a token.
-   * @param {*} dbCtx
-   * @param {http.ClientRequest} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {*} dbCtx db context
+   * @param {http.ClientRequest} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async _codeToken(dbCtx, req, res, ctx) {
     const _scope = _fileScope('_codeToken');
@@ -1393,9 +1398,9 @@ class Manager {
 
   /**
    * Issue, refresh, or validate a token.
-   * @param {http.ClientRequest} req 
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ClientRequest} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async postToken(req, res, ctx) {
     const _scope = _fileScope('postToken');
@@ -1451,9 +1456,10 @@ class Manager {
    * Ingest token from authorization header, setting ctx.bearer.isValid appropriately.
    * ctx.bearer not set if auth method not recognized.
    * This is for legacy validation on token endpoint.
-   * @param {*} dbCtx
-   * @param {http.ClientRequest} req
-   * @param {Object} ctx
+   * @param {*} dbCtx db context
+   * @param {http.ClientRequest} req request
+   * @param {object} ctx context
+   * @returns {Promise<void>}
    */
   async _checkTokenValidationRequest(dbCtx, req, ctx) {
     const _scope = _fileScope('_checkTokenValidationRequest');
@@ -1468,7 +1474,7 @@ class Manager {
           };  
           try {
             Object.assign(ctx.bearer, await this.mysteryBox.unpack(authString));
-          } catch (e) {
+          } catch (e) { // eslint-disable-line no-unused-vars
             this.logger.debug(_scope, 'failed to unpack token', { ctx });
             Manager._setError(ctx, 'invalid_request', 'invalid token');
             return;
@@ -1509,9 +1515,9 @@ class Manager {
 
   /**
    * Accept an unsolicited ticket proffering.
-   * @param {http.ClientRequest} req 
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ClientRequest} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async postTicket(req, res, ctx) {
     const _scope = _fileScope('postTicket');
@@ -1528,14 +1534,14 @@ class Manager {
     if (iss) {
       try {
         new URL(iss);
-      } catch (e) {
+      } catch (e) { // eslint-disable-line no-unused-vars
         this.logger.debug(_scope, 'unparsable issuer', { ticket, resource, subject, iss, ctx });
         // continue, will try resource for metadata
       }
     }
     try {
       new URL(resource);
-    } catch (e) {
+    } catch (e) { // eslint-disable-line no-unused-vars
       this.logger.debug(_scope, 'unparsable resource', { ticket, resource, subject, ctx });
       throw new ResponseError(Enum.ErrorResponse.BadRequest);
     }
@@ -1562,11 +1568,15 @@ class Manager {
   }
 
 
+  /**
+   * @typedef {object} AMQPChannel
+   * @property {Function} ack ack
+   */
   /**
    * Process messages from proffered ticket queue.
    * Attempt to redeem ticket and publish to redeemed token queue.
-   * @param {AMQPChannel} channel
-   * @param {Buffer} message
+   * @param {AMQPChannel} channel channel
+   * @param {Buffer} message message
    */
   async queuedTicketProcessor(channel, message) {
     const _scope = _fileScope('queuedTicketProcessor');
@@ -1601,7 +1611,7 @@ class Manager {
     let resourceUrlObj;
     try {
       resourceUrlObj = new URL(resource);
-    } catch (e) {
+    } catch (e) { // eslint-disable-line no-unused-vars
       this.logger.error(_scope, 'unparsable resource, discarding', { payload });
       channel.ack(message);
       return;
@@ -1662,8 +1672,8 @@ class Manager {
 
   /**
    * Validate a token and return data about it.
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async postIntrospection(res, ctx) {
     const _scope = _fileScope('postIntrospection');
@@ -1718,8 +1728,8 @@ class Manager {
 
   /**
    * Revoke a token or refresh token.
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async postRevocation(res, ctx) {
     const _scope = _fileScope('postRevocation');
@@ -1740,8 +1750,8 @@ class Manager {
 
   /**
    * Profile information for a token.
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async postUserInfo(res, ctx) {
     const _scope = _fileScope('postUserInfo');
@@ -1800,8 +1810,8 @@ class Manager {
 
   /**
    * Show admin interface, allowing manipulation of profiles and scopes.
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async getAdmin(res, ctx) {
     const _scope = _fileScope('getAdmin');
@@ -1822,8 +1832,8 @@ class Manager {
   
   /**
    * Process admin interface events.
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async postAdmin(res, ctx) {
     const _scope = _fileScope('postAdmin');
@@ -1952,8 +1962,8 @@ class Manager {
 
   /**
    * Show ticket proffer interface.
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async getAdminTicket(res, ctx) {
     const _scope = _fileScope('getAdminTicket');
@@ -1975,8 +1985,8 @@ class Manager {
 
   /**
    * Handle ticket proffer interface submission.
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async postAdminTicket(res, ctx) {
     const _scope = _fileScope('postAdminTicket');
@@ -1992,7 +2002,7 @@ class Manager {
         ].forEach((param) => {
           try {
             ctx[param.ctxProp] = new URL(ctx.parsedBody[param.bodyParam]);
-          } catch (e) {
+          } catch (e) { // eslint-disable-line no-unused-vars
             this.logger.debug(_scope, `invalid ${param.bodyParam}`, { ctx });
             ctx.errors.push(param.err);
           }
@@ -2087,8 +2097,8 @@ class Manager {
   /**
    * Report on generally uninteresting backend information.
    * Also allow a few event invocations.
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async getAdminMaintenance(res, ctx) {
     const _scope = _fileScope('getAdminMaintenance');
@@ -2125,8 +2135,8 @@ class Manager {
 
   /**
    * 
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx 
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async getHealthcheck(res, ctx) {
     const _scope = _fileScope('getHealthcheck');
index dc131c35c76a5950f67fa37371a2074e8a12b0d7..f3b7be0d4ef8770eca761dd05e98c6c286b070bd 100644 (file)
@@ -9,14 +9,17 @@ const path = require('path');
 const { Dingus } = require('@squeep/api-dingus');
 const common = require('./common');
 const Manager = require('./manager');
-const { Authenticator, SessionManager } = require('@squeep/authentication-module');
-const { ResourceAuthenticator } = require('@squeep/resource-authentication-module');
+const { Authenticator, ResourceAuthenticator, SessionManager } = require('@squeep/authentication-module');
 const { initContext, navLinks } = require('./template/template-helper');
 const Enum = require('./enum');
 const { ResponseError } = require('./errors');
 
 const _fileScope = common.fileScope(__filename);
 
+/**
+ * @typedef {import('node:http')} http
+ */
+
 class Service extends Dingus {
   constructor(logger, db, options, asyncLocalStorage) {
     super(logger, {
@@ -103,9 +106,9 @@ class Service extends Dingus {
 
   /**
    * Do a little more on each request.
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async preHandler(req, res, ctx) {
     const _scope = _fileScope('preHandler');
@@ -125,9 +128,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerGetAdminLogin(req, res, ctx) {
     const _scope = _fileScope('handlerGetAdminLogin');
@@ -142,9 +145,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerPostAdminLogin(req, res, ctx) {
     const _scope = _fileScope('handlerPostAdminLogin');
@@ -163,9 +166,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerGetAdminSettings(req, res, ctx) {
     const _scope = _fileScope('handlerGetAdminSettings');
@@ -182,9 +185,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerPostAdminSettings(req, res, ctx) {
     const _scope = _fileScope('handlerPostAdminSettings');
@@ -202,9 +205,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerGetAdminLogout(req, res, ctx) {
     const _scope = _fileScope('handlerGetAdminLogout');
@@ -221,9 +224,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerGetAdmin(req, res, ctx) {
     const _scope = _fileScope('handlerGetAdmin');
@@ -240,9 +243,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerPostAdmin(req, res, ctx) {
     const _scope = _fileScope('handlerPostAdmin');
@@ -260,9 +263,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerGetAdminTicket(req, res, ctx) {
     const _scope = _fileScope('handlerGetAdminTicket');
@@ -279,9 +282,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerPostAdminTicket(req, res, ctx) {
     const _scope = _fileScope('handlerPostAdminTicket');
@@ -299,9 +302,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerGetMeta(req, res, ctx) {
     const _scope = _fileScope('handlerGetMeta');
@@ -321,9 +324,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerGetAuthorization(req, res, ctx) {
     const _scope = _fileScope('handlerGetAuthorization');
@@ -340,9 +343,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerPostAuthorization(req, res, ctx) {
     const _scope = _fileScope('handlerPostAuthorization');
@@ -364,9 +367,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerPostConsent(req, res, ctx) {
     const _scope = _fileScope('handlerPostConsent');
@@ -386,9 +389,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerPostTicket(req, res, ctx) {
     const _scope = _fileScope('handlerPostTicket');
@@ -408,9 +411,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerPostToken(req, res, ctx) {
     const _scope = _fileScope('handlerPostToken');
@@ -430,9 +433,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerPostRevocation(req, res, ctx) {
     const _scope = _fileScope('handlerPostRevocation');
@@ -452,9 +455,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerPostIntrospection(req, res, ctx) {
     const _scope = _fileScope('handlerPostIntrospection');
@@ -476,9 +479,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerPostUserInfo(req, res, ctx) {
     const _scope = _fileScope('handlerPostUserInfo');
@@ -498,9 +501,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerGetRoot(req, res, ctx) {
     const _scope = _fileScope('handlerGetRoot');
@@ -521,6 +524,9 @@ class Service extends Dingus {
 
   /**
    * Temporary to see what an unsolicited payload contains.
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerWhaGwan(req, res, ctx) {
     this.setResponseType(this.responseTypes, req, res, ctx);
@@ -529,9 +535,9 @@ class Service extends Dingus {
   }
 
   /**
-   * @param {http.IncomingMessage} req 
-   * @param {http.ServerResponse} res 
-   * @param {Object} ctx 
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerGetHealthcheck(req, res, ctx) {
     const _scope = _fileScope('handlerGetHealthcheck');
@@ -544,9 +550,9 @@ class Service extends Dingus {
 
 
   /**
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerGetAdminMaintenance(req, res, ctx) {
     const _scope = _fileScope('handlerGetAdminMaintenance');
@@ -567,9 +573,9 @@ class Service extends Dingus {
    * Intercept this and redirect if we have enough information, otherwise default to framework.
    * Fixing this will likely have to wait until an e2e test framework is in place.
    * The redirect attempt should probably be contained in a Manager method, but here it is for now.
-   * @param {http.IncomingMessage} req
-   * @param {http.ServerResponse} res
-   * @param {Object} ctx
+   * @param {http.IncomingMessage} req request
+   * @param {http.ServerResponse} res response
+   * @param {object} ctx context
    */
   async handlerInternalServerError(req, res, ctx) {
     const _scope = _fileScope('handlerInternalServerError');
index 3b16a2834411a431fe1d2c661af9847b17dd3572..f72102f27f8bf09883b930b2eeed054d4f5f6735 100644 (file)
@@ -10,11 +10,23 @@ const th = require('./template-helper');
 const { sessionNavLinks } = require('@squeep/authentication-module');
 
 
+/**
+ *
+ * @param {string} profile profile
+ * @returns {string} li
+ */
 function renderProfileLI(profile) {
   return `\t<li><a class="uri" id="${profile}">${profile}</a></li>`;
 }
 
 
+/**
+ *
+ * @param {string} profile profile
+ * @param {string} scope scope
+ * @param {boolean} selected is selected
+ * @returns {string} td
+ */
 function renderProfileScopeIndicator(profile, scope, selected) {
   const checked = selected ? ' checked' : '';
   return `\t\t<td>
@@ -22,6 +34,13 @@ function renderProfileScopeIndicator(profile, scope, selected) {
 \t\t</td>`;
 }
 
+/**
+ *
+ * @param {string} scope scope
+ * @param {object} details details
+ * @param {string[]} profiles profiles
+ * @returns {string} tr
+ */
 function renderScopeRow(scope, details, profiles) {
   return `\t<tr class="scope">
 ${(profiles || []).map((profile) => renderProfileScopeIndicator(profile, scope, details.profiles.includes(profile))).join('\n')}
@@ -37,6 +56,11 @@ ${(profiles || []).map((profile) => renderProfileScopeIndicator(profile, scope,
 }
 
 
+/**
+ *
+ * @param {string} profile profile
+ * @returns {string} th
+ */
 function renderProfileHeader(profile) {
   return `<th scope="col" class="vertical uri">
 \t\t${profile}
@@ -44,6 +68,12 @@ function renderProfileHeader(profile) {
 }
 
 
+/**
+ *
+ * @param {object} scopeIndex scopes
+ * @param {string[]} profiles profiles
+ * @returns {string} table
+ */
 function scopeIndexTable(scopeIndex, profiles) {
   return `<table>
 <thead>
@@ -61,6 +91,11 @@ ${Object.entries(scopeIndex).sort(th.scopeCompare).map(([scope, details]) => ren
 </table>`;
 }
 
+/**
+ *
+ * @param {object} token token
+ * @returns {string} type
+ */
 function _tokenType(token) {
   if (token.resource) {
     return 'ticket-token';
@@ -71,6 +106,11 @@ function _tokenType(token) {
   return 'token';
 }
 
+/**
+ *
+ * @param {object} token token
+ * @returns {string} tr
+ */
 function renderTokenRow(token) {
   const createdTitle = token.refreshed ? 'Refreshed At' : 'Created At';
   const createdDate = token.refreshed ? token.refreshed : token.created;
@@ -91,12 +131,20 @@ function renderTokenRow(token) {
 \t\t</tr>`;
 }
 
+/**
+ * @returns {string} tr
+ */
 function noTokensRows() {
   return [`\t\t<tr>
 \t\t\t<td colspan="10" class="centered">(No active or recent tokens.)</td>
 \t\t</tr>`];
 }
 
+/**
+ *
+ * @param {object} tokens tokens
+ * @returns {string} table
+ */
 function tokenTable(tokens) {
   const tokenRows = tokens?.length ? tokens.map((token) => renderTokenRow(token)) : noTokensRows();
   const formOpen = tokens?.length ? '<form method="POST">\n' : '';
@@ -122,6 +170,11 @@ ${tokenRows.join('\n')}
 </table>${formClose}`;
 }
 
+/**
+ *
+ * @param {object} ctx context
+ * @returns {string} section
+ */
 function mainContent(ctx) {
   const profileList = (ctx.profilesScopes?.profiles || []).map((p) => renderProfileLI(p)).join('\n');
   return `<section>
@@ -186,16 +239,16 @@ ${tokenTable(ctx.tokens)}
 
 /**
  * 
- * @param {Object} ctx
- * @param {Object} ctx.profilesScopes.scopeIndex
- * @param {String[]} ctx.profilesScopes.profiles
- * @param {Object[]} ctx.tokens
- * @param {Object} options
- * @param {Object} options.manager
- * @param {String} options.manager.pageTitle
- * @param {String} options.manager.logoUrl
- * @param {String[]} options.manager.footerEntries
- * @returns {String}
+ * @param {object} ctx context
+ * @param {object} ctx.profilesScopes.scopeIndex scopes
+ * @param {string[]} ctx.profilesScopes.profiles profiles
+ * @param {object[]} ctx.tokens tokens
+ * @param {object} options options
+ * @param {object} options.manager manager options
+ * @param {string} options.manager.pageTitle page title
+ * @param {string} options.manager.logoUrl logo url
+ * @param {string[]} options.manager.footerEntries footer entries
+ * @returns {string} page
  */
 module.exports = (ctx, options) => {
   const pagePathLevel = 1;
index 764b4d32870c06e57f211643a4878f1fcaa2667e..0f4de44ce599fa62b745f1b63dab9d8ec90a2ca4 100644 (file)
@@ -3,6 +3,11 @@
 const th = require('./template-helper');
 const { sessionNavLinks } = require('@squeep/authentication-module');
 
+/**
+ *
+ * @param {object} entry entry
+ * @returns {string} tr
+ */
 function renderAlmanacRow(entry) {
   const { event, date } = entry;
   return `<tr>
@@ -11,6 +16,11 @@ function renderAlmanacRow(entry) {
 </tr>`;
 }
 
+/**
+ *
+ * @param {object[]} almanac entries
+ * @returns {string} section
+ */
 function almanacSection(almanac) {
   return `<section>
 \t<h2>Almanac</h2>
@@ -28,6 +38,12 @@ ${almanac.map((entry) => renderAlmanacRow(entry)).join('\n')}
 </section>`;
 }
 
+/**
+ *
+ * @param {string} choreName name
+ * @param {object} choreDetails details
+ * @returns {string} tr
+ */
 function renderChoreRow(choreName, choreDetails) {
   const { intervalMs, nextSchedule } = choreDetails;
   return `<tr>
@@ -37,6 +53,11 @@ function renderChoreRow(choreName, choreDetails) {
 </tr>`;
 }
 
+/**
+ *
+ * @param {object} chores chores
+ * @returns {string} section
+ */
 function choresSection(chores) {
   return `<section>
 \t<h2>Chores</h2>
@@ -57,15 +78,14 @@ ${Object.entries(chores).map((chore) => renderChoreRow(...chore)).join('\n')}
 
 /**
  * 
- * @param {Object} ctx
- * @param {Object[]} ctx.almanac
- * @param {Object} ctx.chores
- * @param {Object} options
- * @param {Object} options.manager
- * @param {String} options.manager.pageTitle
- * @param {String[]} options.manager.footerEntries
- * @param {String} options.adminContactHTML
- * @returns {String}
+ * @param {object} ctx context
+ * @param {object[]} ctx.almanac entries
+ * @param {object} ctx.chores chores
+ * @param {object} options options
+ * @param {object} options.manager manager options
+ * @param {string} options.manager.pageTitle page title
+ * @param {string[]} options.manager.footerEntries footer entires
+ * @returns {string} page
  */
 module.exports = (ctx, options) => {
   const pagePathLevel = 1;
index 4bb2d0660032b9d23cdde24bfe78443b61fef956..75d7ce7f7475e957f2e8020d2943c476c130878a 100644 (file)
@@ -8,10 +8,20 @@ const th = require('./template-helper');
 const { sessionNavLinks } = require('@squeep/authentication-module');
 
 
+/**
+ *
+ * @param {string} profile profile
+ * @returns {string} option
+ */
 function renderProfileOption(profile) {
   return `<option value="${profile}">${profile}</option>`;
 }
 
+/**
+ *
+ * @param {string} scope scope
+ * @returns {string} tr
+ */
 function renderScopeCheckboxTR(scope) {
   const defaultChecked = ['read'];
   const checked = defaultChecked.includes(scope) ? ' checked' : '';
@@ -21,6 +31,11 @@ function renderScopeCheckboxTR(scope) {
 </tr>`;
 }
 
+/**
+ *
+ * @param {object} ctx context
+ * @returns {string} section
+ */
 function mainContent(ctx) {
   const profileOptions = th.indented(4, (ctx?.profilesScopes?.profiles || []).map((profile) => renderProfileOption(profile)))
     .join('\n');
@@ -71,15 +86,15 @@ ${scopesCheckboxRows}
 
 /**
  * 
- * @param {Object} ctx
- * @param {Object} ctx.profilesScopes.scopeIndex
- * @param {String[]} ctx.profileScopes.profiles
- * @param {Object} options
- * @param {Object} options.manager
- * @param {String} options.manager.pageTitle
- * @param {String} options.manager.logoUrl
- * @param {String[]} options.manager.footerEntries
- * @returns {String}
+ * @param {object} ctx context
+ * @param {object} ctx.profilesScopes.scopeIndex scopes structure
+ * @param {string[]} ctx.profileScopes.profiles profile
+ * @param {object} options options
+ * @param {object} options.manager manager options
+ * @param {string} options.manager.pageTitle page title
+ * @param {string} options.manager.logoUrl logo url
+ * @param {string[]} options.manager.footerEntries footer entries
+ * @returns {string} page
  */
 module.exports = (ctx, options) => {
   const pagePathLevel = 1;
index 16e3c4870bdb300d9ef031d55a628e40d2f2f205..83d952656fac5eef6ab2932fdc8967671783d7cc 100644 (file)
@@ -5,15 +5,15 @@ const { sessionNavLinks } = require('@squeep/authentication-module');
 
 /**
  * 
- * @param {Object} ctx
- * @param {Object} ctx.session
- * @param {String=} ctx.session.error
- * @param {String[]=} ctx.session.errorDescriptions
- * @param {Object} options
- * @param {Object} options.manager
- * @param {String} options.manager.pageTitle
- * @param {String} options.manager.footerEntries
- * @returns {String}
+ * @param {object} ctx context
+ * @param {object} ctx.session session
+ * @param {string=} ctx.session.error errors
+ * @param {string[]=} ctx.session.errorDescriptions errors
+ * @param {object} options options
+ * @param {object} options.manager manager options
+ * @param {string} options.manager.pageTitle page title
+ * @param {string} options.manager.footerEntries footer entries
+ * @returns {string} page
  */
 module.exports = (ctx, options) => {
   const pagePathLevel = 0;
index ac2ea064efe6cc0109c6c3db49c21db6cf0bbd49..b23fb9207665077ce54743e39563ff3fe2b9e4a8 100644 (file)
@@ -4,13 +4,13 @@ const th = require('./template-helper');
 const { sessionNavLinks } = require('@squeep/authentication-module');
 
 /**
- * @param {Object} hApp
- * @param {Object} hApp.properties
- * @param {String[]=} hApp.properties.url
- * @param {String[]=} hApp.properties.summary
- * @param {String[]=} hApp.properties.logo
- * @param {String[]=} hApp.properties.name
- * @returns {String}
+ * @param {object} hApp client identifier h-app
+ * @param {object} hApp.properties properties
+ * @param {string[]=} hApp.properties.url url
+ * @param {string[]=} hApp.properties.summary summary
+ * @param {string[]=} hApp.properties.logo logo
+ * @param {string[]=} hApp.properties.name name
+ * @returns {string} span
  */
 function renderClientIdentifierProperties(hApp) {
   const properties = hApp.properties || {};
@@ -47,9 +47,9 @@ function renderClientIdentifierProperties(hApp) {
 
 
 /**
- * @param {Object} clientIdentifier 
- * @param {Object[]} clientIdentifier.items
- * @returns {String}
+ * @param {object} clientIdentifier client identifier
+ * @param {object[]} clientIdentifier.items items
+ * @returns {string} spans
  */
 function renderClientIdentifier(clientIdentifier) {
   const hAppEntries = clientIdentifier?.items || [];
@@ -58,9 +58,9 @@ function renderClientIdentifier(clientIdentifier) {
 
 
 /**
- * @param {String} profile
- * @param {Boolean} selected
- * @returns {String}
+ * @param {string} profile profile
+ * @param {boolean} selected is selected
+ * @returns {string} option
  */
 function renderProfileOption(profile, selected) {
   return `<option value="${profile}"${selected ? ' selected' : ''}>${profile}</option>`;
@@ -68,9 +68,9 @@ function renderProfileOption(profile, selected) {
 
 
 /**
- * @param {String[]} availableProfiles
- * @param {String} hintProfile
- * @returns {String}
+ * @param {string[]} availableProfiles profiles
+ * @param {string} hintProfile profile
+ * @returns {string} fieldset
  */
 function renderProfileFieldset(availableProfiles, hintProfile) {
   if (!availableProfiles || availableProfiles.length <= 1) {
@@ -93,12 +93,16 @@ ${availableProfiles.map((profile) => renderProfileOption(profile, profile === hi
 
 
 /**
- * @param {ScopeDetails} scope
- * @param {String} scope.scope
- * @param {String} scope.description
- * @param {String[]} scope.profiles
- * @param {Boolean} checked
- * @returns {String}
+ * @typedef {object} ScopeDetails
+ * @property {string} scope scope
+ * @property {string} description description
+ * @property {string[]} profiles profiles
+ */
+
+/**
+ * @param {ScopeDetails} scope scope details
+ * @param {boolean} checked is checked
+ * @returns {string} scope li
  */
 function renderScopeCheckboxLI(scope, checked) {
   let scopeDescription;
@@ -122,6 +126,11 @@ function renderScopeCheckboxLI(scope, checked) {
 }
 
 
+/**
+ *
+ * @param {ScopeDetails[]=} requestedScopes scope details
+ * @returns {string} fieldset
+ */
 function renderRequestedScopes(requestedScopes) {
   if (!requestedScopes?.length) {
     return '';
@@ -140,8 +149,8 @@ ${requestedScopes.map((scopeDetails) => renderScopeCheckboxLI(scopeDetails, true
 }
 
 /**
- * @param {ScopeDetails[]} additionalScopes
- * @returns {String}
+ * @param {ScopeDetails[]} additionalScopes scopes
+ * @returns {string} fieldset
  */
 function renderAdditionalScopes(additionalScopes) {
   const parts = [];
@@ -173,6 +182,8 @@ ${additionalScopes.map((scopeDetails) => renderScopeCheckboxLI(scopeDetails, fal
 
 /**
  * 
+ * @param {string[]} requestedScopes scopes
+ * @returns {string} fieldset
  */
 function renderExpiration(requestedScopes) {
   const tokenableScopes = requestedScopes.filter((s) => !['profile', 'email'].includes(s));
@@ -215,6 +226,15 @@ function renderExpiration(requestedScopes) {
 \t</fieldset>`;
 }
 
+/**
+ *
+ * @param {string} name name
+ * @param {string} value value
+ * @param {string} label label
+ * @param {boolean} checked is checked
+ * @param {number} indent indent
+ * @returns {string} div
+ */
 function radioButton(name, value, label, checked = false, indent = 0) {
   const id = `${name}-${value}`;
   return th.indented(indent, [
@@ -225,27 +245,31 @@ function radioButton(name, value, label, checked = false, indent = 0) {
   ]).join('');
 }
 
+/**
+ * @alias {object} ScopeIndex
+ */
+
 /**
  * 
- * @param {Object} ctx
- * @param {Object[]} ctx.notifications
- * @param {Object} ctx.session
- * @param {String[]=} ctx.session.scope
- * @param {URL=} ctx.session.me
- * @param {String[]} ctx.session.profiles
- * @param {ScopeIndex} ctx.session.scopeIndex
- * @param {Object} ctx.session.clientIdentifier
- * @param {Object[]} ctx.session.clientIdentifier.items
- * @param {Object} ctx.session.clientIdentifier.items.properties
- * @param {String[]=} ctx.session.clientIdentifier.items.properties.url
- * @param {String[]=} ctx.session.clientIdentifier.items.properties.summary
- * @param {String[]=} ctx.session.clientIdentifier.items.properties.logo
- * @param {String[]=} ctx.session.clientIdentifier.items.properties.name
- * @param {String} ctx.session.clientId
- * @param {String} ctx.session.persist
- * @param {String} ctx.session.redirectUri
- * @param {Object} options
- * @returns {String}
+ * @param {object} ctx context
+ * @param {object[]} ctx.notifications notifications
+ * @param {object} ctx.session session
+ * @param {string[]=} ctx.session.scope scopes
+ * @param {URL=} ctx.session.me profile
+ * @param {string[]} ctx.session.profiles profiles
+ * @param {ScopeIndex} ctx.session.scopeIndex scopes structure
+ * @param {object} ctx.session.clientIdentifier client identifier
+ * @param {object[]} ctx.session.clientIdentifier.items items
+ * @param {object} ctx.session.clientIdentifier.items.properties properties
+ * @param {string[]=} ctx.session.clientIdentifier.items.properties.url url
+ * @param {string[]=} ctx.session.clientIdentifier.items.properties.summary sumamry
+ * @param {string[]=} ctx.session.clientIdentifier.items.properties.logo logo
+ * @param {string[]=} ctx.session.clientIdentifier.items.properties.name name
+ * @param {string} ctx.session.clientId client id
+ * @param {string} ctx.session.persist persist
+ * @param {string} ctx.session.redirectUri redirect
+ * @param {object} options options
+ * @returns {string} section
  */
 function mainContent(ctx, options) { // eslint-disable-line no-unused-vars
   const session = ctx.session || {};
@@ -283,7 +307,7 @@ function mainContent(ctx, options) { // eslint-disable-line no-unused-vars
 
   return [
     `<section class="information">
-\tThe application client ${renderClientIdentifier(session.clientIdentifier)} at <a class="uri" aria-label="client-identifier" id="${session.clientId}">${session.clientId}</a> would like to identify you as <a class="uri" aria-label="profile"${hintedProfile ? ' id="' + hintedProfile + '"' : ''}>${hintedProfile ? hintedProfile : '(unspecified)'}</a>.
+\tThe application client ${renderClientIdentifier(session.clientIdentifier)} at <a class="uri" aria-label="client-identifier" id="${session.clientId}">${session.clientId}</a> would like to identify you as <a class="uri" aria-label="profile"${hintedProfile ? ' id="' + hintedProfile + '"' : ''}>${hintedProfile || '(unspecified)'}</a>.
 </section>
 <section class="choices">
 \t<form action="consent" method="POST" class="form-consent">`,
@@ -310,21 +334,21 @@ function mainContent(ctx, options) { // eslint-disable-line no-unused-vars
 
 /**
  * 
- * @param {Object} ctx
- * @param {Object} ctx.session
- * @param {String[]=} ctx.session.scope
- * @param {URL=} ctx.session.me
- * @param {String[]} ctx.session.profiles
- * @param {ScopeIndex} ctx.session.scopeIndex
- * @param {Object} ctx.session.clientIdentifier
- * @param {String} ctx.session.clientId
- * @param {String} ctx.session.persist
- * @param {String} ctx.session.redirectUri
- * @param {Object} options
- * @param {Object} options.manager
- * @param {String} options.manager.pageTitle
- * @param {String} options.manager.footerEntries
- * @returns {String}
+ * @param {object} ctx context
+ * @param {object} ctx.session session object
+ * @param {string[]=} ctx.session.scope scopes
+ * @param {URL=} ctx.session.me url
+ * @param {string[]} ctx.session.profiles profiles
+ * @param {ScopeIndex} ctx.session.scopeIndex scopes structure
+ * @param {object} ctx.session.clientIdentifier client identifier
+ * @param {string} ctx.session.clientId client id
+ * @param {string} ctx.session.persist persist
+ * @param {string} ctx.session.redirectUri redirect url
+ * @param {object} options options
+ * @param {object} options.manager manager options
+ * @param {string} options.manager.pageTitle page title
+ * @param {string} options.manager.footerEntries footer entries
+ * @returns {string} page
  */
 module.exports = (ctx, options) => {
   const pagePathLevel = 0;
index 2b7abfe076173c5b5872b122530eedf7dabcd7ab..c6095dc4384bd59e0792a3754d55b59a76bad609 100644 (file)
@@ -3,6 +3,9 @@
 const th = require('./template-helper');
 const { sessionNavLinks } = require('@squeep/authentication-module');
 
+/**
+ * @returns {string} section
+ */
 function aboutSection() {
   return `
       <section class="about">
@@ -19,6 +22,10 @@ function aboutSection() {
       </section>`;
 }
 
+/**
+ * @param {string} contactHTML content
+ * @returns {string} section
+ */
 function contactSection(contactHTML) {
   let section = '';
   if (contactHTML) {
@@ -31,13 +38,13 @@ ${contactHTML}
 
 /**
  * 
- * @param {Object} ctx
- * @param {Object} options
- * @param {Object} options.manager
- * @param {String} options.manager.pageTitle
- * @param {String[]} options.manager.footerEntries
- * @param {String} options.adminContactHTML
- * @returns {String}
+ * @param {object} ctx context
+ * @param {object} options options
+ * @param {object} options.manager manager options
+ * @param {string} options.manager.pageTitle page title
+ * @param {string[]} options.manager.footerEntries footer entries
+ * @param {string=} options.adminContactHTML content
+ * @returns {string} page
  */
 module.exports = (ctx, options) => {
   const pagePathLevel = 0;
index f15d3e69a99e22dbdb0b9e4412ba82af79aa4f65..4c0ad9d3ec04b940765d0620ee84ed44ef30bf89 100644 (file)
@@ -5,8 +5,8 @@ const { TemplateHelper } = require('@squeep/html-template-helper');
 
 /**
  * Escape a string to be suitable as a CSS name.
- * @param {String} unsafeName 
- * @returns {String}
+ * @param {string} unsafeName unsafe name
+ * @returns {string} escaped name
  */
 function escapeCSS(unsafeName) {
   return unsafeName.replace(/([^0-9a-zA-Z-])/g, '\\$1');
@@ -18,9 +18,9 @@ function escapeCSS(unsafeName) {
  * return the comparison between the two, for sorting.
  * Scopes are sorted such that they are grouped by application, then name.
  * Empty applications are sorted ahead of extant applications.
- * @param {Array} a
- * @param {Array} b
- * @returns {Number}
+ * @param {[string, object]} a [scopeName, scopeDetails]
+ * @param {[string, object]} b [scopeName, scopeDetails]
+ * @returns {number} comparison
  */
 function scopeCompare([aScope, aDetails], [bScope, bDetails]) {
   const { application: aApp } = aDetails;
@@ -49,9 +49,9 @@ function scopeCompare([aScope, aDetails], [bScope, bDetails]) {
 
 /**
  * Populate common navLinks for page templates.
- * @param {Number} pagePathLevel
- * @param {Object} ctx 
- * @param {Object} options 
+ * @param {number} pagePathLevel depth from root
+ * @param {object} ctx context
+ * @param {object} options options
  */
 function navLinks(pagePathLevel, ctx, options) {
   if (!options.navLinks) {
index 02729908033803e449d340c1a928cf4656a5a0f9..1656088f6e807e738686aba17d638c745f159488 100644 (file)
@@ -1,5 +1,3 @@
-/* eslint-env mocha */
-/* eslint-disable node/no-unpublished-require */
 'use strict';
 
 const assert = require('assert');
index bf74e973033ae70d3f734c8284a5861bdb9c9c55..b80850788238d5061614224cec14d850115cc9d6 100644 (file)
@@ -1,8 +1,8 @@
-/* eslint-env mocha */
+/* eslint-disable sonarjs/no-duplicate-string */
 'use strict';
 
 const assert = require('assert');
-const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
+const sinon = require('sinon');
 
 const StubDatabase = require('../../stub-db');
 const StubLogger = require('../../stub-logger');
index 31fdd8d6d8df2aefab933a9d6b0262f24b423937..0d706ddad0e8ef92448d4cf8bc62d58b71f0e84a 100644 (file)
@@ -2,7 +2,7 @@
 'use strict';
 
 const assert = require('assert');
-const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
+const sinon = require('sinon');
 const Logger = require('@squeep/logger-json-console');
 const DB = require('../../../src/db');
 const DBErrors = require('../../../src/db/errors');
index 10e88dfc06c40c84d7e65d48b0d27109fc6b44e7..c26e277878f344573481c8ae0b1196bc43b51a7a 100644 (file)
@@ -1,5 +1,4 @@
-/* eslint-env mocha */
-/* eslint-disable sonarjs/no-identical-functions */
+/* eslint-disable security/detect-object-injection */
 'use strict';
 
 /**
@@ -17,7 +16,7 @@
  */
 
 const assert = require('assert');
-const { step } = require('mocha-steps'); // eslint-disable-line node/no-unpublished-require
+const { step } = require('mocha-steps');
 const StubLogger = require('../../stub-logger');
 // const DBErrors = require('../../../src/db/errors');
 // const testData = require('../../test-data/db-integration');
index c00daad11d7de666c3bd5f6ba19553cb5fc5272e..caa665266999f9d8f51f7201ee84882dcb8e52a3 100644 (file)
@@ -1,12 +1,10 @@
-/* eslint-disable sonarjs/no-identical-functions */
-/* eslint-env mocha */
 /* eslint-disable sonarjs/no-duplicate-string */
 'use strict';
 
 /* This provides implementation coverage, stubbing pg-promise. */
 
 const assert = require('assert');
-const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
+const sinon = require('sinon');
 const StubLogger = require('../../stub-logger');
 const StubDatabase = require('../../stub-db');
 const DB = require('../../../src/db/postgres');
@@ -281,7 +279,7 @@ describe('DatabasePostgres', function () {
       assert(db.db.batch.called);
     });
     it('failure', async function () {
-      sinon.stub(db.db, 'tx').rejects(expectedException)
+      sinon.stub(db.db, 'tx').rejects(expectedException);
       await assert.rejects(() => db._purgeTables(true), expectedException);
     });
   }); // _purgeTables
@@ -318,7 +316,7 @@ describe('DatabasePostgres', function () {
     let event, date;
     beforeEach(function () {
       event = 'test_event';
-      date = new Date('Fri Dec 22 03:27 UTC 2023')
+      date = new Date('Fri Dec 22 03:27 UTC 2023');
     });
     it('success', async function () {
       const dbResult = {
@@ -823,7 +821,7 @@ describe('DatabasePostgres', function () {
         duration: 22,
       };
       sinon.stub(db.db, 'result').resolves(dbResult);
-      await db.resourceUpsert(dbCtx, resourceId, secret, description)
+      await db.resourceUpsert(dbCtx, resourceId, secret, description);
     });
     it('failure', async function () {
       const dbResult = {
index cc876d4d645e7725d415ac679ca6cbb0d0b755e1..2859d303caf481d26379810c1f8b2f73c20841c0 100644 (file)
@@ -1,8 +1,7 @@
-/* eslint-env mocha */
 'use strict';
 
 const assert = require('assert');
-const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
+const sinon = require('sinon');
 const fs = require('fs');
 const svh = require('../../../src/db/schema-version-helper');
 
@@ -121,6 +120,7 @@ describe('SchemaVersionHelper', function () {
         .onCall(i++).returns(notDir) // 'init.sql'
         .onCall(i++).returns(isDir).onCall(i++).returns(isMig) // '1.0.1'
         .onCall(i++).returns(isDir).onCall(i++).returns(isMig) // '1.0.0'
+      ;
       const result = svh.allSchemaVersions('path');
       assert.deepStrictEqual(result, expected);
     });
@@ -151,9 +151,10 @@ describe('SchemaVersionHelper', function () {
         .onCall(i++).returns(notDir) // 'init.sql'
         .onCall(i++).returns(isDir).onCall(i++).returns(isMig) // '1.0.1'
         .onCall(i++).returns(isDir).onCall(i++).returns(isMig) // '1.0.0'
+      ;
       const result = svh.unappliedSchemaVersions('path', current, supported);
       assert.deepStrictEqual(result, expected);
     });
   }); // unappliedSchemaVersions
 
-});
\ No newline at end of file
+});
index f2d95461b439915005833267a8890d60f82ff94e..46163b88e9510b53f409be139cbb2b540f9a2c1a 100644 (file)
@@ -1,12 +1,10 @@
-/* eslint-disable sonarjs/no-identical-functions */
-/* eslint-env mocha */
 /* eslint-disable sonarjs/no-duplicate-string */
 'use strict';
 
 /* This provides implementation coverage, stubbing parts of better-sqlite3. */
 
 const assert = require('assert');
-const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
+const sinon = require('sinon');
 const StubDatabase = require('../../stub-db');
 const StubLogger = require('../../stub-logger');
 const DB = require('../../../src/db/sqlite');
@@ -112,7 +110,7 @@ describe('DatabaseSQLite', function () {
       db._optimize();
       assert(db.db.pragma.called);
       assert(db.statement._optimize.all.called);
-      assert.strictEqual(db.changesSinceLastOptimize, 0n)
+      assert.strictEqual(db.changesSinceLastOptimize, 0n);
     });
   }); // _optimize
 
@@ -220,7 +218,7 @@ describe('DatabaseSQLite', function () {
     let event, date, dbResult;
     beforeEach(function () {
       event = 'test_event';
-      date = new Date('Fri Dec 22 03:27 UTC 2023')
+      date = new Date('Fri Dec 22 03:27 UTC 2023');
       sinon.stub(db.statement.almanacUpsert, 'run');
       dbResult = {
         changes: 1,
@@ -574,7 +572,7 @@ describe('DatabaseSQLite', function () {
         expires: new Date(refreshResponse.expires * 1000),
         refreshExpires: new Date(refreshResponse.refreshExpires * 1000),
         scopes: ['blah'],
-      }
+      };
       const response = db.refreshCode(dbCtx, codeId, refreshed, removeScopes);
       assert.deepStrictEqual(response, expectedResponse);
     });
@@ -584,7 +582,7 @@ describe('DatabaseSQLite', function () {
       const expectedResponse = {
         expires: new Date(refreshResponse.expires * 1000),
         refreshExpires: new Date(refreshResponse.refreshExpires * 1000),
-      }
+      };
       removeScopes = [];
       const response = db.refreshCode(dbCtx, codeId, refreshed, removeScopes);
       assert.deepStrictEqual(response, expectedResponse);
@@ -596,7 +594,7 @@ describe('DatabaseSQLite', function () {
         expires: new Date(refreshResponse.expires * 1000),
         refreshExpires: new Date(refreshResponse.refreshExpires * 1000),
         scopes: [],
-      }
+      };
       const response = db.refreshCode(dbCtx, codeId, refreshed, removeScopes);
       assert.deepStrictEqual(response, expectedResponse);
     });
@@ -723,7 +721,7 @@ describe('DatabaseSQLite', function () {
       sinon.stub(db.statement.scopeInUse, 'get');
       dbGetResult = {
         inUse: false,
-      }
+      };
       sinon.stub(db.statement.scopeDelete, 'run');
       dbRunResult = {
         changes: 1,
@@ -898,7 +896,7 @@ describe('DatabaseSQLite', function () {
     let dbResult, codeId;
     beforeEach(function () {
       codeId = '2f226616-3e79-11ec-ad0f-0025905f714a';
-      sinon.stub(db.statement.tokenRevokeByCodeId, 'run')
+      sinon.stub(db.statement.tokenRevokeByCodeId, 'run');
       dbResult = {
         changes: 1,
         lastInsertRowid: undefined,
@@ -1049,7 +1047,7 @@ describe('DatabaseSQLite', function () {
       const dbResultAlmanac = {
         ...dbResult,
         changes: 0,
-      }
+      };
       db.statement.ticketTokenPublished.run.returns(dbResult);
       db.statement.almanacUpsert.run.returns(dbResultAlmanac);
       assert.throws(() => db.ticketTokenPublished(dbCtx, redeemedData), DBErrors.UnexpectedResult);
index cb5914b3584cb100ba15e91054d9a5758005c9e4..e28308c34413258b6abb6490aa47e11b612f0378 100644 (file)
@@ -1,8 +1,8 @@
-/* eslint-env mocha */
+/* eslint-disable sonarjs/no-duplicate-string */
 'use strict';
 
 const assert = require('assert');
-const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
+const sinon = require('sinon');
 const Logger = require('../../src/logger');
 const Config = require('../../config');
 
@@ -70,7 +70,7 @@ describe('Logger', function () {
         otpConfirmKey: '1234567890123456789012',
         otpConfirmBox: 'xxxMysteryxxx',
         otpState: 'xxxMysteryxxx',
-      }
+      },
     });
     assert(logger.backend.info.called);
     assert(!logger.backend.info.args[0][0].includes('"1234567890123456789012"'));
index 66e6f59dd6ef84116b65ccc5e46a4b075a7ee7e1..f2d708b93947c3f1c3730140eac96277e5313f70 100644 (file)
@@ -1,10 +1,9 @@
-/* eslint-env mocha */
-/* eslint-disable capitalized-comments, sonarjs/no-duplicate-string, sonarjs/no-identical-functions */
+/* eslint-disable sonarjs/no-duplicate-string */
 
 'use strict';
 
 const assert = require('assert');
-const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
+const sinon = require('sinon');
 
 const Manager = require('../../src/manager');
 const Config = require('../../config');
@@ -1558,13 +1557,13 @@ describe('Manager', function () {
       manager.mysteryBox.unpack.resolves({});
       req.getHeader.returns('Bearer XXX');
       await manager._checkTokenValidationRequest(dbCtx, req, ctx);
-      assert(ctx.session.error)
+      assert(ctx.session.error);
     });
     it('covers no token', async function () {
       manager.mysteryBox.unpack.resolves({ c: 'xxx' });
       req.getHeader.returns('Bearer XXX');
       await manager._checkTokenValidationRequest(dbCtx, req, ctx);
-      assert(ctx.session.error)
+      assert(ctx.session.error);
     });
     it('covers db error', async function () {
       manager.mysteryBox.unpack.resolves({ c: 'xxx' });
index e55502913d3cccd917b94fe17a92aaeaa80c9ef8..d91112548a75c6617e6b2f92987d43b74dfc1ede 100644 (file)
@@ -1,10 +1,8 @@
-/* eslint-env mocha */
-/* eslint-disable capitalized-comments */
-
+/* eslint-disable sonarjs/no-duplicate-string */
 'use strict';
 
 const assert = require('assert');
-const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
+const sinon = require('sinon');
 const { AsyncLocalStorage } = require('node:async_hooks');
 
 const StubDb = require('../stub-db');
@@ -323,4 +321,4 @@ describe('Service', function () {
     });
   }); // handlerWhaGwan
 
-});
\ No newline at end of file
+});
index 8f5a6c24523f74629ebb6ad853e35f0e716d00c1..bf44d9fbd6032bc963fd5f67bf3a14585590db1b 100644 (file)
@@ -1,4 +1,4 @@
-/* eslint-env mocha */
+/* eslint-disable sonarjs/no-duplicate-string */
 'use strict';
 
 const assert = require('assert');
index d5eb28fdee62780dcdf2905ef20c5f8c23daa05f..41e9076aeea97ab609f51560548ee9e114d5a600 100644 (file)
@@ -1,4 +1,4 @@
-/* eslint-env mocha */
+/* eslint-disable sonarjs/no-duplicate-string */
 'use strict';
 
 const assert = require('assert');
index 4858b51c8238e061b3d166d059262b7f6f009be6..2d1567f1767bf1dbb86819146a1b284937a157fc 100644 (file)
@@ -27,7 +27,7 @@ describe('Authorization Error HTML Template', function () {
     ctx.session = {
       error: 'error_name',
       errorDescriptions: ['something went wrong', 'another thing went wrong'],
-    }
+    };
     const result = template(ctx, config);
     await lintHtml(result);
     assert(result);
index cefefef35ee90244dd178e48fb8610491d2c097c..8077310752490f4037f2c1cf6c5811a6f5b8f24b 100644 (file)
@@ -1,4 +1,4 @@
-/* eslint-env mocha */
+/* eslint-disable sonarjs/no-duplicate-string */
 'use strict';
 
 const assert = require('assert');
index 054cc47d857810bbfeb33834857f1c665eecb1e2..8eab524b5cad8243d8d4c3d89d8691539e60c94c 100644 (file)
@@ -1,7 +1,6 @@
-/* eslint-disable security/detect-object-injection */
 'use strict';
 
-const { StubDatabase: Base } = require('@squeep/test-helper'); // eslint-disable-line node/no-unpublished-require
+const { StubDatabase: Base } = require('@squeep/test-helper');
 
 class StubDatabase extends Base {
   get _stubFns() {
index 8be142f3eeec9f301cc5c7193e275c62307274a6..c1521573cac8fabc62b46d518177b22c9619f060 100644 (file)
@@ -1,10 +1,10 @@
 'use strict';
 
-const { StubLogger: Base } = require('@squeep/test-helper'); // eslint-disable-line node/no-unpublished-require
+const { StubLogger: Base } = require('@squeep/test-helper');
 
 
 class StubLogger extends Base {
 
 }
 
-module.exports = StubLogger;
\ No newline at end of file
+module.exports = StubLogger;