@slopware/sloppy-darwin-x64 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +5 -0
- package/bin/sloppy +0 -0
- package/bin/sloppyc +0 -0
- package/docs/KNOWN_LIMITATIONS.md +16 -0
- package/docs/LICENSES.md +6 -0
- package/docs/NOTICE.md +8 -0
- package/examples/README.md +140 -0
- package/examples/auth-api/README.md +20 -0
- package/examples/auth-api/app.js +61 -0
- package/examples/auth-api/appsettings.json +7 -0
- package/examples/auth-api/sloppy.json +5 -0
- package/examples/cache-basic/README.md +9 -0
- package/examples/cache-basic/app.js +32 -0
- package/examples/cache-hybrid-postgres/README.md +10 -0
- package/examples/cache-hybrid-postgres/app.js +27 -0
- package/examples/cache-output-api/README.md +10 -0
- package/examples/cache-output-api/app.js +35 -0
- package/examples/codec-base64-hex/README.md +14 -0
- package/examples/codec-base64-hex/app.js +15 -0
- package/examples/codec-checksums/README.md +15 -0
- package/examples/codec-checksums/app.js +8 -0
- package/examples/codec-compression/README.md +13 -0
- package/examples/codec-compression/app.js +9 -0
- package/examples/codec-streaming-compression/README.md +19 -0
- package/examples/codec-streaming-compression/app.js +16 -0
- package/examples/codec-text-binary/README.md +16 -0
- package/examples/codec-text-binary/app.js +17 -0
- package/examples/compiler-hello/README.md +71 -0
- package/examples/compiler-hello/app.js +7 -0
- package/examples/compiler-hello/expected/app.js +8 -0
- package/examples/compiler-hello/expected/app.js.map +53 -0
- package/examples/compiler-hello/expected/app.plan.json +229 -0
- package/examples/compiler-hello/expected/routes.slrt +0 -0
- package/examples/config-basic/README.md +13 -0
- package/examples/config-basic/app.js +13 -0
- package/examples/config-basic/appsettings.json +7 -0
- package/examples/config-secrets-redaction/README.md +9 -0
- package/examples/config-secrets-redaction/app.js +9 -0
- package/examples/config-secrets-redaction/appsettings.json +5 -0
- package/examples/config-strict-mode/README.md +7 -0
- package/examples/config-strict-mode/app.js +10 -0
- package/examples/config-strict-mode/appsettings.json +7 -0
- package/examples/configured-api/README.md +38 -0
- package/examples/configured-api/app.js +12 -0
- package/examples/configured-api/appsettings.Development.json +5 -0
- package/examples/configured-api/appsettings.json +6 -0
- package/examples/configured-api/sloppy.json +5 -0
- package/examples/core-config-secrets/README.md +10 -0
- package/examples/core-config-secrets/app.js +15 -0
- package/examples/core-fs-time-codec/README.md +9 -0
- package/examples/core-fs-time-codec/app.js +8 -0
- package/examples/core-network-time-codec/README.md +11 -0
- package/examples/core-network-time-codec/app.js +20 -0
- package/examples/core-policy-audit/README.md +7 -0
- package/examples/core-policy-audit/app.js +22 -0
- package/examples/core-process-time-codec/README.md +8 -0
- package/examples/core-process-time-codec/app.js +28 -0
- package/examples/core-worker-time/README.md +8 -0
- package/examples/core-worker-time/app.js +17 -0
- package/examples/crypto-hash-hmac/README.md +17 -0
- package/examples/crypto-hash-hmac/app.js +29 -0
- package/examples/crypto-password/README.md +21 -0
- package/examples/crypto-password/app.js +12 -0
- package/examples/crypto-random-token/README.md +16 -0
- package/examples/crypto-random-token/app.js +12 -0
- package/examples/crypto-secret-constant-time/README.md +21 -0
- package/examples/crypto-secret-constant-time/app.js +15 -0
- package/examples/data-foundation/README.md +39 -0
- package/examples/data-foundation/app.js +63 -0
- package/examples/dependency-graph/README.md +19 -0
- package/examples/dependency-graph/fixtures/graph-helper/index.js +3 -0
- package/examples/dependency-graph/fixtures/graph-helper/package.json +6 -0
- package/examples/dependency-graph/package.json +7 -0
- package/examples/dependency-graph/public/message.txt +1 -0
- package/examples/dependency-graph/sloppy.json +9 -0
- package/examples/dependency-graph/src/main.ts +8 -0
- package/examples/dogfood/README.md +23 -0
- package/examples/dogfood/dogfood.json +136 -0
- package/examples/dynamic-module-include/README.md +20 -0
- package/examples/dynamic-module-include/public/readme.txt +1 -0
- package/examples/dynamic-module-include/sloppy.json +12 -0
- package/examples/dynamic-module-include/src/main.ts +6 -0
- package/examples/dynamic-module-include/src/plugins/alpha.js +3 -0
- package/examples/dynamic-module-include/src/plugins/beta.js +3 -0
- package/examples/ergonomics/README.md +42 -0
- package/examples/ergonomics/app.js +38 -0
- package/examples/framework-controller/README.md +12 -0
- package/examples/framework-controller/app.js +31 -0
- package/examples/framework-di-services/README.md +17 -0
- package/examples/framework-di-services/app.ts +40 -0
- package/examples/framework-explicit-binding/README.md +12 -0
- package/examples/framework-explicit-binding/app.ts +34 -0
- package/examples/framework-hello/README.md +16 -0
- package/examples/framework-hello/app.ts +16 -0
- package/examples/framework-postgres-crud/README.md +73 -0
- package/examples/framework-postgres-crud/app.ts +64 -0
- package/examples/framework-sqlite-crud/README.md +52 -0
- package/examples/framework-sqlite-crud/app.ts +90 -0
- package/examples/framework-sqlite-crud/appsettings.json +11 -0
- package/examples/framework-sqlserver-crud/README.md +73 -0
- package/examples/framework-sqlserver-crud/app.ts +64 -0
- package/examples/framework-validation-errors/README.md +12 -0
- package/examples/framework-validation-errors/app.ts +16 -0
- package/examples/fs-basic/README.md +24 -0
- package/examples/fs-basic/app.js +12 -0
- package/examples/fs-roots-policy/README.md +14 -0
- package/examples/fs-roots-policy/app.js +4 -0
- package/examples/fs-streams/README.md +18 -0
- package/examples/fs-streams/app.js +11 -0
- package/examples/fs-watch/README.md +19 -0
- package/examples/fs-watch/app.js +11 -0
- package/examples/hello/README.md +63 -0
- package/examples/hello/app.js +19 -0
- package/examples/hello-minimal/README.md +51 -0
- package/examples/hello-minimal/sloppy.json +5 -0
- package/examples/hello-minimal/src/main.ts +9 -0
- package/examples/http-client-basic/README.md +11 -0
- package/examples/http-client-basic/app.js +46 -0
- package/examples/http-client-generated/README.md +22 -0
- package/examples/http-client-generated/openapi.json +45 -0
- package/examples/http-client-resilience/README.md +4 -0
- package/examples/http-client-resilience/app.js +38 -0
- package/examples/http-client-runtime-loopback/README.md +24 -0
- package/examples/http-client-testhost/README.md +4 -0
- package/examples/http-client-testhost/app.js +27 -0
- package/examples/http-client-testhost-package-mock/README.md +26 -0
- package/examples/http-client-typed/README.md +5 -0
- package/examples/http-client-typed/app.js +33 -0
- package/examples/modules-api/README.md +30 -0
- package/examples/modules-api/app.js +9 -0
- package/examples/modules-api/modules/routes.js +16 -0
- package/examples/modules-api/sloppy.json +5 -0
- package/examples/modules-basic/README.md +32 -0
- package/examples/modules-basic/app.js +41 -0
- package/examples/net-deadline-cancel/README.md +13 -0
- package/examples/net-deadline-cancel/app.js +34 -0
- package/examples/net-local-ipc/README.md +12 -0
- package/examples/net-local-ipc/app.js +46 -0
- package/examples/net-policy-strict/README.md +12 -0
- package/examples/net-policy-strict/app.js +34 -0
- package/examples/net-tcp-client/README.md +10 -0
- package/examples/net-tcp-client/app.js +23 -0
- package/examples/net-tcp-echo/README.md +11 -0
- package/examples/net-tcp-echo/app.js +45 -0
- package/examples/net-tcp-server/README.md +10 -0
- package/examples/net-tcp-server/app.js +28 -0
- package/examples/node-compat-path-events/README.md +15 -0
- package/examples/node-compat-path-events/sloppy.json +6 -0
- package/examples/node-compat-path-events/src/main.ts +15 -0
- package/examples/ops-compiler/README.md +9 -0
- package/examples/ops-compiler/app.js +26 -0
- package/examples/ops-health-metrics-management/README.md +14 -0
- package/examples/ops-health-metrics-management/app.js +24 -0
- package/examples/orm-basic/README.md +17 -0
- package/examples/orm-basic/app.js +82 -0
- package/examples/orm-cursor-export/README.md +16 -0
- package/examples/orm-cursor-export/app.js +28 -0
- package/examples/orm-migrations/README.md +14 -0
- package/examples/orm-migrations/migrations/.gitkeep +1 -0
- package/examples/orm-migrations/sloppy.json +9 -0
- package/examples/orm-migrations/src/app.ts +34 -0
- package/examples/orm-relations-includes/README.md +10 -0
- package/examples/orm-relations-includes/app.js +47 -0
- package/examples/orm-testservices/README.md +37 -0
- package/examples/orm-testservices/test.mjs +32 -0
- package/examples/os-runtime-api/README.md +11 -0
- package/examples/os-runtime-api/app.js +44 -0
- package/examples/package-zod-like/README.md +28 -0
- package/examples/package-zod-like/fixtures/zod-like/index.js +48 -0
- package/examples/package-zod-like/fixtures/zod-like/package.json +12 -0
- package/examples/package-zod-like/package.json +7 -0
- package/examples/package-zod-like/sloppy.json +6 -0
- package/examples/package-zod-like/src/main.ts +16 -0
- package/examples/postgres-basic/README.md +31 -0
- package/examples/postgres-basic/app.js +50 -0
- package/examples/prealpha-control-plane/README.md +50 -0
- package/examples/prealpha-control-plane/appsettings.Development.json +11 -0
- package/examples/prealpha-control-plane/appsettings.json +15 -0
- package/examples/prealpha-control-plane/sloppy.json +5 -0
- package/examples/prealpha-control-plane/src/db/schema.js +7 -0
- package/examples/prealpha-control-plane/src/db/seed.js +6 -0
- package/examples/prealpha-control-plane/src/main.js +21 -0
- package/examples/prealpha-control-plane/src/routes/apps.js +34 -0
- package/examples/prealpha-control-plane/src/routes/builds.js +25 -0
- package/examples/prealpha-control-plane/src/routes/deployments.js +19 -0
- package/examples/prealpha-control-plane/src/routes/diagnostics.js +11 -0
- package/examples/prealpha-control-plane/src/routes/health.js +27 -0
- package/examples/prealpha-control-plane/src/routes/projects.js +38 -0
- package/examples/prealpha-control-plane/src/services/diagnosticsSink.js +11 -0
- package/examples/prealpha-control-plane/src/services/repositories.js +9 -0
- package/examples/prealpha-control-plane/src/validation/schemas.js +6 -0
- package/examples/program-fs-process/README.md +31 -0
- package/examples/program-fs-process/sloppy.json +9 -0
- package/examples/program-fs-process/src/main.ts +27 -0
- package/examples/program-hello/README.md +32 -0
- package/examples/program-hello/main.ts +8 -0
- package/examples/program-hello/message.ts +1 -0
- package/examples/program-hello/sloppy.json +5 -0
- package/examples/rate-limit-auth/README.md +3 -0
- package/examples/rate-limit-auth/app.js +14 -0
- package/examples/rate-limit-basic/README.md +3 -0
- package/examples/rate-limit-basic/app.js +13 -0
- package/examples/rate-limit-redis/README.md +5 -0
- package/examples/rate-limit-redis/app.js +20 -0
- package/examples/rate-limit-testhost/README.md +4 -0
- package/examples/rate-limit-testhost/app.js +13 -0
- package/examples/rate-limit-websocket/README.md +3 -0
- package/examples/rate-limit-websocket/app.js +16 -0
- package/examples/realtime-auth/README.md +8 -0
- package/examples/realtime-auth/app.js +25 -0
- package/examples/realtime-auth/test.mjs +43 -0
- package/examples/realtime-chat/README.md +8 -0
- package/examples/realtime-chat/app.js +32 -0
- package/examples/realtime-chat/test.mjs +52 -0
- package/examples/realtime-dashboard/README.md +20 -0
- package/examples/realtime-dashboard/app.js +37 -0
- package/examples/realtime-presence/README.md +8 -0
- package/examples/realtime-presence/app.js +32 -0
- package/examples/realtime-presence/test.mjs +50 -0
- package/examples/realtime-testhost/README.md +8 -0
- package/examples/realtime-testhost/test.mjs +31 -0
- package/examples/redis-basic/README.md +17 -0
- package/examples/redis-basic/app.js +39 -0
- package/examples/redis-cache/README.md +14 -0
- package/examples/redis-cache/app.js +36 -0
- package/examples/redis-locks/README.md +13 -0
- package/examples/redis-locks/app.js +49 -0
- package/examples/request-context/README.md +32 -0
- package/examples/request-context/app.js +15 -0
- package/examples/sqlite-basic/README.md +52 -0
- package/examples/sqlite-basic/app.js +56 -0
- package/examples/sqlserver-basic/README.md +36 -0
- package/examples/sqlserver-basic/app.js +59 -0
- package/examples/static-files-basic/README.md +11 -0
- package/examples/static-files-basic/app.js +12 -0
- package/examples/static-files-basic/public/app.js +1 -0
- package/examples/static-files-basic/public/site.css +3 -0
- package/examples/static-files-package/README.md +12 -0
- package/examples/static-files-package/app.js +10 -0
- package/examples/static-files-package/public/index.html +2 -0
- package/examples/static-files-precompressed/README.md +12 -0
- package/examples/static-files-precompressed/app.js +11 -0
- package/examples/static-files-precompressed/public/app.js +1 -0
- package/examples/static-files-precompressed/public/app.js.br +0 -0
- package/examples/static-files-precompressed/public/app.js.gz +0 -0
- package/examples/static-files-spa/README.md +12 -0
- package/examples/static-files-spa/app.js +16 -0
- package/examples/static-files-spa/dist/assets/app.js +1 -0
- package/examples/static-files-spa/dist/index.html +4 -0
- package/examples/static-files-testhost/README.md +8 -0
- package/examples/static-files-testhost/app.js +13 -0
- package/examples/static-files-testhost/public/app.js +1 -0
- package/examples/static-files-testhost/public/app.js.gz +0 -0
- package/examples/static-files-testhost/test.mjs +38 -0
- package/examples/testhost-basic/README.md +26 -0
- package/examples/testhost-db/README.md +31 -0
- package/examples/testservices-postgres/README.md +68 -0
- package/examples/testservices-redis/README.md +71 -0
- package/examples/testservices-sqlserver/README.md +75 -0
- package/examples/time-basic/README.md +18 -0
- package/examples/time-basic/app.js +12 -0
- package/examples/time-deadline-cancellation/README.md +11 -0
- package/examples/time-deadline-cancellation/app.js +27 -0
- package/examples/time-fake-clock/README.md +14 -0
- package/examples/time-fake-clock/app.js +25 -0
- package/examples/time-interval-schedule/README.md +13 -0
- package/examples/time-interval-schedule/app.js +60 -0
- package/examples/users-api-sqlite/README.md +74 -0
- package/examples/users-api-sqlite/app.js +11 -0
- package/examples/users-api-sqlite/appsettings.Development.json +11 -0
- package/examples/users-api-sqlite/appsettings.json +11 -0
- package/examples/users-api-sqlite/modules/users.js +40 -0
- package/examples/users-api-sqlite/sloppy.json +5 -0
- package/examples/validation-errors/README.md +36 -0
- package/examples/validation-errors/app.js +14 -0
- package/examples/validation-errors/invalid-user.http +6 -0
- package/examples/validation-errors/sloppy.json +5 -0
- package/examples/web-dynamic-routes/README.md +17 -0
- package/examples/web-dynamic-routes/app.ts +27 -0
- package/examples/webhooks-basic/README.md +11 -0
- package/examples/webhooks-basic/app.js +48 -0
- package/examples/websocket-auth/README.md +8 -0
- package/examples/websocket-auth/app.js +16 -0
- package/examples/websocket-echo/README.md +9 -0
- package/examples/websocket-echo/app.js +36 -0
- package/examples/websocket-json-schema/README.md +5 -0
- package/examples/websocket-json-schema/app.js +25 -0
- package/examples/websocket-testhost/README.md +11 -0
- package/examples/websocket-testhost/test.mjs +49 -0
- package/examples/workers-background-service/README.md +7 -0
- package/examples/workers-background-service/app.js +16 -0
- package/examples/workers-js-isolate/README.md +8 -0
- package/examples/workers-js-isolate/app.js +19 -0
- package/examples/workers-js-isolate/workers/parser.ts +11 -0
- package/examples/workers-shutdown/README.md +6 -0
- package/examples/workers-shutdown/app.js +26 -0
- package/examples/workers-workerpool/README.md +6 -0
- package/examples/workers-workerpool/app.js +23 -0
- package/examples/workers-workqueue/README.md +8 -0
- package/examples/workers-workqueue/app.js +24 -0
- package/manifest.json +59 -0
- package/package.json +31 -0
- package/stdlib/sloppy/README.md +177 -0
- package/stdlib/sloppy/app.js +2142 -0
- package/stdlib/sloppy/auth.js +1813 -0
- package/stdlib/sloppy/bootstrap.manifest.json +83 -0
- package/stdlib/sloppy/cache.js +1542 -0
- package/stdlib/sloppy/codec.js +1153 -0
- package/stdlib/sloppy/config.js +61 -0
- package/stdlib/sloppy/crypto.js +312 -0
- package/stdlib/sloppy/data.js +2945 -0
- package/stdlib/sloppy/ffi.js +185 -0
- package/stdlib/sloppy/fs.js +795 -0
- package/stdlib/sloppy/health.js +603 -0
- package/stdlib/sloppy/http.js +1595 -0
- package/stdlib/sloppy/index.js +59 -0
- package/stdlib/sloppy/internal/bytes.js +31 -0
- package/stdlib/sloppy/internal/capabilities.js +155 -0
- package/stdlib/sloppy/internal/config.js +640 -0
- package/stdlib/sloppy/internal/disposable.js +31 -0
- package/stdlib/sloppy/internal/headers.js +63 -0
- package/stdlib/sloppy/internal/intrinsics.js +2 -0
- package/stdlib/sloppy/internal/json.js +20 -0
- package/stdlib/sloppy/internal/logging.js +278 -0
- package/stdlib/sloppy/internal/modules.js +405 -0
- package/stdlib/sloppy/internal/redaction.js +87 -0
- package/stdlib/sloppy/internal/routes.js +2279 -0
- package/stdlib/sloppy/internal/runtime-classic.js +19837 -0
- package/stdlib/sloppy/internal/services.js +690 -0
- package/stdlib/sloppy/internal/shared.js +32 -0
- package/stdlib/sloppy/internal/testhost-diagnostics.js +88 -0
- package/stdlib/sloppy/internal/testhost-http-server.js +238 -0
- package/stdlib/sloppy/internal/testhost-http.js +118 -0
- package/stdlib/sloppy/internal/testhost-loopback.js +50 -0
- package/stdlib/sloppy/internal/testservices-docker.js +154 -0
- package/stdlib/sloppy/internal/validation.js +117 -0
- package/stdlib/sloppy/metrics.js +427 -0
- package/stdlib/sloppy/net.js +5208 -0
- package/stdlib/sloppy/node/assert/strict.js +39 -0
- package/stdlib/sloppy/node/assert.js +228 -0
- package/stdlib/sloppy/node/buffer.js +247 -0
- package/stdlib/sloppy/node/console.js +33 -0
- package/stdlib/sloppy/node/constants.js +9 -0
- package/stdlib/sloppy/node/crypto.js +89 -0
- package/stdlib/sloppy/node/diagnostics_channel.js +41 -0
- package/stdlib/sloppy/node/events.js +113 -0
- package/stdlib/sloppy/node/fs/promises.js +27 -0
- package/stdlib/sloppy/node/fs.js +280 -0
- package/stdlib/sloppy/node/http.js +11 -0
- package/stdlib/sloppy/node/https.js +11 -0
- package/stdlib/sloppy/node/module.js +40 -0
- package/stdlib/sloppy/node/os.js +22 -0
- package/stdlib/sloppy/node/path.js +78 -0
- package/stdlib/sloppy/node/perf_hooks.js +12 -0
- package/stdlib/sloppy/node/process.js +129 -0
- package/stdlib/sloppy/node/querystring.js +21 -0
- package/stdlib/sloppy/node/stream/promises.js +3 -0
- package/stdlib/sloppy/node/stream.js +132 -0
- package/stdlib/sloppy/node/string_decoder.js +23 -0
- package/stdlib/sloppy/node/timers.js +26 -0
- package/stdlib/sloppy/node/tty.js +18 -0
- package/stdlib/sloppy/node/url.js +17 -0
- package/stdlib/sloppy/node/util.js +95 -0
- package/stdlib/sloppy/node/zlib.js +72 -0
- package/stdlib/sloppy/orm.js +2188 -0
- package/stdlib/sloppy/os.js +580 -0
- package/stdlib/sloppy/problem-details.js +29 -0
- package/stdlib/sloppy/providers/sqlite.js +26 -0
- package/stdlib/sloppy/rate-limit.js +856 -0
- package/stdlib/sloppy/realtime.js +1508 -0
- package/stdlib/sloppy/redis.js +1272 -0
- package/stdlib/sloppy/request-id.js +184 -0
- package/stdlib/sloppy/request-logging.js +101 -0
- package/stdlib/sloppy/results.js +933 -0
- package/stdlib/sloppy/schema.js +546 -0
- package/stdlib/sloppy/testing.js +4081 -0
- package/stdlib/sloppy/testservices.js +1041 -0
- package/stdlib/sloppy/time.js +894 -0
- package/stdlib/sloppy/webhooks.js +1330 -0
- package/stdlib/sloppy/workers.js +986 -0
- package/templates/api/README.md +82 -0
- package/templates/api/appsettings.Development.json +14 -0
- package/templates/api/appsettings.json +13 -0
- package/templates/api/data/.gitkeep +1 -0
- package/templates/api/gitignore +4 -0
- package/templates/api/migrations/0001_create_users.sql +1 -0
- package/templates/api/package.json +16 -0
- package/templates/api/public/hello.txt +1 -0
- package/templates/api/sloppy.json +14 -0
- package/templates/api/src/config.ts +1 -0
- package/templates/api/src/db/migrate.ts +14 -0
- package/templates/api/src/db/schema.ts +4 -0
- package/templates/api/src/db/usersRepository.ts +23 -0
- package/templates/api/src/main.ts +18 -0
- package/templates/api/src/models/user.ts +7 -0
- package/templates/api/src/routes/health.ts +20 -0
- package/templates/api/src/routes/users.ts +40 -0
- package/templates/api/src/services/usersService.ts +21 -0
- package/templates/api/tsconfig.json +15 -0
- package/templates/cli/README.md +16 -0
- package/templates/cli/gitignore +2 -0
- package/templates/cli/package.json +13 -0
- package/templates/cli/sloppy.json +6 -0
- package/templates/cli/src/commands/echo.ts +9 -0
- package/templates/cli/src/commands/inspect.ts +20 -0
- package/templates/cli/src/main.ts +50 -0
- package/templates/cli/tsconfig.json +15 -0
- package/templates/minimal-api/README.md +14 -0
- package/templates/minimal-api/gitignore +3 -0
- package/templates/minimal-api/package.json +14 -0
- package/templates/minimal-api/sloppy.json +5 -0
- package/templates/minimal-api/src/main.ts +9 -0
- package/templates/minimal-api/tsconfig.json +15 -0
- package/templates/node-compat/README.md +40 -0
- package/templates/node-compat/gitignore +2 -0
- package/templates/node-compat/package.json +11 -0
- package/templates/node-compat/sloppy.json +6 -0
- package/templates/node-compat/src/main.ts +40 -0
- package/templates/package-api/README.md +44 -0
- package/templates/package-api/fixtures/validator-lite/index.js +7 -0
- package/templates/package-api/fixtures/validator-lite/package.json +6 -0
- package/templates/package-api/gitignore +3 -0
- package/templates/package-api/package.json +17 -0
- package/templates/package-api/sloppy.json +5 -0
- package/templates/package-api/src/main.ts +10 -0
- package/templates/package-api/src/routes/health.ts +5 -0
- package/templates/package-api/src/routes/users.ts +12 -0
- package/templates/package-api/tsconfig.json +15 -0
- package/templates/program/README.md +12 -0
- package/templates/program/gitignore +1 -0
- package/templates/program/package.json +10 -0
- package/templates/program/sloppy.json +6 -0
- package/templates/program/src/main.ts +9 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
import { isPlainObject } from "./internal/validation.js";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
import * as net from "node:net";
|
|
4
|
+
|
|
5
|
+
import { ProviderHealth } from "./data.js";
|
|
6
|
+
import { Results } from "./results.js";
|
|
7
|
+
|
|
8
|
+
const HEALTH_STATUSES = Object.freeze(["healthy", "degraded", "unhealthy"]);
|
|
9
|
+
const STATUS_RANK = Object.freeze({
|
|
10
|
+
healthy: 0,
|
|
11
|
+
degraded: 1,
|
|
12
|
+
unhealthy: 2,
|
|
13
|
+
});
|
|
14
|
+
const SECRET_KEY_PATTERN = /authorization|cookie|credential|connection[-_]?string|password|secret|token|api[-_]?key|accesskey|private[-_]?key|client[-_]?secret|set[-_]?cookie/iu;
|
|
15
|
+
const DEFAULT_TIMEOUT_MS = 5000;
|
|
16
|
+
const DEFAULT_MAX_DATA_DEPTH = 4;
|
|
17
|
+
const DEFAULT_MAX_DATA_KEYS = 32;
|
|
18
|
+
const DEFAULT_MAX_STRING = 256;
|
|
19
|
+
|
|
20
|
+
function assertName(name, subject) {
|
|
21
|
+
if (typeof name !== "string" || name.length === 0 || name.includes("\0")) {
|
|
22
|
+
throw new TypeError(`Sloppy ${subject} name must be a non-empty string without NUL.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeStatus(status, fallback = "healthy") {
|
|
27
|
+
if (status === undefined) {
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
if (!HEALTH_STATUSES.includes(status)) {
|
|
31
|
+
throw new TypeError("Sloppy health status must be healthy, degraded, or unhealthy.");
|
|
32
|
+
}
|
|
33
|
+
return status;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeTags(tags = undefined) {
|
|
37
|
+
if (tags === undefined) {
|
|
38
|
+
return Object.freeze([]);
|
|
39
|
+
}
|
|
40
|
+
if (!Array.isArray(tags)) {
|
|
41
|
+
throw new TypeError("Sloppy health check tags must be an array.");
|
|
42
|
+
}
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
const output = [];
|
|
45
|
+
for (const tag of tags) {
|
|
46
|
+
if (typeof tag !== "string" || tag.length === 0 || tag.includes("\0")) {
|
|
47
|
+
throw new TypeError("Sloppy health check tags must be non-empty strings without NUL.");
|
|
48
|
+
}
|
|
49
|
+
if (!seen.has(tag)) {
|
|
50
|
+
seen.add(tag);
|
|
51
|
+
output.push(tag);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return Object.freeze(output);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeTimeoutMs(value) {
|
|
58
|
+
if (value === undefined) {
|
|
59
|
+
return DEFAULT_TIMEOUT_MS;
|
|
60
|
+
}
|
|
61
|
+
if (!Number.isInteger(value) || value < 0 || value > 0xffffffff) {
|
|
62
|
+
throw new TypeError("Sloppy health timeoutMs must be an integer from 0 to 4294967295.");
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeCacheMs(value) {
|
|
68
|
+
if (value === undefined) {
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
if (!Number.isInteger(value) || value < 0 || value > 0xffffffff) {
|
|
72
|
+
throw new TypeError("Sloppy health cacheMs must be an integer from 0 to 4294967295.");
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeCheckOptions(options = undefined) {
|
|
78
|
+
if (options === undefined) {
|
|
79
|
+
return Object.freeze({
|
|
80
|
+
tags: Object.freeze(["ready"]),
|
|
81
|
+
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
82
|
+
cacheMs: 0,
|
|
83
|
+
critical: true,
|
|
84
|
+
degradedIsUnhealthy: false,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (!isPlainObject(options)) {
|
|
88
|
+
throw new TypeError("Sloppy health check options must be a plain object.");
|
|
89
|
+
}
|
|
90
|
+
return Object.freeze({
|
|
91
|
+
tags: normalizeTags(options.tags ?? ["ready"]),
|
|
92
|
+
timeoutMs: normalizeTimeoutMs(options.timeoutMs),
|
|
93
|
+
cacheMs: normalizeCacheMs(options.cacheMs),
|
|
94
|
+
critical: options.critical !== false,
|
|
95
|
+
degradedIsUnhealthy: options.degradedIsUnhealthy === true,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function nowMs() {
|
|
100
|
+
if (globalThis.performance !== undefined && typeof globalThis.performance.now === "function") {
|
|
101
|
+
return globalThis.performance.now();
|
|
102
|
+
}
|
|
103
|
+
return Date.now();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function safeDate() {
|
|
107
|
+
return new Date().toISOString();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function redactValue(value, depth = 0) {
|
|
111
|
+
if (value === undefined) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
if (value === null || typeof value === "boolean" || typeof value === "number") {
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
if (typeof value === "string") {
|
|
118
|
+
return value.length > DEFAULT_MAX_STRING ? `${value.slice(0, DEFAULT_MAX_STRING)}...` : value;
|
|
119
|
+
}
|
|
120
|
+
if (typeof value === "bigint") {
|
|
121
|
+
return value.toString();
|
|
122
|
+
}
|
|
123
|
+
if (value instanceof Error) {
|
|
124
|
+
return Object.freeze({
|
|
125
|
+
name: typeof value.name === "string" ? value.name : "Error",
|
|
126
|
+
message: String(value.message ?? ""),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (depth >= DEFAULT_MAX_DATA_DEPTH) {
|
|
130
|
+
return "[truncated]";
|
|
131
|
+
}
|
|
132
|
+
if (Array.isArray(value)) {
|
|
133
|
+
return Object.freeze(value.slice(0, DEFAULT_MAX_DATA_KEYS).map((item) => redactValue(item, depth + 1)));
|
|
134
|
+
}
|
|
135
|
+
if (typeof value === "object") {
|
|
136
|
+
const output = {};
|
|
137
|
+
let count = 0;
|
|
138
|
+
for (const [key, nested] of Object.entries(value).sort(([left], [right]) => left.localeCompare(right))) {
|
|
139
|
+
if (count >= DEFAULT_MAX_DATA_KEYS) {
|
|
140
|
+
output.truncated = true;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
output[key] = SECRET_KEY_PATTERN.test(key) ? "[redacted]" : redactValue(nested, depth + 1);
|
|
144
|
+
count += 1;
|
|
145
|
+
}
|
|
146
|
+
return Object.freeze(output);
|
|
147
|
+
}
|
|
148
|
+
return String(value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeCheckValue(value) {
|
|
152
|
+
if (value === undefined || value === true) {
|
|
153
|
+
return { status: "healthy" };
|
|
154
|
+
}
|
|
155
|
+
if (value === false) {
|
|
156
|
+
return { status: "unhealthy" };
|
|
157
|
+
}
|
|
158
|
+
if (isPlainObject(value)) {
|
|
159
|
+
const status = typeof value.status === "string"
|
|
160
|
+
? normalizeStatus(value.status)
|
|
161
|
+
: typeof value.ok === "boolean"
|
|
162
|
+
? (value.ok ? "healthy" : "unhealthy")
|
|
163
|
+
: "healthy";
|
|
164
|
+
return {
|
|
165
|
+
status,
|
|
166
|
+
message: typeof value.message === "string" ? value.message : undefined,
|
|
167
|
+
errorCode: typeof value.errorCode === "string" ? value.errorCode : undefined,
|
|
168
|
+
diagnosticId: typeof value.diagnosticId === "string" ? value.diagnosticId : undefined,
|
|
169
|
+
data: value.data === undefined ? undefined : redactValue(value.data),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return { status: "healthy" };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function publicResult(name, outcome, started, checkedAtUtc, check, cached) {
|
|
176
|
+
const result = {
|
|
177
|
+
name,
|
|
178
|
+
status: outcome.status,
|
|
179
|
+
durationMs: Math.max(0, Math.round(nowMs() - started)),
|
|
180
|
+
checkedAtUtc,
|
|
181
|
+
tags: check.tags,
|
|
182
|
+
critical: check.critical,
|
|
183
|
+
cached,
|
|
184
|
+
timeoutMs: check.timeoutMs,
|
|
185
|
+
};
|
|
186
|
+
if (outcome.message !== undefined) {
|
|
187
|
+
result.message = outcome.message;
|
|
188
|
+
}
|
|
189
|
+
if (outcome.errorCode !== undefined) {
|
|
190
|
+
result.errorCode = outcome.errorCode;
|
|
191
|
+
}
|
|
192
|
+
if (outcome.diagnosticId !== undefined) {
|
|
193
|
+
result.diagnosticId = outcome.diagnosticId;
|
|
194
|
+
}
|
|
195
|
+
if (outcome.data !== undefined) {
|
|
196
|
+
result.data = outcome.data;
|
|
197
|
+
}
|
|
198
|
+
return Object.freeze(result);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function runWithTimeout(promise, timeoutMs, name) {
|
|
202
|
+
let timer;
|
|
203
|
+
const timeout = new Promise((resolve) => {
|
|
204
|
+
timer = setTimeout(() => {
|
|
205
|
+
resolve({
|
|
206
|
+
status: "unhealthy",
|
|
207
|
+
message: `health check '${name}' exceeded ${timeoutMs}ms`,
|
|
208
|
+
errorCode: "SLOPPY_E_HEALTH_TIMEOUT",
|
|
209
|
+
});
|
|
210
|
+
}, timeoutMs);
|
|
211
|
+
});
|
|
212
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function runOneCheck(check, context) {
|
|
216
|
+
const cacheNow = Date.now();
|
|
217
|
+
if (check.cacheMs > 0 && check.cache !== undefined && cacheNow < check.cache.expiresAt) {
|
|
218
|
+
return { ...check.cache.result, cached: true };
|
|
219
|
+
}
|
|
220
|
+
const started = nowMs();
|
|
221
|
+
const checkedAtUtc = safeDate();
|
|
222
|
+
let outcome;
|
|
223
|
+
try {
|
|
224
|
+
const run = Promise.resolve(check.check(context));
|
|
225
|
+
outcome = normalizeCheckValue(await runWithTimeout(run, check.timeoutMs, check.name));
|
|
226
|
+
} catch (error) {
|
|
227
|
+
outcome = {
|
|
228
|
+
status: "unhealthy",
|
|
229
|
+
message: String(error?.message ?? "health check failed"),
|
|
230
|
+
errorCode: "SLOPPY_E_HEALTH_CHECK_FAILED",
|
|
231
|
+
data: redactValue(error),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const result = publicResult(check.name, outcome, started, checkedAtUtc, check, false);
|
|
235
|
+
if (check.cacheMs > 0) {
|
|
236
|
+
check.cache = {
|
|
237
|
+
expiresAt: cacheNow + check.cacheMs,
|
|
238
|
+
result,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function checkMatchesMode(check, mode, tags) {
|
|
245
|
+
if (tags.length > 0 && !tags.every((tag) => check.tags.includes(tag))) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (mode === "live") {
|
|
249
|
+
return check.tags.includes("live");
|
|
250
|
+
}
|
|
251
|
+
if (mode === "ready") {
|
|
252
|
+
return check.tags.includes("ready");
|
|
253
|
+
}
|
|
254
|
+
if (mode === "startup") {
|
|
255
|
+
return check.tags.includes("startup");
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function aggregateStatus(results, checksByName) {
|
|
261
|
+
let rank = 0;
|
|
262
|
+
for (const result of Object.values(results)) {
|
|
263
|
+
const check = checksByName.get(result.name);
|
|
264
|
+
let status = result.status;
|
|
265
|
+
if (!result.critical && status === "unhealthy") {
|
|
266
|
+
status = "degraded";
|
|
267
|
+
}
|
|
268
|
+
if (result.critical && result.status === "degraded" && check?.degradedIsUnhealthy === true) {
|
|
269
|
+
status = "unhealthy";
|
|
270
|
+
}
|
|
271
|
+
rank = Math.max(rank, STATUS_RANK[status]);
|
|
272
|
+
}
|
|
273
|
+
return HEALTH_STATUSES[rank];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function summary(results) {
|
|
277
|
+
const counts = { healthy: 0, degraded: 0, unhealthy: 0 };
|
|
278
|
+
for (const result of Object.values(results)) {
|
|
279
|
+
counts[result.status] += 1;
|
|
280
|
+
}
|
|
281
|
+
return Object.freeze(counts);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function createHealthRegistry(options = undefined) {
|
|
285
|
+
if (options !== undefined && !isPlainObject(options)) {
|
|
286
|
+
throw new TypeError("Sloppy Health registry options must be a plain object.");
|
|
287
|
+
}
|
|
288
|
+
const checks = new Map();
|
|
289
|
+
const registry = {
|
|
290
|
+
check(name, check, checkOptions = undefined) {
|
|
291
|
+
assertName(name, "health check");
|
|
292
|
+
if (checks.has(name)) {
|
|
293
|
+
throw new TypeError(`Sloppy health check '${name}' is already registered.`);
|
|
294
|
+
}
|
|
295
|
+
if (typeof check !== "function") {
|
|
296
|
+
throw new TypeError("Sloppy health check must be a function.");
|
|
297
|
+
}
|
|
298
|
+
const normalized = normalizeCheckOptions(checkOptions);
|
|
299
|
+
checks.set(name, {
|
|
300
|
+
name,
|
|
301
|
+
check,
|
|
302
|
+
...normalized,
|
|
303
|
+
cache: undefined,
|
|
304
|
+
});
|
|
305
|
+
return registry;
|
|
306
|
+
},
|
|
307
|
+
checks() {
|
|
308
|
+
return Object.freeze([...checks.values()].map((check) => Object.freeze({
|
|
309
|
+
name: check.name,
|
|
310
|
+
tags: check.tags,
|
|
311
|
+
timeoutMs: check.timeoutMs,
|
|
312
|
+
cacheMs: check.cacheMs,
|
|
313
|
+
critical: check.critical,
|
|
314
|
+
})));
|
|
315
|
+
},
|
|
316
|
+
async evaluate(mode = "health", context = undefined, options = undefined) {
|
|
317
|
+
if (!["health", "live", "ready", "startup"].includes(mode)) {
|
|
318
|
+
throw new TypeError("Sloppy health mode must be health, live, ready, or startup.");
|
|
319
|
+
}
|
|
320
|
+
if (options !== undefined && !isPlainObject(options)) {
|
|
321
|
+
throw new TypeError("Sloppy health evaluate options must be a plain object.");
|
|
322
|
+
}
|
|
323
|
+
const tags = normalizeTags(options?.tags ?? []);
|
|
324
|
+
const started = nowMs();
|
|
325
|
+
const checkedAtUtc = safeDate();
|
|
326
|
+
const selected = [...checks.values()].filter((check) => checkMatchesMode(check, mode, tags));
|
|
327
|
+
const entries = {};
|
|
328
|
+
const checksByName = new Map(selected.map((check) => [check.name, check]));
|
|
329
|
+
const settled = await Promise.allSettled(selected.map((check) => runOneCheck(check, context)));
|
|
330
|
+
for (let index = 0; index < selected.length; index += 1) {
|
|
331
|
+
const check = selected[index];
|
|
332
|
+
const result = settled[index];
|
|
333
|
+
entries[check.name] = result.status === "fulfilled"
|
|
334
|
+
? result.value
|
|
335
|
+
: Object.freeze({
|
|
336
|
+
name: check.name,
|
|
337
|
+
status: "unhealthy",
|
|
338
|
+
message: String(result.reason?.message ?? "health check failed"),
|
|
339
|
+
errorCode: "SLOPPY_E_HEALTH_CHECK_FAILED",
|
|
340
|
+
durationMs: 0,
|
|
341
|
+
checkedAtUtc: safeDate(),
|
|
342
|
+
tags: check.tags,
|
|
343
|
+
critical: check.critical,
|
|
344
|
+
timeoutMs: check.timeoutMs,
|
|
345
|
+
cached: false,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
const status = aggregateStatus(entries, checksByName);
|
|
349
|
+
return Object.freeze({
|
|
350
|
+
status,
|
|
351
|
+
durationMs: Math.max(0, Math.round(nowMs() - started)),
|
|
352
|
+
checkedAtUtc,
|
|
353
|
+
checks: Object.freeze(entries),
|
|
354
|
+
summary: summary(entries),
|
|
355
|
+
});
|
|
356
|
+
},
|
|
357
|
+
resetCache() {
|
|
358
|
+
for (const check of checks.values()) {
|
|
359
|
+
check.cache = undefined;
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
return Object.freeze(registry);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function createHealthHandler(registry, mode, options = undefined) {
|
|
367
|
+
const unhealthyStatus = options?.unhealthyStatus ?? 503;
|
|
368
|
+
const degradedStatus = options?.degradedStatus ?? 200;
|
|
369
|
+
return async function healthHandler(context) {
|
|
370
|
+
const body = await registry.evaluate(mode, context);
|
|
371
|
+
if (body.status === "unhealthy") {
|
|
372
|
+
return Results.status(unhealthyStatus, body);
|
|
373
|
+
}
|
|
374
|
+
if (body.status === "degraded") {
|
|
375
|
+
return Results.status(degradedStatus, body);
|
|
376
|
+
}
|
|
377
|
+
return Results.ok(body);
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function selfCheck() {
|
|
382
|
+
return () => ({ status: "healthy", message: "process is alive" });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function runtimeCheck() {
|
|
386
|
+
return (ctx) => {
|
|
387
|
+
const shuttingDown = ctx?.lifecycle?.shuttingDown === true || ctx?.app?.lifecycle?.shuttingDown === true;
|
|
388
|
+
const startupComplete = ctx?.lifecycle?.startupComplete ?? ctx?.app?.lifecycle?.startupComplete ?? true;
|
|
389
|
+
if (startupComplete !== true) {
|
|
390
|
+
return { status: "unhealthy", message: "runtime startup is not complete", errorCode: "SLOPPY_E_RUNTIME_STARTING" };
|
|
391
|
+
}
|
|
392
|
+
return shuttingDown
|
|
393
|
+
? { status: "unhealthy", message: "runtime is shutting down", errorCode: "SLOPPY_E_RUNTIME_SHUTTING_DOWN" }
|
|
394
|
+
: { status: "healthy", message: "runtime is accepting requests" };
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function configCheck(required = []) {
|
|
399
|
+
const requiredKeys = Array.isArray(required) ? required : required?.required ?? [];
|
|
400
|
+
if (!Array.isArray(requiredKeys)) {
|
|
401
|
+
throw new TypeError("Health.config required keys must be an array.");
|
|
402
|
+
}
|
|
403
|
+
return (ctx) => {
|
|
404
|
+
const missing = [];
|
|
405
|
+
for (const key of requiredKeys) {
|
|
406
|
+
if (typeof key !== "string" || key.length === 0) {
|
|
407
|
+
throw new TypeError("Health.config required keys must be non-empty strings.");
|
|
408
|
+
}
|
|
409
|
+
const value = ctx?.config?.get?.(key, undefined);
|
|
410
|
+
if (value === undefined || value === null || value === "") {
|
|
411
|
+
missing.push(key);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return missing.length === 0
|
|
415
|
+
? { status: "healthy", data: { required: requiredKeys.length } }
|
|
416
|
+
: { status: "unhealthy", message: "required config is missing", errorCode: "SLOPPY_E_CONFIG_MISSING", data: { missing } };
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function dataCheck(provider, options = undefined) {
|
|
421
|
+
return async () => {
|
|
422
|
+
const result = await ProviderHealth.check(provider, options ?? {});
|
|
423
|
+
return { status: "healthy", data: result };
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function jobsCheck(resource = undefined, options = undefined) {
|
|
428
|
+
const maxDead = options?.maxDead ?? 0;
|
|
429
|
+
return () => {
|
|
430
|
+
if (resource === undefined || resource === null) {
|
|
431
|
+
return { status: "degraded", message: "job scheduler is not configured", data: { configured: false } };
|
|
432
|
+
}
|
|
433
|
+
const state = typeof resource.state === "function" ? resource.state() : resource.state;
|
|
434
|
+
const dead = Number(state?.dead ?? state?.failed ?? 0);
|
|
435
|
+
if (Number.isFinite(dead) && dead > maxDead) {
|
|
436
|
+
return { status: "degraded", message: `${dead} failed jobs exceed threshold`, data: { dead, maxDead } };
|
|
437
|
+
}
|
|
438
|
+
return { status: "healthy", data: redactValue(state) };
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function memoryCheck(options = undefined) {
|
|
443
|
+
const degradedRssBytes = options?.degradedRssBytes;
|
|
444
|
+
const unhealthyRssBytes = options?.unhealthyRssBytes;
|
|
445
|
+
return () => {
|
|
446
|
+
const memory = globalThis.process?.memoryUsage?.();
|
|
447
|
+
if (memory === undefined) {
|
|
448
|
+
return { status: "degraded", message: "memory usage is unavailable", data: { available: false } };
|
|
449
|
+
}
|
|
450
|
+
const rss = memory.rss;
|
|
451
|
+
if (typeof unhealthyRssBytes === "number" && rss >= unhealthyRssBytes) {
|
|
452
|
+
return { status: "unhealthy", message: "RSS exceeds unhealthy threshold", data: { rss } };
|
|
453
|
+
}
|
|
454
|
+
if (typeof degradedRssBytes === "number" && rss >= degradedRssBytes) {
|
|
455
|
+
return { status: "degraded", message: "RSS exceeds degraded threshold", data: { rss } };
|
|
456
|
+
}
|
|
457
|
+
return { status: "healthy", data: { rss, heapUsed: memory.heapUsed, heapTotal: memory.heapTotal } };
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function diskCheck(options = undefined) {
|
|
462
|
+
if (!isPlainObject(options)) {
|
|
463
|
+
throw new TypeError("Health.disk options must be a plain object.");
|
|
464
|
+
}
|
|
465
|
+
const targetPath = options.path;
|
|
466
|
+
if (typeof targetPath !== "string" || targetPath.length === 0) {
|
|
467
|
+
throw new TypeError("Health.disk path must be a non-empty string.");
|
|
468
|
+
}
|
|
469
|
+
return async () => {
|
|
470
|
+
await fs.access(targetPath);
|
|
471
|
+
if (typeof fs.statfs === "function" && options.minFreeBytes !== undefined) {
|
|
472
|
+
const stats = await fs.statfs(targetPath);
|
|
473
|
+
const free = Number(stats.bavail) * Number(stats.bsize);
|
|
474
|
+
if (free < options.minFreeBytes) {
|
|
475
|
+
return { status: "degraded", message: "disk free space is below threshold", data: { freeBytes: free, minFreeBytes: options.minFreeBytes } };
|
|
476
|
+
}
|
|
477
|
+
return { status: "healthy", data: { freeBytes: free } };
|
|
478
|
+
}
|
|
479
|
+
return { status: "healthy", data: { path: targetPath } };
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function httpCheck(url, options = undefined) {
|
|
484
|
+
if (typeof url !== "string" || url.length === 0) {
|
|
485
|
+
throw new TypeError("Health.http url must be a non-empty string.");
|
|
486
|
+
}
|
|
487
|
+
const expectedStatus = options?.expectedStatus ?? 200;
|
|
488
|
+
return async () => {
|
|
489
|
+
if (typeof fetch !== "function") {
|
|
490
|
+
return { status: "degraded", message: "fetch is unavailable", errorCode: "SLOPPY_E_HEALTH_HTTP_UNAVAILABLE" };
|
|
491
|
+
}
|
|
492
|
+
const response = await fetch(url, {
|
|
493
|
+
method: options?.method ?? "GET",
|
|
494
|
+
signal: options?.signal,
|
|
495
|
+
});
|
|
496
|
+
return response.status === expectedStatus
|
|
497
|
+
? { status: "healthy", data: { status: response.status } }
|
|
498
|
+
: { status: "unhealthy", message: "HTTP health target returned unexpected status", data: { status: response.status, expectedStatus } };
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function tcpCheck(host, port, options = undefined) {
|
|
503
|
+
if (typeof host !== "string" || host.length === 0 || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
504
|
+
throw new TypeError("Health.tcp requires a host string and TCP port.");
|
|
505
|
+
}
|
|
506
|
+
return async () => {
|
|
507
|
+
const timeoutMs = normalizeTimeoutMs(options?.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
508
|
+
return await new Promise((resolve) => {
|
|
509
|
+
const socket = net.createConnection({ host, port });
|
|
510
|
+
let done = false;
|
|
511
|
+
function finish(status, message = undefined) {
|
|
512
|
+
if (done) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
done = true;
|
|
516
|
+
socket.destroy();
|
|
517
|
+
resolve({ status, message, data: { host, port } });
|
|
518
|
+
}
|
|
519
|
+
socket.setTimeout(timeoutMs, () => finish("unhealthy", "TCP health target timed out"));
|
|
520
|
+
socket.once("connect", () => finish("healthy"));
|
|
521
|
+
socket.once("error", (error) => finish("unhealthy", String(error.message ?? error)));
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function realtimeCheck(backplane) {
|
|
527
|
+
if (backplane === undefined || typeof backplane.health !== "function") {
|
|
528
|
+
throw new TypeError("Health.realtime requires a Realtime backplane with health().");
|
|
529
|
+
}
|
|
530
|
+
return async () => {
|
|
531
|
+
const health = await backplane.health();
|
|
532
|
+
const status = health?.status === "ok" || health?.status === "healthy"
|
|
533
|
+
? "healthy"
|
|
534
|
+
: health?.status === "unavailable" || health?.status === "unhealthy"
|
|
535
|
+
? "unhealthy"
|
|
536
|
+
: "degraded";
|
|
537
|
+
return {
|
|
538
|
+
status,
|
|
539
|
+
message: status === "healthy" ? undefined : "Realtime backplane is not healthy",
|
|
540
|
+
data: redactValue({
|
|
541
|
+
kind: health?.kind,
|
|
542
|
+
status: health?.status,
|
|
543
|
+
}),
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function unavailableCheck(feature) {
|
|
549
|
+
return () => ({
|
|
550
|
+
status: "degraded",
|
|
551
|
+
message: `${feature} is unavailable in this app`,
|
|
552
|
+
errorCode: "SLOPPY_E_HEALTH_FEATURE_UNAVAILABLE",
|
|
553
|
+
data: { feature },
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function rateLimitCheck(store) {
|
|
558
|
+
if (store === undefined || store === null || store.__sloppyRateLimitStore !== true && store[Symbol.for("sloppy.rateLimit.store")] !== true) {
|
|
559
|
+
throw new TypeError("Health.rateLimit expects a RateLimit store.");
|
|
560
|
+
}
|
|
561
|
+
return async () => {
|
|
562
|
+
if (typeof store.health === "function") {
|
|
563
|
+
return store.health();
|
|
564
|
+
}
|
|
565
|
+
return { status: "healthy", data: { kind: store.kind ?? "custom" } };
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const Health = Object.freeze({
|
|
570
|
+
createRegistry: createHealthRegistry,
|
|
571
|
+
handler: createHealthHandler,
|
|
572
|
+
self: selfCheck,
|
|
573
|
+
runtime: runtimeCheck,
|
|
574
|
+
config: configCheck,
|
|
575
|
+
data: dataCheck,
|
|
576
|
+
jobs: jobsCheck,
|
|
577
|
+
disk: diskCheck,
|
|
578
|
+
memory: memoryCheck,
|
|
579
|
+
http: httpCheck,
|
|
580
|
+
tcp: tcpCheck,
|
|
581
|
+
realtime: realtimeCheck,
|
|
582
|
+
openApi: () => unavailableCheck("openapi"),
|
|
583
|
+
redis: (redis) => async () => redis === undefined
|
|
584
|
+
? { status: "degraded", message: "redis is not configured", data: { configured: false } }
|
|
585
|
+
: await redis.health(),
|
|
586
|
+
cache: (cache) => () => {
|
|
587
|
+
if (cache === undefined || cache === null) {
|
|
588
|
+
return { status: "degraded", message: "cache is not configured", data: { configured: false } };
|
|
589
|
+
}
|
|
590
|
+
const stats = typeof cache?.stats === "function" ? cache.stats() : cache.state;
|
|
591
|
+
if (stats?.disposed === true) {
|
|
592
|
+
return { status: "unhealthy", message: "cache is disposed", errorCode: "SLOPPY_E_CACHE_DISPOSED", data: redactValue(stats) };
|
|
593
|
+
}
|
|
594
|
+
return { status: "healthy", data: redactValue(stats ?? { configured: true }) };
|
|
595
|
+
},
|
|
596
|
+
storage: (storage) => () => storage === undefined
|
|
597
|
+
? { status: "degraded", message: "storage is not configured", data: { configured: false } }
|
|
598
|
+
: { status: "healthy", data: redactValue(storage.state ?? { configured: true }) },
|
|
599
|
+
rateLimit: rateLimitCheck,
|
|
600
|
+
redact: redactValue,
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
export { Health, createHealthHandler, createHealthRegistry, redactValue };
|