From eb7353aa1bac635a055aedeb0ca9320d44b48d40 Mon Sep 17 00:00:00 2001 From: netanelC Date: Mon, 29 Jun 2026 15:51:08 +0300 Subject: [PATCH] feat: support reload and use health-check package --- package-lock.json | 185 +++++++++++++++++++++++----------------- package.json | 5 +- src/common/config.ts | 37 ++++++++ src/common/constants.ts | 1 + src/containerConfig.ts | 46 +++++----- src/index.ts | 13 +-- 6 files changed, 181 insertions(+), 106 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2bbb3efb..840ea861 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,15 +10,16 @@ "license": "ISC", "dependencies": { "@godaddy/terminus": "^4.12.1", - "@map-colonies/config": "^4.0.1", + "@map-colonies/config": "https://ghatmpstorage.blob.core.windows.net/npm-packages/config-67ebc15497f4312a32b03966ee4d43a9eb7d9e5e.tgz", "@map-colonies/error-express-handler": "^4.0.0", "@map-colonies/express-access-log-middleware": "^4.0.0", + "@map-colonies/health-check": "file:../infra-packages/packages/health-check", "@map-colonies/js-logger": "^4.0.0", "@map-colonies/openapi-express-viewer": "^5.0.0", "@map-colonies/openapi-helpers": "^5.1.0", "@map-colonies/prometheus": "^1.0.0", "@map-colonies/read-pkg": "^2.0.0", - "@map-colonies/schemas": "^1.14.0", + "@map-colonies/schemas": "^1.22.0", "@map-colonies/semantic-conventions": "^1.0.0", "@map-colonies/tracing": "^1.0.0", "@map-colonies/tracing-utils": "^1.0.0", @@ -92,6 +93,65 @@ "node": ">=24" } }, + "../infra-packages/packages/health-check": { + "name": "@map-colonies/health-check", + "version": "0.0.1", + "dependencies": { + "@godaddy/terminus": "^4.12.1", + "@map-colonies/js-logger": "workspace:^" + }, + "devDependencies": { + "@arethetypeswrong/cli": "catalog:", + "@map-colonies/eslint-config": "workspace:^", + "@map-colonies/tsconfig": "workspace:^", + "@microsoft/api-extractor": "catalog:", + "@types/node": "catalog:", + "eslint": "catalog:", + "publint": "catalog:", + "rimraf": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:", + "vitest-config": "workspace:^" + }, + "engines": { + "node": ">=24" + } + }, + "../ts-npm-package-boilerplate": { + "name": "@map-colonies/healthcheck", + "version": "1.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "@godaddy/terminus": "^4.12.1", + "@map-colonies/js-logger": "^5.0.0" + }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.18.2", + "@commitlint/cli": "^20.4.1", + "@map-colonies/commitlint-config": "^2.0.0", + "@map-colonies/eslint-config": "^8.0.0", + "@map-colonies/prettier-config": "^1.0.0", + "@map-colonies/tsconfig": "^2.0.0", + "@types/node": "^22.20.0", + "@vitest/coverage-v8": "^4.0.18", + "@vitest/eslint-plugin": "^1.6.9", + "@vitest/ui": "^4.0.18", + "commitlint": "^20.4.1", + "eslint": "^9.39.2", + "husky": "^9.1.7", + "prettier": "^3.5.3", + "pretty-quick": "^4.2.2", + "publint": "^0.3.17", + "rimraf": "^6.1.3", + "typedoc": "^0.28.17", + "typescript": "^5.9.3", + "vitest": "^4.0.18" + }, + "engines": { + "node": ">=24" + } + }, "node_modules/@apideck/better-ajv-errors": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", @@ -110,20 +170,18 @@ } }, "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.9.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", - "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-15.4.0.tgz", + "integrity": "sha512-QbMJJlMZd27fZmi7Q+twCqwABlfaO4Qdc1YvbbwkqN2lILfELnfWfw9xFFl6CeF5jAeGQpokUVhvCkhz+rvEGg==", "license": "MIT", "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" + "js-yaml": "^4.2.0" }, "engines": { - "node": ">= 16" + "node": ">=20" }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" + "peerDependencies": { + "@types/json-schema": "^7.0.15" } }, "node_modules/@babel/code-frame": { @@ -1599,17 +1657,17 @@ }, "node_modules/@map-colonies/config": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@map-colonies/config/-/config-4.0.1.tgz", - "integrity": "sha512-NIc+N7dOiSIe/9eoMwHkLvPFlSZcNh8BSR/f8QhEGnjLDGPeFE45lVXuI0QuvnbLrJxTr0rH3O6y+9JIKEK9JQ==", + "resolved": "https://ghatmpstorage.blob.core.windows.net/npm-packages/config-67ebc15497f4312a32b03966ee4d43a9eb7d9e5e.tgz", + "integrity": "sha512-LV887ojRdy0HayjuVyMed3lQFtYQIoSnDksjOpEH8Z8DMIed7+iiSfsoYQvlc6dMEjbdnqR7mjII65kcilP0UA==", "license": "ISC", "dependencies": { "@apideck/better-ajv-errors": "^0.3.6", - "@apidevtools/json-schema-ref-parser": "^11.6.4", + "@apidevtools/json-schema-ref-parser": "^15.3.5", "@map-colonies/read-pkg": "^0.0.1", "@types/lodash": "^4.17.0", "ajv": "^8.16.0", "ajv-formats": "^3.0.1", - "config": "^3.3.11", + "config": "^4.4.1", "debug": "^4.3.5", "deepmerge": "^4.3.1", "http-status-codes": "^2.3.0", @@ -1621,7 +1679,7 @@ "node": ">=24.0.0" }, "peerDependencies": { - "@map-colonies/schemas": "^1.9.0", + "@map-colonies/schemas": "^1.22.0", "prom-client": "^15.0.0" }, "peerDependenciesMeta": { @@ -1891,6 +1949,10 @@ "node": ">=24" } }, + "node_modules/@map-colonies/health-check": { + "resolved": "../infra-packages/packages/health-check", + "link": true + }, "node_modules/@map-colonies/infra-copilot-instructions": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@map-colonies/infra-copilot-instructions/-/infra-copilot-instructions-1.2.0.tgz", @@ -1969,21 +2031,6 @@ "supertest": "^7.0.0" } }, - "node_modules/@map-colonies/openapi-helpers/node_modules/@apidevtools/json-schema-ref-parser": { - "version": "15.2.2", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-15.2.2.tgz", - "integrity": "sha512-54fvjSwWiBTdVviiUItOCeyxtPSBmCrSEjlOl8XFEDuYD3lXY1lOBWKim/WJ3i1EYzdGx6rSOjK5KRDMppLI4Q==", - "license": "MIT", - "dependencies": { - "js-yaml": "^4.1.1" - }, - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@types/json-schema": "^7.0.15" - } - }, "node_modules/@map-colonies/openapi-helpers/node_modules/@commander-js/extra-typings": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-14.0.0.tgz", @@ -1998,7 +2045,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", "license": "MIT", - "peer": true, "engines": { "node": ">=20" } @@ -2075,11 +2121,10 @@ } }, "node_modules/@map-colonies/schemas": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@map-colonies/schemas/-/schemas-1.14.0.tgz", - "integrity": "sha512-EBR36xOnIrRrXc+HG1MnGUKe/epXF5HAyEZc5jwV0RQzvn66Buo70+/jW9A/yb/mtrroSqHn2bdlLuH5kml6iQ==", - "license": "MIT", - "peer": true + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@map-colonies/schemas/-/schemas-1.22.0.tgz", + "integrity": "sha512-yieLkagKcCSHiAy3/V6OC+Zo8ougtAVuyP2poFGFJFAUXH17yu8CgxHXTARVuQCKCXxcH/Rem8QWfW3EdTggPA==", + "license": "MIT" }, "node_modules/@map-colonies/semantic-conventions": { "version": "1.0.0", @@ -2217,7 +2262,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz", "integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -4222,7 +4266,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -4696,7 +4739,6 @@ "integrity": "sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g==", "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18.18" }, @@ -5964,7 +6006,6 @@ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -6021,8 +6062,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/lodash": { "version": "4.17.21", @@ -6067,7 +6107,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.0.tgz", "integrity": "sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -6289,7 +6328,6 @@ "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.40.0", "@typescript-eslint/types": "8.40.0", @@ -6456,7 +6494,6 @@ "integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.40.0", @@ -6822,7 +6859,6 @@ "integrity": "sha512-zodmXRsVKFsuHxHJILuTFaaKsrsxm0YsiOX65clk+LpCW9JrVXaf6ERXr0caDs+NEk0S62Jyk0K7XYQ7gWXheA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "^8.58.0", "@typescript-eslint/utils": "^8.58.0" @@ -7144,7 +7180,6 @@ "integrity": "sha512-rkoPH+RqWopVxDnCBE/ysIdfQ2A7j1eDmW8tCxxrR9nnFBa9jKf86VgsSAzxBd1x+ny0GC4JgiD3SNfRHv3pOg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "4.0.16", "fflate": "^0.8.2", @@ -7215,7 +7250,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7256,7 +7290,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7304,6 +7337,7 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -7642,7 +7676,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -8168,15 +8201,15 @@ "license": "MIT" }, "node_modules/config": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/config/-/config-3.3.12.tgz", - "integrity": "sha512-Vmx389R/QVM3foxqBzXO8t2tUikYZP64Q6vQxGrsMpREeJc/aWRnPRERXWsYzOHAumx/AOoILWe6nU3ZJL+6Sw==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/config/-/config-4.4.1.tgz", + "integrity": "sha512-XfN4Q4+wBKkGtgMyQ+5ayjepdb0MrdiGKfBr0G1PTLx9rnqsX+Xiw03LEUtSALZU0UVfcFp6+xYV0NL8HLF94g==", "license": "MIT", "dependencies": { "json5": "^2.2.3" }, "engines": { - "node": ">= 10.0.0" + "node": ">= 20.0.0" } }, "node_modules/consola": { @@ -8401,7 +8434,6 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -8931,7 +8963,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9068,7 +9099,6 @@ "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/types": "^8.35.0", "comment-parser": "^1.4.1", @@ -9475,7 +9505,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -10535,6 +10564,7 @@ "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -11117,9 +11147,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -11686,7 +11726,6 @@ "integrity": "sha512-UczzB+0nnwGotYSgllfARAqWCJ5e/skuV2K/l+Zyck/H6pJIhLXuBnz+6vn2i211o7DtbE78HQtsYEKICHGI+g==", "dev": true, "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" @@ -12397,14 +12436,14 @@ "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/openapi-typescript": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.8.0.tgz", "integrity": "sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==", "license": "MIT", + "peer": true, "dependencies": { "@redocly/openapi-core": "^1.34.3", "ansi-colors": "^4.1.3", @@ -12425,6 +12464,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", @@ -12442,6 +12482,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.1.0.tgz", "integrity": "sha512-GBuewsPrhJPftT+fqDa9oI/zc5HNsG9nREqwzoSFDOIqf0NggOZbHQj2TE1P1CDJK8ZogFnlZY9hWoUiur7I/A==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -13134,7 +13175,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -13252,7 +13292,6 @@ "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.0.tgz", "integrity": "sha512-0GNPNzHXBKw6U/InGe79A3Crzyk9bcSyObF9/Gfo9DLEf5qj5RF50RSjsu0W1rZ6ZqRGdzDFCRBQvi9/rSGPtA==", "license": "MIT", - "peer": true, "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", @@ -13501,7 +13540,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -13599,7 +13637,6 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@prisma/config": "6.19.0", "@prisma/engines": "6.19.0" @@ -13690,7 +13727,6 @@ "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/api": "^1.4.0", "tdigest": "^0.1.1" @@ -13878,7 +13914,6 @@ "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -13889,7 +13924,6 @@ "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -14882,7 +14916,6 @@ "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", @@ -14957,7 +14990,6 @@ "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", "license": "MIT", - "peer": true, "dependencies": { "methods": "^1.1.2", "superagent": "^10.2.3" @@ -15213,7 +15245,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -15415,7 +15446,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15681,7 +15711,6 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -15775,7 +15804,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -15818,7 +15846,6 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", diff --git a/package.json b/package.json index 929581e7..71132529 100644 --- a/package.json +++ b/package.json @@ -45,14 +45,15 @@ "license": "ISC", "dependencies": { "@godaddy/terminus": "^4.12.1", - "@map-colonies/config": "^4.0.1", + "@map-colonies/config": "https://ghatmpstorage.blob.core.windows.net/npm-packages/config-67ebc15497f4312a32b03966ee4d43a9eb7d9e5e.tgz", "@map-colonies/error-express-handler": "^4.0.0", "@map-colonies/express-access-log-middleware": "^4.0.0", + "@map-colonies/health-check": "file:../infra-packages/packages/health-check", "@map-colonies/js-logger": "^4.0.0", "@map-colonies/openapi-express-viewer": "^5.0.0", "@map-colonies/openapi-helpers": "^5.1.0", "@map-colonies/read-pkg": "^2.0.0", - "@map-colonies/schemas": "^1.14.0", + "@map-colonies/schemas": "^1.22.0", "@map-colonies/prometheus": "^1.0.0", "@map-colonies/semantic-conventions": "^1.0.0", "@map-colonies/tracing": "^1.0.0", diff --git a/src/common/config.ts b/src/common/config.ts index 6215cb43..8608e544 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -1,5 +1,10 @@ +import 'reflect-metadata'; import { type ConfigInstance, config } from '@map-colonies/config'; import { infraJobnikManagerV1, type infraJobnikManagerV1Type } from '@map-colonies/schemas'; +import { container } from 'tsyringe'; +import type { HealthCheckManager } from '@map-colonies/health-check'; +import type { Logger } from '@map-colonies/js-logger'; +import { SERVICES } from './constants'; // Choose here the type of the config instance and import this type from the entire application type ConfigType = ConfigInstance; @@ -12,9 +17,41 @@ let configInstance: ConfigType | undefined; * @returns A Promise that resolves when the configuration is successfully initialized. */ async function initConfig(offlineMode?: boolean): Promise { + const terminatePod = process.env.TERMINATE_POD !== 'false'; + configInstance = await config({ + configName: 'infra-jobnik-manager', + terminatePod, schema: infraJobnikManagerV1, offlineMode: offlineMode, + pollIntervalMs: 1000, + async onChange() { + const logger = container.resolve(SERVICES.LOGGER); + + if (!container.isRegistered(SERVICES.HEALTHCHECK)) { + logger.warn('Configuration changed, but healthcheck manager is not yet registered.'); + return; + } + const readinessManager = container.resolve(SERVICES.HEALTHCHECK); + + logger.info('Configuration changed! Blocking readiness and waiting for active requests to finish.'); + readinessManager.setReady(false); + + await readinessManager.waitUntilZeroActiveRequests(); + + logger.info('All active requests finished.'); + + if (terminatePod) { + logger.info('Triggering graceful shutdown...'); + process.kill(process.pid, 'SIGTERM'); + } else { + logger.info('Applying new configuration locally and restoring readiness...'); + + // Handle local hot reload logic here + + readinessManager.setReady(true); + } + }, }); } diff --git a/src/common/constants.ts b/src/common/constants.ts index 1c587ffe..11fff812 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -25,6 +25,7 @@ export const SERVICES = { SERVICE_METRICS: Symbol('SERVICE_METRICS'), PRISMA: Symbol('Prisma'), HEALTHCHECK: Symbol('Healthcheck'), + SERVER: Symbol('Server'), } satisfies Record; export const ROUTERS = { diff --git a/src/containerConfig.ts b/src/containerConfig.ts index 4ccf9b69..a8dd8024 100644 --- a/src/containerConfig.ts +++ b/src/containerConfig.ts @@ -1,17 +1,18 @@ +import type { Server } from 'node:http'; import { getOtelMixin } from '@map-colonies/tracing-utils'; import { trace } from '@opentelemetry/api'; import { Registry } from 'prom-client'; import type { DependencyContainer } from 'tsyringe/dist/typings/types'; -import { jsLogger } from '@map-colonies/js-logger'; +import { type Logger, jsLogger } from '@map-colonies/js-logger'; import { instanceCachingFactory, instancePerContainerCachingFactory } from 'tsyringe'; -import type { HealthCheck } from '@godaddy/terminus'; +import { HealthCheckManager } from '@map-colonies/health-check'; import type { PrismaClient } from '@prismaClient'; import { type InjectionObject, registerDependencies } from '@common/dependencyRegistration'; import { DB_CONNECTION_TIMEOUT, ROUTERS, SERVICES, SERVICE_NAME } from '@common/constants'; import { getTracing } from '@common/tracing'; +import { promiseTimeout } from './common/utils/promiseTimeout'; import { getConfig } from './common/config'; import { createConnectionOptions, createPrismaClient } from './db/createConnection'; -import { promiseTimeout } from './common/utils/promiseTimeout'; import { SERVICE_METRICS_SYMBOL, serviceMetricsFactory } from './common/serviceMetrics'; import { jobV1RouterFactory } from './api/v1/jobs/router'; import { stageV1RouterFactory } from './api/v1/stages/router'; @@ -39,16 +40,6 @@ export const registerExternalValues = async (options?: RegisterOptions): Promise const prismaClientConfig = createConnectionOptions(dbConfig); - /* v8 ignore next 7 -- @preserve */ - const healthCheck = (prisma: PrismaClient): HealthCheck => { - return async (): Promise => { - const check = prisma.$queryRaw`SELECT 1`.then(() => { - return; - }); - return promiseTimeout(DB_CONNECTION_TIMEOUT, check); - }; - }; - const dependencies: InjectionObject[] = [ { token: SERVICES.CONFIG, provider: { useValue: configInstance } }, { token: SERVICES.LOGGER, provider: { useValue: logger } }, @@ -67,10 +58,27 @@ export const registerExternalValues = async (options?: RegisterOptions): Promise { token: SERVICES.HEALTHCHECK, provider: { - /* v8 ignore next 4 -- @preserve */ + /* v8 ignore next 17 -- @preserve */ useFactory: instanceCachingFactory((container) => { + const server = container.resolve(SERVICES.SERVER); + const logger = container.resolve(SERVICES.LOGGER); + const onSignal = container.resolve<() => Promise>('onSignal'); const prisma = container.resolve(SERVICES.PRISMA); - return healthCheck(prisma); + + const manager = new HealthCheckManager(server, logger, { onSignal }); + + manager.registerReadinessCheck('postgres', async () => { + const check = prisma.$queryRaw`SELECT 1`.then(() => { + return; + }); + return promiseTimeout(DB_CONNECTION_TIMEOUT, check); + }); + + manager.registerShutdownHook('postgres', async () => { + await prisma.$disconnect(); + }); + + return manager; }), }, }, @@ -83,11 +91,9 @@ export const registerExternalValues = async (options?: RegisterOptions): Promise { token: 'onSignal', provider: { - useValue: { - /* v8 ignore next 3 -- @preserve */ - useValue: async (): Promise => { - await Promise.all([getTracing().stop()]); - }, + /* v8 ignore next 3 -- @preserve */ + useValue: async (): Promise => { + await Promise.all([getTracing().stop()]); }, }, }, diff --git a/src/index.ts b/src/index.ts index eb3d028c..b280adfe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,7 @@ // this import must be called before the first import of tsyringe import 'reflect-metadata'; import { createServer } from 'node:http'; -import { createTerminus, type HealthCheck } from '@godaddy/terminus'; -import type { Logger } from '@map-colonies/js-logger'; +import { type Logger } from '@map-colonies/js-logger'; import { container } from 'tsyringe'; import { SERVICES } from '@common/constants'; import type { ConfigType } from '@common/config'; @@ -14,11 +13,15 @@ void getApp() const config = container.resolve(SERVICES.CONFIG); const port = config.get('server.port'); - const healthCheck = container.resolve(SERVICES.HEALTHCHECK); + const httpServer = createServer(app); - const server = createTerminus(createServer(app), { healthChecks: { '/liveness': healthCheck }, onSignal: container.resolve('onSignal') }); + // Register the server so containerConfig factories can use it + container.registerInstance(SERVICES.SERVER, httpServer); - server.listen(port, () => { + // Trigger lazy registration of health checks from containerConfig + container.resolve(SERVICES.HEALTHCHECK); + + httpServer.listen(port, () => { logger.info(`app started on port ${port}`); }); })