From 1b97f0e1d154a003aaec351accba7941f9b2ee1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0mol=C3=ADk?= Date: Sat, 4 May 2024 14:01:36 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20Rewrite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .circleci/config.yml | 127 --- .github/workflows/test.yml | 26 + .gitignore | 3 +- .npmignore | 3 - .prettierignore | 2 + .prettierrc | 5 + CHANGELOG | 15 + LICENSE | 21 + README | 45 ++ README.md | 105 --- demo/express.ts | 39 + html-preview.png | Bin 0 -> 13508 bytes package-lock.json | 1517 ++++++++++++++++++++++++++++++++++-- package.json | 49 +- src/adapters/knex.ts | 5 - src/adapters/mongoose.ts | 15 - src/healthz.test.ts | 56 ++ src/healthz.ts | 330 ++++---- src/http.test.ts | 69 ++ src/http.ts | 87 +++ src/index.ts | 36 +- tsconfig.json | 72 +- 22 files changed, 2033 insertions(+), 594 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/test.yml delete mode 100644 .npmignore create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 CHANGELOG create mode 100644 LICENSE create mode 100644 README delete mode 100644 README.md create mode 100644 demo/express.ts create mode 100644 html-preview.png delete mode 100644 src/adapters/knex.ts delete mode 100644 src/adapters/mongoose.ts create mode 100644 src/healthz.test.ts create mode 100644 src/http.test.ts create mode 100644 src/http.ts diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index c62f864..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,127 +0,0 @@ -version: 2 - -defaults: &defaults - working_directory: ~/repo - docker: - - image: circleci/node:8.11.3 - -jobs: - checkout: - <<: *defaults - steps: - - checkout - - restore_cache: - keys: - - v1-dependencies-{{ checksum "package.json" }} - - v1-dependencies- - - run: npm install - - save_cache: - paths: - - node_modules - key: v1-dependencies-{{ checksum "package.json" }} - - persist_to_workspace: - root: ~/repo - paths: . - - build: - <<: *defaults - steps: - - attach_workspace: - at: ~/repo - - run: npm run build - - persist_to_workspace: - root: ~/repo - paths: . - - test: - <<: *defaults - steps: - - attach_workspace: - at: ~/repo - - run: - name: Run tests - command: npm test -- --ci - environment: - JEST_JUNIT_OUTPUT: reports/jest/results.xml - - store_test_results: - path: reports - - persist_to_workspace: - root: ~/repo - paths: - - reports - - coverage - coveralls: - <<: *defaults - steps: - - attach_workspace: - at: ~/repo - - run: - name: Submit coverage report to Coveralls.io - command: npm run test:coveralls - publish: - <<: *defaults - steps: - - attach_workspace: - at: ~/repo - - run: - name: Authenticate with registry - command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc - - run: - name: Publish package - command: npm publish - -workflows: - version: 2 - build: - jobs: - - checkout - - build: - requires: - - checkout - # - test: - # requires: - # - checkout - # - coveralls: - # requires: - # - test - release: - jobs: - - checkout: - filters: - tags: - only: /^v.*/ - branches: - ignore: /.*/ - - build: - requires: - - checkout - filters: - tags: - only: /^v.*/ - branches: - ignore: /.*/ - # - test: - # requires: - # - checkout - # filters: - # tags: - # only: /^v.*/ - # branches: - # ignore: /.*/ - # - coveralls: - # requires: - # - test - # filters: - # tags: - # only: /^v.*/ - # branches: - # ignore: /.*/ - - publish: - requires: - - build - # - test - filters: - tags: - only: /^v.*/ - branches: - ignore: /.*/ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..ff87b55 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,26 @@ +name: Node.js CI + +on: + push: + branches: + - '**' + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x, 18.x, 20.x] + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm install + - run: npm run build + - run: npm test diff --git a/.gitignore b/.gitignore index 33eca0b..f06235c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ node_modules -.vscode -dist \ No newline at end of file +dist diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 8f8cf14..0000000 --- a/.npmignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -.vscode -src \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..e17b023 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# Ignore artifacts: +dist diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..fcdd95b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": false, + "singleQuote": true + +} diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..77e767c --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,15 @@ +# Changelog + +## [Unreleased] - 2024-05-03 + +### Added +- Changelog +- Tests +- ExpressJS demo +- Basic healthz functionality +- HTML and JSON preview +- Build & minification with esbuild +- GitHub actions + +### Removed +- Everything \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e6c1f1f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Jiri Smolik + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..7b16d91 --- /dev/null +++ b/README @@ -0,0 +1,45 @@ +# node-healthz + +The "health-checkup" npm package simplifies the process of implementing health checks in Node.js applications, allowing you to ensure the reliability and availability of your system components effortlessly. + +```ts +import { check } from 'node-healthz' +check({ + checks: [ + { + id: 'PostgreSQL', + required: true, + fn: async () => /* ... */, + }, + { + id: 'Redis', + fn: async () => /* ... */, + }, + ], +}) +``` +``` +curl http://localhost:46461/healthz --verbose | jq +< HTTP/1.1 200 OK +{ + "status": "OK", + "checks": [ + { + "id": "PostgreSQL", + "required": "true", + "t": "1,003ms", + "output": "" + }, + { + "id": "Redis", + "required": "false", + "t": "11ms", + "output": "" + } + ] +} +``` + +### Guides + +- [How to use with ExpressJS](./demo/express.ts) \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 74ca49d..0000000 --- a/README.md +++ /dev/null @@ -1,105 +0,0 @@ -# node-healthz - -Define a set of checkers to your express application. `/healthz` endpoint then becomes available. -```js -router.use( - expressMiddleware( - { - sql: { - type: 'knex', - adapter: knex, // Your knex instance - }, - mongo: { - type: 'mongoose', - adapter: mongoose, // Your mongoose instance - }, - custom: { - check: () => true,/* Custom check. May be async */ - } - } - ) -) -``` -..and resulting `GET /healthz` resopnse: -```json -{ - "tldr": "OK", - "t": "501ms", - "checkers": { - "sql": { - "result": true, - "error": null, - "t": "0ms", - "health": "OK", - "crucial": false - }, - "hello": { - "result": null, - "error": null, - "t": "501ms", - "health": "TIMEOUT", - "crucial": false - }, - }, - "options": { - "timeout": 500 - } -} -``` - -## API - -### `expressMiddleware({ [key: string]: AdapterOptions }): RequestHandler` - -Define a set of app health checkers. Returns an express route handler. - -System is considered healthy if all the checkers responded without error in given timeout. - -If request comes, and routes starts `/healthz`, system health is calculated and returned: - -``` -GET /healthz -``` -|Supported query parameters|| -|-|-| -| `timeout` | Optional. Default: 5000. How much time in ms do the checkers have to respond. - -Status code is 500 for unhealthy system, 200 otherwise. - - -**Response JSON body**: -```ts -{ - tldr: 'OK' | 'NOT_OK', - checkers: [ - { - result: boolean // true if checker responds in given time - error: string | null // error message if the checker fails - t: string // time, 245ms - health: 'OK' | 'ERROR' | 'TIMEOUT' - crucial: boolean - } - ], - t: string // total time, 245ms - options: { // Optional parameters from the input - timeout: 1000, - } -} -``` - - -### Types -### `AdapterOptions: object` - -|Attribute|Type|Description -|-|-|-| -|type| AdapterType | Optional. If not supplied, `check` function has to be defined. -|adapter| any | Optional. If `type` is specified, appropriate adapter instance has to be passed in here. E.g. you Mongoose instance. -| crucial | boolean | Optional. Default: `false`. If `true` and checker fails, system health outcome cannot be OK. -| check | function | Optional. Use this if you want to do the check completely by yourself. Check may return a Promise. Check is called with `AdapterOptions`, extended with a `timeout: number` prop. - -### `AdapterType: string` -Supported adapter types defining the instance that is found in `adapter` option. -```ts -'knex' | 'mongoose' -``` \ No newline at end of file diff --git a/demo/express.ts b/demo/express.ts new file mode 100644 index 0000000..0ff600f --- /dev/null +++ b/demo/express.ts @@ -0,0 +1,39 @@ +// To run demo: npx ts-node demo/express.ts + +import express from 'express' +import * as healthz from '../dist/index' + +const app = express() + +app.use('/healthz', async (req, res, next) => { + try { + const health = await healthz + .check({ + checks: [ + { + id: 'PostgreSQL', + required: true, + fn: async () => new Promise(resolve => setTimeout(resolve, 1000)), + }, + { + id: 'Redis', + fn: async () => new Promise(resolve => setTimeout(resolve, 10)), + }, + ], + timeout: parseInt(String(req.query.timeout)) || undefined, + }) + res.status(healthz.status(health)) + if (req.header('accept')?.includes('html')) { + res.type('html') + res.end(healthz.html(health)) + } else { + res.json(healthz.json(health)) + } + } catch (error) { + next(error) + } +}) + +const running = app.listen(process.env.PORT ?? 0) +const port = (running.address() as any).port +console.log(`Express app running on http://localhost:${port}`) diff --git a/html-preview.png b/html-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..1f5d3a7ed75d946295570011320cb8124323d466 GIT binary patch literal 13508 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 yE+YK{k#N@Ra~#2Y_pg@Qe=12" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], "dev": true, - "requires": { - "@types/connect": "3.4.32", - "@types/node": "8.10.37" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" } }, - "@types/connect": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", - "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.12.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.8.tgz", + "integrity": "sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, - "requires": { - "@types/node": "8.10.37" + "engines": { + "node": ">= 0.8" } }, - "@types/events": { + "node_modules/destroy": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", - "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", "dev": true }, - "@types/express": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.0.tgz", - "integrity": "sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==", + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, - "requires": { - "@types/body-parser": "1.17.0", - "@types/express-serve-static-core": "4.16.0", - "@types/serve-static": "1.13.2" + "engines": { + "node": ">= 0.6" } }, - "@types/express-serve-static-core": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz", - "integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==", + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "requires": { - "@types/events": "1.2.0", - "@types/node": "8.10.37", - "@types/range-parser": "1.2.2" + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" } }, - "@types/mime": { + "node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz", - "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "@types/node": { - "version": "8.10.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.37.tgz", - "integrity": "sha512-Jp39foY8Euv/PG4OGPyzxis82mnjcUtXLEMA8oFMCE4ilmuJgZPdV2nZNV1moz+99EJTtcpOSgDCgATUwABKig==", + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "@types/range-parser": { + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.2.tgz", - "integrity": "sha512-HtKGu+qG1NPvYe1z7ezLsyIaXYyi8SoAVqWDZgDQ8dLrsZvSzUNCwZyfX33uhWxL/SU0ZDQZ3nwZ0nimt507Kw==", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true }, - "@types/serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, - "requires": { - "@types/express-serve-static-core": "4.16.0", - "@types/mime": "2.0.0" + "engines": { + "node": ">= 0.8" } }, - "typescript": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", - "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==", + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } } } } diff --git a/package.json b/package.json index 7c3a9b8..5dd5c17 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,44 @@ { "name": "node-healthz", - "version": "1.0.2", - "description": "", + "version": "2.0.0", + "description": "The \"health-checkup\" npm package simplifies the process of implementing health checks in Node.js applications, allowing you to ensure the reliability and availability of your system components effortlessly.", + "keywords": [ + "nodejs", + "health-check", + "health", + "utility", + "status" + ], + "homepage": "https://github.com/smoliji/node-healthz", + "bugs": { + "url": "https://github.com/smoliji/node-healthz/issues" + }, + "license": "MIT", + "author": "Jiri Smolik ", "main": "dist/index.js", + "files": [ + "dist/*" + ], "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "echo \"Error: no test specified\" && exit 1" + "prepublish": "npm run build", + "build": "esbuild src/index.ts --bundle --minify --platform=node --outfile=dist/index.js && tsc", + "test": "node --require ts-node/register --require source-map-support/register --test **/*.test.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/smoliji/node-healthz.git" + }, + "engines": { + "node": ">=16" }, - "types": "dist/index.d.ts", - "author": "smolikjirka@gmail.com", - "license": "MIT", "devDependencies": { - "@types/express": "^4.16.0", - "@types/node": "^8.0.55", - "typescript": "^3.1.6" + "@types/express": "^4.17.21", + "@types/node": "^20.12.8", + "esbuild": "^0.20.2", + "express": "^4.19.2", + "prettier": "3.2.5", + "source-map-support": "^0.5.21", + "ts-node": "^10.9.2", + "typescript": "^5.4.5" } -} +} \ No newline at end of file diff --git a/src/adapters/knex.ts b/src/adapters/knex.ts deleted file mode 100644 index 274d4f7..0000000 --- a/src/adapters/knex.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { AdapterOptions } from 'healthz'; - -export default (knex: any, def: AdapterOptions) => { - return knex.raw('show status'); -}; diff --git a/src/adapters/mongoose.ts b/src/adapters/mongoose.ts deleted file mode 100644 index 63a5a07..0000000 --- a/src/adapters/mongoose.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AdapterOptions } from 'healthz'; - -export default (mongoose: any, definition: AdapterOptions) => { - return new Promise((resolve, reject) => { - let db = mongoose.connection - ? mongoose.connection.db // prev versions - : mongoose.db // current versions - db.command({ ping: 1 }, { failFast: true }, (error: Error) => { - if (error) { - return reject(error); - } - return resolve(true); - }); - }); -}; diff --git a/src/healthz.test.ts b/src/healthz.test.ts new file mode 100644 index 0000000..ffc6cb0 --- /dev/null +++ b/src/healthz.test.ts @@ -0,0 +1,56 @@ +import test, { describe } from 'node:test' +import { Status, createConfig, check } from './healthz' +import { equal } from 'node:assert' + +describe('healthz', () => { + test('Health check results are masked by default', async () => { + const masked = await check({ + checks: [ + { + id: 'check', + fn: async () => 'result', + }, + ], + }) + equal(masked.checks[0].output, '') + equal(masked.checks[0].rawOutput, 'result') + const unmasked = await check({ + checks: [ + { + id: 'check', + fn: async () => 'result', + maskOutput: false, + }, + ], + }) + equal(unmasked.checks[0].output, 'result') + equal(unmasked.checks[0].rawOutput, 'result') + }) + test('Health checks timeout can be set; default is 5s', async () => { + equal(createConfig().timeout, 5_000) + equal(createConfig({ timeout: 1 }).timeout, 1) + }) + test('Required check makes result Unhealthy on failure', async () => { + const result = await check({ + checks: [ + { + id: 'check', + fn: async () => Promise.reject(new Error('check failed')), + required: true, + }, + ], + }) + equal(result.status, Status.Unhealthy) + }) + test('Failing a non-required check still results in Healthy', async () => { + const result = await check({ + checks: [ + { + id: 'check', + fn: async () => Promise.reject(new Error('check failed')), + }, + ], + }) + equal(result.status, Status.Healthy) + }) +}) diff --git a/src/healthz.ts b/src/healthz.ts index 83b575c..df85a7d 100644 --- a/src/healthz.ts +++ b/src/healthz.ts @@ -1,212 +1,154 @@ -import * as express from 'express'; -import * as url from 'url'; -import knex from './adapters/knex'; -import mongoose from './adapters/mongoose'; +export type Healthz = () => Promise -export interface AdapterOptions { - type?: AdapterType; - adapter?: any; - crucial: boolean; - customCheck?: Check; - check?: Check; - timeout: number; - ignoreResult: boolean, +export enum Status { + Healthy, + Unhealthy, } -export type AdapterType = 'knex' | 'mongoose'; -export const AdapterType = { - Knex: 'knex' as AdapterType, - Mongoose: 'mongoose' as AdapterType, -}; - -export interface Health { - tldr: HealthState; +export interface Config { + checks: Array<{ + id: string + required: boolean + fn: () => Promise + maskOutput: boolean + }> + timeout: number } -export type HealthState = 'OK' | 'NOT_OK' | 'UNKNOWN' | 'TIMEOUT' | 'ERROR'; -export const HealthState = { - OK: 'OK' as HealthState, - NOT_OK: 'NOT_OK' as HealthState, - UNKNOWN: 'UNKNOWN' as HealthState, - TIMEOUT: 'TIMEOUT' as HealthState, - ERROR: 'ERROR' as HealthState, -}; - -export interface HealthzDefinition { - [key: string]: Partial; -}; - -export interface HealthzOptions { - timeout: number; - ignoreResults: boolean, +export interface Result { + status: Status + checks: CheckResult[] } -const defaults: HealthzOptions = { - timeout: 5000, - ignoreResults: true, -}; - - -const stopwatch = { - // see https://nodejs.org/api/process.html#process_process_hrtime_time for details - start: () => { - return <[number, number]>process.hrtime(); - }, - stop: (te: [number, number]): number => { - const t = process.hrtime(te); - return (t[0] + t[1] * 1e-9) * 1e3; - }, -}; -const ms = (t: number) => `${Math.round(t)}ms`; - -const adapterMap = { - [AdapterType.Knex]: knex, - [AdapterType.Mongoose]: mongoose, -}; - -export type Check = ((adapterOptions: AdapterOptions) => any) - // | ((adapter: any, adapterOptions: AdapterOptions) => any); +export enum CheckStatus { + Ok, + Error, + Timeout, +} export interface CheckResult { - name: string; - result: null | any; - error: null | string; - t: null | string | number[]; - health: HealthState, + id: string + status: CheckStatus + output: unknown + rawOutput: unknown + required: boolean + /** + * Milliseconds it took the check to finish. Wall clock time. + */ + latency: number } -const getChecker = (adapterOptions: Partial, healthzOptions: HealthzOptions) => { - const adapterOpts: AdapterOptions = { - timeout: healthzOptions.timeout, - crucial: false, - ignoreResult: healthzOptions.ignoreResults, - ...(adapterOptions || {}), - }; - - const check = (() => { - if (adapterMap[adapterOptions.type!]) { - return () => adapterMap[adapterOptions.type!](adapterOptions.adapter, adapterOpts); - } - if (adapterOptions.customCheck) { - return () => adapterOptions.customCheck!(adapterOpts); - } - if (adapterOptions.check) { - return () => adapterOptions.check!(adapterOpts); - } - throw new TypeError('Unsupported adapter type and no custom `check` function supplied'); - })(); - if (!adapterOpts.ignoreResult) { - return check; - } - return async () => { - await check(); - return ''; - }; +export interface Option { + /** + * Array of health checks. + */ + checks?: Array<{ + /** + * Unique identifier of the check. + * There cannot be multiple checks with the same ID. + */ + id: string + /** + * Health check function. + */ + fn: () => Promise + /** + * Boolean that if any health check with TRUE fails, entire health check + * is considered as Unhealthy. + * @default false + */ + required?: boolean + /** + * Boolean that if TRUE, check output will be masked and the return/thrown + * value from the `fn` won't be used. + * @default true + */ + maskOutput?: boolean + }> + /** + * How many milliseconds to wait for the check to complete. It if fails + * to finish within this time, check status will be Timeout. + * @default 10_000 + **/ + timeout?: number } -const defineHealth = async (definition?: HealthzDefinition, options: Partial = defaults) => { - const opts: HealthzOptions = { - ...defaults, - ...(options || {}), - }; - const checkers = Object.keys(definition || {}) - .map(key => ({ name: key, check: getChecker(definition![key], opts) })); - - const tStart = stopwatch.start(); - const checkersHealth = await (new Promise(resolve => { - const results: CheckResult[] = checkers.map(({ name }) => ( - { - name, - result: null, - error: null, - t: null, - health: HealthState.UNKNOWN, - crucial: !!definition![name].crucial, - } - )); - - const clock = setTimeout( - () => resolve( - results.map(result => ({ - ...result, - health: result.health === HealthState.UNKNOWN - ? HealthState.TIMEOUT - : result.health, - t: result.t == null ? ms(stopwatch.stop(tStart)) : result.t, - })) - ), - opts.timeout - ); +export function createConfig(option?: Option): Config { + return { + checks: + option?.checks?.map((x) => ({ + id: x.id, + required: x.required ?? false, + fn: x.fn, + maskOutput: x.maskOutput ?? true, + })) ?? [], + timeout: option?.timeout ?? 5_000, + } +} - const setResponse = (i: number, health: HealthState, error: string | null, result: any, t: string) => { - return results[i] = { ...results[i], health, error, result, t }; - } - Promise.all( - checkers.map( - async (checker, i) => { - const t = stopwatch.start(); - let health = HealthState.OK - let result = null; - let error = null; - try { - result = await checker.check(); - } catch (caught) { - error = caught.message; - health = HealthState.ERROR; - } - setResponse(i, health, error, result, ms(stopwatch.stop(t))); - } - ) - ) - .then(() => { - clearTimeout(clock); - resolve(results); - }); - }) as Promise); - return { - tldr: Object.keys(definition!) - .reduce( - (status, checker, i) => { - const health = checkersHealth[i].health; - const crucial = definition![checker].crucial; - if (status === HealthState.OK && crucial && health !== HealthState.OK) { - return HealthState.NOT_OK; - } - return status; - }, - HealthState.OK - ), - t: ms(stopwatch.stop(tStart)), - checkers: checkersHealth - .reduce( - (acc, { name, ...rest }) => { - acc[name] = rest; - return acc; - }, - {} as any), - options, - } +export function check(option?: Option) { + const config = createConfig(option) + return checkForConfig(config) } -export default defineHealth; +type RawCheckResult = Pick + +async function checkForConfig(config: Config): Promise { + const t0 = Date.now() + const timeout = createTimeout(config.timeout) + const results = await Promise.all( + config.checks.map((x) => + Promise.race([ + x + .fn() + .then((rawOutput) => ({ + status: CheckStatus.Ok, + rawOutput, + latency: Date.now() - t0, + })) + .catch((rawOutput) => ({ + status: CheckStatus.Error, + rawOutput, + latency: Date.now() - t0, + })), + timeout.promise, + ]), + ), + ) + timeout.clear() + return { + status: results.find( + (x, i) => config.checks[i].required && x.status !== CheckStatus.Ok, + ) + ? Status.Unhealthy + : Status.Healthy, + checks: config.checks.map((x, i) => ({ + id: x.id, + status: results[i].status, + output: x.maskOutput ? '' : results[i].rawOutput, + rawOutput: results[i].rawOutput, + latency: results[i].latency, + required: x.required, + })), + } +} -export const healthz = (def: HealthzDefinition, opts?: Partial) => { - return (req: Req, res: Res) => { - const query = url.parse(req.url, true).query; - const specOpts = { ...opts } as Partial; - if (query && ('timeout' in query)) { - specOpts.timeout = parseInt(String(query.timeout), 10); - } - return defineHealth(def, specOpts) - .then(result => { - const statusCode = result.tldr === HealthState.OK - ? 200 - : 500; - res.writeHead(statusCode, { - 'Content-type': 'application/json' - }); - res.write(JSON.stringify(result)); - res.end(); - }); - } +function createTimeout(ms: number) { + let ref: NodeJS.Timeout + return { + promise: new Promise((resolve) => { + ref = setTimeout( + () => + resolve({ + status: CheckStatus.Timeout, + rawOutput: undefined, + latency: ms, + }), + ms, + ) + }), + clear() { + clearTimeout(ref) + }, + } } diff --git a/src/http.test.ts b/src/http.test.ts new file mode 100644 index 0000000..214aa15 --- /dev/null +++ b/src/http.test.ts @@ -0,0 +1,69 @@ +import test, { describe } from 'node:test' +import { htmlResult, jsonResult, resultStatusCode } from './http' +import { CheckStatus, Status } from './healthz' +import { deepEqual, equal } from 'node:assert' + +describe('http', () => { + test('status code 200 if Ok, 500 otherwise', () => { + equal( + resultStatusCode({ + checks: [], + status: Status.Healthy, + }), + 200, + ) + equal( + resultStatusCode({ + checks: [], + status: Status.Unhealthy, + }), + 500, + ) + }) + test('json', () => { + deepEqual( + jsonResult({ + checks: [ + { + id: 'a', + latency: 100, + output: 'o', + rawOutput: 'ro', + required: true, + status: CheckStatus.Ok, + }, + ], + status: Status.Healthy, + }), + { + status: 'OK', + checks: [ + { + id: 'a', + status: 'OK', + t: '100ms', + output: 'o', + required: true, + }, + ], + }, + ) + }) + test('html', () => { + const html = htmlResult({ + checks: [ + { + id: 'a', + latency: 100, + output: 'o', + rawOutput: 'ro', + required: true, + status: CheckStatus.Ok, + }, + ], + status: Status.Healthy, + }) + equal(html.includes(''), true) + equal(html.includes(' ({ + id: x.id, + status: x.status === CheckStatus.Error + ? 'ERROR' + : x.status === CheckStatus.Ok + ? 'OK' + : x.status === CheckStatus.Timeout + ? 'TIMEOUT' + : 'UNKNOWN', + required: x.required, + t: `${Number(x.latency).toLocaleString()}ms`, + output: x.output, + })), + } +} + +export function htmlResult(result: Result) { + return ` + + + + + Service Status + + + + + + +
+
+ ${result.status === Status.Healthy ? html('healthy') : html('unhealthy')} +
+
    +
  • +
    + Ok + Error + Timeout +
    +
  • + ${result.checks + .map(x => html('service', { title: x.id, required: x.required, status: x.status, output: String(x.output ?? '') })) + .join('\n')} +
+
+ + + + ` +} + +function html(piece: 'healthy' | 'unhealthy' | 'service' | 'check-ok' | 'check-error' | 'check-timeout', arg?: { title?: string, required?: boolean, status?: CheckStatus, output?: string }): string { + switch (piece) { + case 'healthy': + return '' + case 'unhealthy': + return '' + case 'check-ok': + return `` + case 'check-error': + return `` + case 'check-timeout': + return `` + case 'service': + return `
  • +
    + ${arg?.title ?? 'Unknown'} +
    + ${arg?.required ? 'Required' : 'Optional'} +
    + ${arg?.status === CheckStatus.Ok ? html('check-ok') : arg?.status === CheckStatus.Error ? html('check-error') : html('check-timeout')} +
  • + ` + default: + return '' + } +} + +export function resultStatusCode(result: Result) { + return result.status === Status.Healthy ? 200 : 500 +} diff --git a/src/index.ts b/src/index.ts index d9a2617..f2be426 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,23 +1,15 @@ -import * as express from 'express'; -import defineHealth, { healthz, HealthzDefinition, HealthzOptions, AdapterType } from './healthz'; - -export default healthz; - export { - AdapterType -} - -const expressMiddleware = < - Req extends express.Request, - Res extends express.Response, - Next extends express.NextFunction ->(def: HealthzDefinition , opts?: HealthzOptions) => - ((req: Req, res: Res, next: Next) => { - if (/^\/healthz/.test(req.url)) { - healthz(def, opts)(req, res); - } else { - next(); - } - }) as express.RequestHandler - -export { healthz, defineHealth, expressMiddleware, }; + check, + CheckStatus, + CheckResult, + Config, + Healthz, + Option, + Status, + Result, +} from './healthz' +export { + htmlResult as html, + jsonResult as json, + resultStatusCode as status, +} from './http' diff --git a/tsconfig.json b/tsconfig.json index bd476dd..0bc79a6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,54 +1,20 @@ - { - "include": [ - "./src/**/*" - ], - "compilerOptions": { - /* Basic Options */ - "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": ["es2016"], /* Specify library files to be included in the compilation: */ - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - "declaration": true, /* Generates corresponding '.d.ts' file. */ - "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./dist", /* Redirect output structure to the directory. */ - "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - "baseUrl": "./src", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - /* Source Map Options */ - // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ - } - } \ No newline at end of file + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "src/**/*.test.ts" + ] +}