@fluojs/terminus 1.0.4 → 1.1.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/README.ko.md CHANGED
@@ -14,6 +14,7 @@ fluo 애플리케이션을 위한 헬스 인디케이터(Health Indicator) 툴
14
14
  - [DI 기반 인디케이터](#di-기반-인디케이터)
15
15
  - [실행 가드레일](#실행-가드레일)
16
16
  - [실패 시맨틱](#실패-시맨틱)
17
+ - [NestJS 마이그레이션 경계](#nestjs-마이그레이션-경계)
17
18
  - [공개 API 개요](#공개-api-개요)
18
19
  - [관련 패키지](#관련-패키지)
19
20
  - [예제 소스](#예제-소스)
@@ -24,10 +25,12 @@ fluo 애플리케이션을 위한 헬스 인디케이터(Health Indicator) 툴
24
25
  pnpm add @fluojs/terminus
25
26
  ```
26
27
 
27
- Redis indicator subpath를 사용할 때만 선택적 peer 설치하세요.
28
+ 사용하는 indicator provider seam에 필요한 선택적 peer 설치하세요.
28
29
 
29
30
  ```bash
30
31
  pnpm add @fluojs/redis ioredis
32
+ pnpm add @fluojs/prisma @prisma/client
33
+ pnpm add @fluojs/drizzle drizzle-orm
31
34
  ```
32
35
 
33
36
  ## 사용 시점
@@ -100,12 +103,16 @@ TerminusModule.forRoot({
100
103
 
101
104
  Drizzle의 경우 `createDrizzleHealthIndicatorProvider()`는 `@fluojs/drizzle`이 노출하는 lifecycle-aware `DrizzleDatabase` wrapper를 우선 사용합니다. Drizzle이 종료 중이거나 중지되었거나 `DrizzleDatabase.createPlatformStatusSnapshot()` 기준으로 준비되지 않은 상태이면 SQL probe를 실행하기 전에 해당 indicator를 `down`으로 보고합니다. legacy raw `DRIZZLE_DATABASE` handle만 등록된 경우에는 기존 lightweight SQL probe 동작을 유지합니다.
102
105
 
106
+ Prisma의 경우 `createPrismaHealthIndicatorProvider()`는 `@fluojs/prisma`가 노출하는 lifecycle-aware `PrismaService` / `PrismaServiceFacade` token을 우선 사용하고, probe 전에 `createPlatformStatusSnapshot()`을 확인한 뒤 `current()`를 호출해 ambient transaction/lifecycle seam이 health probe에 보이도록 합니다. 기본 Prisma registration을 대상으로 하려면 `name`을 생략하고, `PrismaModule.forRoot({ name })`으로 등록한 named registration을 대상으로 하려면 `name`을 전달하세요. 수동 provider graph에서는 명시적인 `serviceToken` / `clientToken` 값을 전달할 수 있습니다. raw Prisma client token만 등록된 경우에는 root package에서 optional Prisma peer를 import하지 않으면서 기존 lightweight query probe 동작을 유지합니다.
107
+
103
108
  Provider factory는 반복 등록할 수 있습니다. 각 인스턴스가 서로 다른 indicator key나 dependency option을 사용한다면 같은 factory가 만든 provider 여러 개를 하나의 `indicatorProviders` 배열에 등록할 수 있으며, Terminus는 나중에 등록된 같은 타입 provider가 앞선 provider를 덮어쓰지 않도록 각 provider 인스턴스를 별도 DI token으로 보관합니다.
104
109
 
105
110
  ### 실행 가드레일
106
111
 
107
112
  커스텀 인디케이터가 멈추거나 느린 하위 서비스에 의존할 수 있다면 `execution.indicatorTimeoutMs`를 사용하세요. probe가 설정된 시간을 넘기면 Terminus는 무기한 대기하지 않고 해당 인디케이터를 `down`으로 표시합니다.
108
113
 
114
+ Terminus는 각 `TerminusHealthService` / application container 안에서 같은 indicator instance에 대한 check도 직렬화합니다. Timeout된 probe나 느린 probe가 아직 실행 중일 때 같은 container의 다른 `/health` 또는 `/ready` 요청이 들어오면, Terminus는 같은 downstream에 겹치는 probe를 새로 시작하지 않고 해당 indicator를 새 요청에서 `down`으로 보고합니다. 테스트나 multi-app process가 같은 indicator object를 재사용하더라도 별도 application container는 독립적인 in-flight state를 유지합니다. Built-in HTTP indicator는 자체 timeout이 만료되면 `fetch` 요청을 abort하지만, 다른 driver와 custom callback은 cancellation을 노출하지 않을 수 있으므로 원래 promise가 settle될 때까지 overlap을 막는 방식으로 보호합니다.
115
+
109
116
  ```typescript
110
117
  TerminusModule.forRoot({
111
118
  execution: {
@@ -121,7 +128,7 @@ TerminusModule.forRoot({
121
128
 
122
129
  ### 실패 시맨틱
123
130
 
124
- 인디케이터가 실패하면 `HealthCheckError`를 던집니다. `TerminusHealthService`는 이 실패들을 모아 보고서를 작성합니다.
131
+ 인디케이터가 `down` 결과를 반환하거나 `HealthCheckError`를 던지면, `TerminusHealthService`는 이 실패들을 모아 보고서를 작성합니다.
125
132
 
126
133
  - 하나 이상의 인디케이터가 실패하면 `/health`는 HTTP `503`을 반환합니다.
127
134
  - 등록된 indicator가 실패하거나, custom readiness check가 `false`를 반환하거나, runtime shutdown이 시작되었거나, platform readiness가 `ready`가 아닌 경우 `/ready`는 HTTP `503`을 반환합니다. Platform `critical` metadata는 diagnostics에 보존되지만 HTTP readiness endpoint 자체는 binary ready/unavailable gate이며 warning severity bucket을 노출하지 않습니다.
@@ -130,11 +137,20 @@ TerminusModule.forRoot({
130
137
  - 지원하지 않는 status, 빈 결과, 객체가 아닌 인디케이터 결과는 조용히 버려지지 않고 `down` 진단으로 보고됩니다.
131
138
  - Blank indicator result key는 healthy entry로 기여하지 않고 `down` 진단으로 보고됩니다.
132
139
  - 같은 실행에서 이미 보고된 key를 다른 인디케이터가 다시 사용하면, Terminus는 먼저 기록된 entry를 유지하고 데이터를 조용히 덮어쓰는 대신 결정적인 `*-duplicate-key-error` contributor를 추가합니다.
133
- - 플랫폼 health/readiness 실패는 `/health` 응답에서 결정적인 `fluo-platform-health`, `fluo-platform-readiness` contributor로 노출됩니다.
140
+ - 플랫폼 health/readiness 실패는 `/health` 응답에서 결정적인 `fluo-platform-health`, `fluo-platform-readiness` contributor로 노출됩니다. 이 key들은 platform diagnostic용으로 예약되어 있으며, platform failure 중 user indicator가 같은 key를 반환하면 Terminus는 platform payload를 예약 key 아래에 유지하고 runtime state를 떨어뜨리지 않도록 결정적인 `*-user-key-collision` diagnostic을 추가합니다.
134
141
  - Runtime diagnostics가 있으면 `/health` response에 platform health/readiness detail을 담은 `platform` block이 포함될 수 있습니다.
142
+ - DI provider로 생성한 Prisma indicator는 query보다 먼저 `@fluojs/prisma` service lifecycle readiness/health state를 반영하므로, raw client handle이 아직 호출 가능하더라도 종료 중이거나 중지되었거나 아직 연결되지 않은 통합은 `/health`와 `/ready`를 unavailable로 표시합니다.
135
143
  - DI provider로 생성한 Drizzle indicator는 SQL probe보다 먼저 Drizzle lifecycle readiness/health state를 반영하므로, underlying driver가 raw ping을 아직 받을 수 있어도 종료 중이거나 중지된 통합은 `/health`와 `/ready`를 unavailable로 표시합니다.
136
144
  - Redis subpath로 생성한 Redis indicator는 `PING` 전에 `@fluojs/redis` client lifecycle state를 반영하므로, 종료 중이거나 연결이 끊긴 Redis client는 command 실행 전에도 `/health`와 `/ready`를 unavailable로 표시합니다.
137
145
 
146
+ ## NestJS 마이그레이션 경계
147
+
148
+ `@nestjs/terminus`에서 마이그레이션할 때는 `TerminusModule.forRoot(...)`를 fluo의 기본 API로 취급하세요. fluo는 `HealthCheckService.check([...])`를 호출하는 controller-level `@HealthCheck()` 메서드를 주요 애플리케이션 계약으로 모델링하지 않습니다. 테스트나 커스텀 애플리케이션 코드에서 `TerminusHealthService.check()`를 직접 호출할 수는 있지만, 프로덕션 엔드포인트 등록은 indicator와 readiness hook을 module option에 두어 runtime `/health`와 `/ready` 경로가 platform diagnostics를 일관되게 포함하도록 해야 합니다.
149
+
150
+ Terminus는 별도의 process-only liveness route도 기본으로 만들지 않습니다. 기본 route model은 집계 헬스를 위한 `GET /health`, readiness를 위한 `GET /ready`입니다. 배포 환경에서 좁은 의미의 process liveness probe가 필요하다면, Terminus가 NestJS-style 추가 route를 만들어 준다고 가정하지 말고 애플리케이션 또는 배포 계층에서 해당 probe를 정의하세요.
151
+
152
+ Runtime-specific indicator는 subpath별로 분리되어 있습니다. Node.js memory 및 disk check에는 `@fluojs/terminus/node`를 사용하고, Redis check에는 `@fluojs/terminus/redis`를 사용하세요. Prisma와 Drizzle provider helper는 token-only DI seam을 해석하므로 해당 선택적 peer가 없어도 root package import는 안전하게 유지되며, Node disk filesystem access도 lazy하게 유지되어 애플리케이션이 runtime-specific probe에 명시적으로 opt in합니다.
153
+
138
154
  ## 공개 API 개요
139
155
 
140
156
  ### `TerminusModule`
@@ -153,7 +169,7 @@ TerminusModule.forRoot({
153
169
  ### 직접 helper와 token
154
170
 
155
171
  - `runHealthCheck(...)`, `assertHealthCheck(...)`: 직접 aggregation/testing helper입니다.
156
- - `TERMINUS_HEALTH_INDICATORS`, `TERMINUS_INDICATOR_PROVIDER_TOKENS`: 등록된 indicator와 provider token을 위한 DI token입니다.
172
+ - `TERMINUS_HEALTH_INDICATORS`, `TERMINUS_INDICATOR_PROVIDER_TOKENS`: 등록된 indicator와 provider token을 위한 DI token입니다. `TerminusModule.forRoot(...)`는 두 token을 모두 export하므로 downstream module은 Terminus 내부를 재구성하지 않고도 구성된 indicator/provider-token set을 확인할 수 있습니다.
157
173
  - Built-in indicator는 `create*HealthIndicator()` 및 `create*HealthIndicatorProvider()` helper도 노출합니다. Provider helper는 `indicatorProviders`를 위한 의도적인 DI composition 예외이며, 애플리케이션 등록은 계속 `TerminusModule.forRoot(...)`를 사용해야 합니다.
158
174
 
159
175
  ### `@fluojs/terminus/redis`
package/README.md CHANGED
@@ -14,6 +14,7 @@ Health indicator toolkit for fluo applications. `@fluojs/terminus` layers on top
14
14
  - [DI-Backed Indicators](#di-backed-indicators)
15
15
  - [Execution Guardrails](#execution-guardrails)
16
16
  - [Failure Semantics](#failure-semantics)
17
+ - [NestJS Migration Boundaries](#nestjs-migration-boundaries)
17
18
  - [Public API Overview](#public-api-overview)
18
19
  - [Related Packages](#related-packages)
19
20
  - [Example Sources](#example-sources)
@@ -24,10 +25,12 @@ Health indicator toolkit for fluo applications. `@fluojs/terminus` layers on top
24
25
  pnpm add @fluojs/terminus
25
26
  ```
26
27
 
27
- Install the optional peer only when you use the Redis indicator subpath:
28
+ Install optional peers only for the indicator provider seams you enable:
28
29
 
29
30
  ```bash
30
31
  pnpm add @fluojs/redis ioredis
32
+ pnpm add @fluojs/prisma @prisma/client
33
+ pnpm add @fluojs/drizzle drizzle-orm
31
34
  ```
32
35
 
33
36
  ## When to Use
@@ -100,12 +103,16 @@ Redis indicators created through `@fluojs/terminus/redis` are lifecycle-aware wh
100
103
 
101
104
  For Drizzle, `createDrizzleHealthIndicatorProvider()` prefers the lifecycle-aware `DrizzleDatabase` wrapper exported by `@fluojs/drizzle`. The indicator reports `down` before probing SQL whenever Drizzle is shutting down, stopped, or otherwise not ready according to `DrizzleDatabase.createPlatformStatusSnapshot()`. If only the legacy raw `DRIZZLE_DATABASE` handle is registered, the provider keeps the previous lightweight SQL probe behavior.
102
105
 
106
+ For Prisma, `createPrismaHealthIndicatorProvider()` prefers the lifecycle-aware `PrismaService` / `PrismaServiceFacade` token exported by `@fluojs/prisma`, checks `createPlatformStatusSnapshot()` before probing, and then calls `current()` so ambient transaction/lifecycle seams stay visible to the health probe. Omit `name` to target the default Prisma registration, pass `name` to target `PrismaModule.forRoot({ name })`, or pass explicit `serviceToken` / `clientToken` values for manual provider graphs. If only a raw Prisma client token is registered, the provider keeps the previous lightweight query probe behavior without importing the optional Prisma peer from the root package.
107
+
103
108
  Provider factories are repeatable. You may register multiple providers created by the same factory in one `indicatorProviders` array when each instance uses a distinct indicator key or dependency option; Terminus keeps every provider instance under its own DI token instead of letting later same-type providers overwrite earlier ones.
104
109
 
105
110
  ### Execution Guardrails
106
111
 
107
112
  Use `execution.indicatorTimeoutMs` when custom indicators might hang or depend on slow downstreams. When a probe exceeds the configured timeout, Terminus marks that indicator as `down` instead of waiting forever.
108
113
 
114
+ Terminus also serializes checks per indicator instance inside each `TerminusHealthService` / application container. If a timed-out or otherwise slow probe is still running when another `/health` or `/ready` request arrives for the same container, Terminus reports that indicator as `down` for the new request instead of starting an overlapping probe against the same downstream. Separate application containers keep independent in-flight state even when tests or multi-app processes reuse the same indicator object. Built-in HTTP indicators abort their `fetch` request when their own timeout expires; other drivers and custom callbacks may not expose cancellation, so they are protected from overlap until the original promise settles.
115
+
109
116
  ```typescript
110
117
  TerminusModule.forRoot({
111
118
  execution: {
@@ -121,7 +128,7 @@ Use `path` to mount the health endpoints under a custom path, and `readinessChec
121
128
 
122
129
  ### Failure Semantics
123
130
 
124
- When an indicator fails, it throws a `HealthCheckError`. The `TerminusHealthService` aggregates these failures into a report:
131
+ When an indicator returns a `down` result or throws a `HealthCheckError`, the `TerminusHealthService` aggregates the failure into a report:
125
132
 
126
133
  - `/health` returns HTTP `503` if any indicator fails.
127
134
  - `/ready` returns HTTP `503` when registered indicators fail, a custom readiness check returns `false`, runtime shutdown has begun, or platform readiness is anything other than `ready`. Platform `critical` metadata is preserved in diagnostics, but the HTTP readiness endpoint itself is a binary ready/unavailable gate and does not expose warning severity buckets.
@@ -130,11 +137,20 @@ When an indicator fails, it throws a `HealthCheckError`. The `TerminusHealthServ
130
137
  - Unsupported, empty, or non-object indicator results are reported as `down` diagnostics instead of being silently discarded.
131
138
  - Blank indicator result keys are reported as `down` diagnostics instead of contributing healthy entries.
132
139
  - If an indicator reuses a key that was already reported earlier in the same run, Terminus keeps the first entry and adds a deterministic `*-duplicate-key-error` contributor instead of silently overwriting data.
133
- - Platform health/readiness failures are surfaced as deterministic `fluo-platform-health` and `fluo-platform-readiness` contributors in `/health` responses.
140
+ - Platform health/readiness failures are surfaced as deterministic `fluo-platform-health` and `fluo-platform-readiness` contributors in `/health` responses. These keys are reserved for platform diagnostics; if a user indicator returns one of them during a platform failure, Terminus keeps the platform payload under the reserved key and adds a deterministic `*-user-key-collision` diagnostic instead of dropping runtime state.
134
141
  - `/health` responses may include a `platform` block with platform health/readiness details when runtime diagnostics are available.
142
+ - Prisma indicators created through the DI provider map `@fluojs/prisma` service lifecycle readiness/health state before querying, so shutdown, stopped, or not-yet-connected integrations mark `/health` and `/ready` unavailable even when the raw client handle is still callable.
135
143
  - Drizzle indicators created through the DI provider map Drizzle lifecycle readiness/health state before SQL probing, so shutdown or stopped integrations mark `/health` and `/ready` as unavailable even if the underlying driver still accepts a raw ping.
136
144
  - Redis indicators created through the Redis subpath map `@fluojs/redis` client lifecycle state before `PING`, so shutdown or disconnected Redis clients mark `/health` and `/ready` as unavailable even before command execution.
137
145
 
146
+ ## NestJS Migration Boundaries
147
+
148
+ When migrating from `@nestjs/terminus`, treat `TerminusModule.forRoot(...)` as the primary fluo API. fluo does not model controller-level `@HealthCheck()` methods that call `HealthCheckService.check([...])` as the main application contract. You can still call `TerminusHealthService.check()` directly from tests or custom application code, but production endpoint registration should keep indicators and readiness hooks in module options so the runtime `/health` and `/ready` routes include platform diagnostics consistently.
149
+
150
+ Terminus also does not create a separate process-only liveness route by default. The default route model remains `GET /health` for aggregated health and `GET /ready` for readiness. If your deployment requires a narrow process liveness probe, define that probe at the application or deployment layer instead of assuming Terminus will add a NestJS-style extra route.
151
+
152
+ Runtime-specific indicators are split by subpath. Use `@fluojs/terminus/node` for Node.js memory and disk checks, and use `@fluojs/terminus/redis` for Redis checks. Prisma and Drizzle provider helpers resolve token-only DI seams so the root package stays import-safe when those optional peers are absent, and Node disk filesystem access stays lazy so applications opt into runtime-specific probes explicitly.
153
+
138
154
  ## Public API Overview
139
155
 
140
156
  ### `TerminusModule`
@@ -153,7 +169,7 @@ When an indicator fails, it throws a `HealthCheckError`. The `TerminusHealthServ
153
169
  ### Direct helpers and tokens
154
170
 
155
171
  - `runHealthCheck(...)`, `assertHealthCheck(...)`: Direct aggregation/testing helpers.
156
- - `TERMINUS_HEALTH_INDICATORS`, `TERMINUS_INDICATOR_PROVIDER_TOKENS`: DI tokens for registered indicators and provider tokens.
172
+ - `TERMINUS_HEALTH_INDICATORS`, `TERMINUS_INDICATOR_PROVIDER_TOKENS`: DI tokens for registered indicators and provider tokens. `TerminusModule.forRoot(...)` exports both tokens so downstream modules can inspect the composed indicator/provider-token set without rebuilding Terminus internals.
157
173
  - Built-in indicators also expose `create*HealthIndicator()` and `create*HealthIndicatorProvider()` helpers. Provider helpers are intentional DI-composition exceptions for `indicatorProviders`, while application registration should still go through `TerminusModule.forRoot(...)`.
158
174
 
159
175
  ### `@fluojs/terminus/redis`
@@ -2,6 +2,11 @@ import type { HealthCheckExecutionOptions, HealthCheckReport, HealthIndicator }
2
2
  /**
3
3
  * Run every registered health indicator and aggregate their results.
4
4
  *
5
+ * @remarks
6
+ * Direct helper calls receive an isolated execution scope. Use `TerminusHealthService`
7
+ * when repeated checks should serialize overlapping probes for a container-owned
8
+ * indicator set.
9
+ *
5
10
  * @param indicators Indicator instances to execute for the current health probe.
6
11
  * @param executionOptions Optional timeout guardrails for indicator execution.
7
12
  * @returns A structured report containing `info`, `error`, and full `details` maps.
@@ -20,6 +25,7 @@ export declare function assertHealthCheck(report: HealthCheckReport, message?: s
20
25
  export declare class TerminusHealthService {
21
26
  private readonly indicators;
22
27
  private readonly executionOptions;
28
+ private readonly runningIndicatorChecks;
23
29
  constructor(indicators: readonly HealthIndicator[], executionOptions?: HealthCheckExecutionOptions);
24
30
  /**
25
31
  * Execute all registered indicators once.
@@ -1 +1 @@
1
- {"version":3,"file":"health-check.d.ts","sourceRoot":"","sources":["../src/health-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,2BAA2B,EAC3B,iBAAiB,EACjB,eAAe,EAGhB,MAAM,YAAY,CAAC;AAyQpB;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,gBAAgB,GAAE,2BAAgC,GACjD,OAAO,CAAC,iBAAiB,CAAC,CAoB5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,SAAyB,GAAG,iBAAiB,CAMhH;AAED,0FAA0F;AAC1F,qBAAa,qBAAqB;IAE9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBADhB,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,gBAAgB,GAAE,2BAAgC;IAGrE;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAIzC;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGpC"}
1
+ {"version":3,"file":"health-check.d.ts","sourceRoot":"","sources":["../src/health-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,2BAA2B,EAC3B,iBAAiB,EACjB,eAAe,EAGhB,MAAM,YAAY,CAAC;AAsVpB;;;;;;;;;;;GAWG;AACH,wBAAsB,cAAc,CAClC,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,gBAAgB,GAAE,2BAAgC,GACjD,OAAO,CAAC,iBAAiB,CAAC,CAE5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,SAAyB,GAAG,iBAAiB,CAMhH;AAED,0FAA0F;AAC1F,qBAAa,qBAAqB;IAI9B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAJnC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAyC;gBAG7D,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,gBAAgB,GAAE,2BAAgC;IAGrE;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAIzC;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAGpC"}
@@ -8,6 +8,32 @@ function normalizeIndicatorTimeoutMs(value) {
8
8
  function createTimeoutMessage(timeoutMs) {
9
9
  return `Health indicator timed out after ${String(timeoutMs)}ms.`;
10
10
  }
11
+ function createInFlightResult(key) {
12
+ return {
13
+ [key]: {
14
+ message: 'A previous health indicator probe is still running; Terminus will not start an overlapping probe for the same indicator instance.',
15
+ status: 'down'
16
+ }
17
+ };
18
+ }
19
+ function startSerializedIndicatorCheck(indicator, key, runningIndicatorChecks) {
20
+ const runningCheck = runningIndicatorChecks.get(indicator);
21
+ if (runningCheck) {
22
+ return undefined;
23
+ }
24
+ const check = Promise.resolve().then(() => indicator.check(key));
25
+ runningIndicatorChecks.set(indicator, check);
26
+ check.then(() => {
27
+ if (runningIndicatorChecks.get(indicator) === check) {
28
+ runningIndicatorChecks.delete(indicator);
29
+ }
30
+ }, () => {
31
+ if (runningIndicatorChecks.get(indicator) === check) {
32
+ runningIndicatorChecks.delete(indicator);
33
+ }
34
+ });
35
+ return check;
36
+ }
11
37
  async function withTimeout(promise, timeoutMs) {
12
38
  let timer;
13
39
  try {
@@ -134,11 +160,18 @@ function createDuplicateKeyFailure(indicatorKey, duplicateKeys, seenKeys) {
134
160
  status: 'down'
135
161
  }];
136
162
  }
137
- async function runIndicator(indicator, index, executionOptions) {
163
+ async function runIndicator(indicator, index, executionOptions, runningIndicatorChecks) {
138
164
  const key = inferIndicatorKey(indicator, index);
139
165
  const indicatorTimeoutMs = normalizeIndicatorTimeoutMs(executionOptions.indicatorTimeoutMs);
166
+ const runningCheck = startSerializedIndicatorCheck(indicator, key, runningIndicatorChecks);
167
+ if (!runningCheck) {
168
+ return {
169
+ entries: Object.entries(createInFlightResult(key)),
170
+ indicatorKey: key
171
+ };
172
+ }
140
173
  try {
141
- const result = indicatorTimeoutMs === undefined ? await indicator.check(key) : await withTimeout(indicator.check(key), indicatorTimeoutMs);
174
+ const result = indicatorTimeoutMs === undefined ? await runningCheck : await withTimeout(runningCheck, indicatorTimeoutMs);
142
175
  return {
143
176
  entries: normalizeIndicatorResult(key, result),
144
177
  indicatorKey: key
@@ -178,16 +211,8 @@ function aggregateIndicatorEntries(checks) {
178
211
  }
179
212
  return aggregatedEntries;
180
213
  }
181
-
182
- /**
183
- * Run every registered health indicator and aggregate their results.
184
- *
185
- * @param indicators Indicator instances to execute for the current health probe.
186
- * @param executionOptions Optional timeout guardrails for indicator execution.
187
- * @returns A structured report containing `info`, `error`, and full `details` maps.
188
- */
189
- export async function runHealthCheck(indicators, executionOptions = {}) {
190
- const checks = aggregateIndicatorEntries(await Promise.all(indicators.map((indicator, index) => runIndicator(indicator, index, executionOptions))));
214
+ async function executeHealthCheck(indicators, executionOptions = {}, runningIndicatorChecks) {
215
+ const checks = aggregateIndicatorEntries(await Promise.all(indicators.map((indicator, index) => runIndicator(indicator, index, executionOptions, runningIndicatorChecks))));
191
216
  const details = Object.fromEntries(checks);
192
217
  const infoEntries = checks.filter(([, result]) => result.status === 'up');
193
218
  const errorEntries = checks.filter(([, result]) => result.status === 'down');
@@ -204,6 +229,22 @@ export async function runHealthCheck(indicators, executionOptions = {}) {
204
229
  };
205
230
  }
206
231
 
232
+ /**
233
+ * Run every registered health indicator and aggregate their results.
234
+ *
235
+ * @remarks
236
+ * Direct helper calls receive an isolated execution scope. Use `TerminusHealthService`
237
+ * when repeated checks should serialize overlapping probes for a container-owned
238
+ * indicator set.
239
+ *
240
+ * @param indicators Indicator instances to execute for the current health probe.
241
+ * @param executionOptions Optional timeout guardrails for indicator execution.
242
+ * @returns A structured report containing `info`, `error`, and full `details` maps.
243
+ */
244
+ export async function runHealthCheck(indicators, executionOptions = {}) {
245
+ return executeHealthCheck(indicators, executionOptions, new WeakMap());
246
+ }
247
+
207
248
  /**
208
249
  * Assert that an aggregated health report is fully healthy.
209
250
  *
@@ -221,6 +262,7 @@ export function assertHealthCheck(report, message = 'Health check failed.') {
221
262
 
222
263
  /** Service facade that resolves and runs the health indicators registered in Terminus. */
223
264
  export class TerminusHealthService {
265
+ runningIndicatorChecks = new WeakMap();
224
266
  constructor(indicators, executionOptions = {}) {
225
267
  this.indicators = indicators;
226
268
  this.executionOptions = executionOptions;
@@ -232,7 +274,7 @@ export class TerminusHealthService {
232
274
  * @returns The aggregated health report for this check cycle.
233
275
  */
234
276
  async check() {
235
- return runHealthCheck(this.indicators, this.executionOptions);
277
+ return executeHealthCheck(this.indicators, this.executionOptions, this.runningIndicatorChecks);
236
278
  }
237
279
 
238
280
  /**
@@ -1,5 +1,5 @@
1
1
  import { optional } from '@fluojs/di';
2
- import { createDownResult, createUpResult, resolveIndicatorKey, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
2
+ import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
3
3
  const DRIZZLE_DATABASE = Symbol.for('fluo.drizzle.database');
4
4
  const DRIZZLE_HANDLE_PROVIDER = Symbol.for('fluo.drizzle.handle-provider');
5
5
 
@@ -85,8 +85,8 @@ export class DrizzleHealthIndicator {
85
85
  }
86
86
  async check(key) {
87
87
  const indicatorKey = resolveIndicatorKey('drizzle', this.options.key ?? key);
88
- const timeoutMs = this.options.timeoutMs ?? DEFAULT_DRIZZLE_TIMEOUT_MS;
89
88
  try {
89
+ const timeoutMs = resolveIndicatorTimeoutMs(this.options.timeoutMs, DEFAULT_DRIZZLE_TIMEOUT_MS, indicatorKey);
90
90
  const snapshot = createDrizzleLifecycleSnapshot(this.options.handleProvider);
91
91
  const lifecycleDownResult = snapshot ? createDrizzleLifecycleDownResult(indicatorKey, snapshot) : undefined;
92
92
  if (lifecycleDownResult) {
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/indicators/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE1E,wDAAwD;AACxD,MAAM,WAAW,0BAA0B;IACzC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AAoBD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,eAAe,CAE9F;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,0BAA0B,GAAG,QAAQ,CAO/F;AAED,6EAA6E;AAC7E,qBAAa,mBAAoB,YAAW,eAAe;IAG7C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEJ,OAAO,EAAE,0BAA0B;IAI1D,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CA6CzD"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/indicators/http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAE1E,wDAAwD;AACxD,MAAM,WAAW,0BAA0B;IACzC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AA8BD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,eAAe,CAE9F;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,0BAA0B,GAAG,QAAQ,CAO/F;AAED,6EAA6E;AAC7E,qBAAa,mBAAoB,YAAW,eAAe;IAG7C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEJ,OAAO,EAAE,0BAA0B;IAI1D,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAmDzD"}
@@ -1,4 +1,4 @@
1
- import { createDownResult, createUpResult, resolveIndicatorKey, throwHealthCheckError } from './utils.js';
1
+ import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError } from './utils.js';
2
2
  import { HealthCheckError } from '../errors.js';
3
3
 
4
4
  /** Options for probing one upstream HTTP dependency. */
@@ -16,6 +16,13 @@ function isExpectedStatus(status, expected) {
16
16
  }
17
17
  return status >= 200 && status < 300;
18
18
  }
19
+ function cancelResponseBody(response) {
20
+ const body = response.body;
21
+ if (!body) {
22
+ return;
23
+ }
24
+ void body.cancel().catch(() => undefined);
25
+ }
19
26
 
20
27
  /**
21
28
  * Create an HTTP-backed health indicator.
@@ -50,39 +57,45 @@ export class HttpHealthIndicator {
50
57
  }
51
58
  async check(key) {
52
59
  const indicatorKey = resolveIndicatorKey('http', this.options.key ?? key);
53
- const timeoutMs = this.options.timeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
54
60
  const method = this.options.method ?? 'GET';
55
- const abortController = new AbortController();
56
- const startedAt = Date.now();
57
- const timeout = setTimeout(() => {
58
- abortController.abort(new Error(`HTTP health check timed out after ${String(timeoutMs)}ms.`));
59
- }, timeoutMs);
60
61
  try {
61
- const response = await fetch(this.options.url, {
62
- headers: this.options.headers,
63
- method,
64
- signal: abortController.signal
65
- });
66
- const responseTimeMs = Date.now() - startedAt;
67
- if (!isExpectedStatus(response.status, this.options.expectedStatus)) {
68
- throwHealthCheckError('HTTP health check failed.', createDownResult(indicatorKey, `Unexpected status code ${String(response.status)} from ${this.options.url}.`, {
69
- responseTimeMs,
70
- statusCode: response.status,
71
- url: this.options.url
72
- }));
62
+ const timeoutMs = resolveIndicatorTimeoutMs(this.options.timeoutMs, DEFAULT_HTTP_TIMEOUT_MS, indicatorKey);
63
+ const abortController = new AbortController();
64
+ const startedAt = Date.now();
65
+ const timeout = setTimeout(() => {
66
+ abortController.abort(new Error(`HTTP health check timed out after ${String(timeoutMs)}ms.`));
67
+ }, timeoutMs);
68
+ try {
69
+ const response = await fetch(this.options.url, {
70
+ headers: this.options.headers,
71
+ method,
72
+ signal: abortController.signal
73
+ });
74
+ const responseTimeMs = Date.now() - startedAt;
75
+ try {
76
+ if (!isExpectedStatus(response.status, this.options.expectedStatus)) {
77
+ throwHealthCheckError('HTTP health check failed.', createDownResult(indicatorKey, `Unexpected status code ${String(response.status)} from ${this.options.url}.`, {
78
+ responseTimeMs,
79
+ statusCode: response.status,
80
+ url: this.options.url
81
+ }));
82
+ }
83
+ return createUpResult(indicatorKey, {
84
+ responseTimeMs,
85
+ statusCode: response.status,
86
+ url: this.options.url
87
+ });
88
+ } finally {
89
+ cancelResponseBody(response);
90
+ }
91
+ } finally {
92
+ clearTimeout(timeout);
73
93
  }
74
- return createUpResult(indicatorKey, {
75
- responseTimeMs,
76
- statusCode: response.status,
77
- url: this.options.url
78
- });
79
94
  } catch (error) {
80
95
  if (error instanceof HealthCheckError) {
81
96
  throw error;
82
97
  }
83
98
  throwHealthCheckError('HTTP health check failed.', createDownResult(indicatorKey, error instanceof Error ? error.message : `HTTP health check failed for ${this.options.url}.`));
84
- } finally {
85
- clearTimeout(timeout);
86
99
  }
87
100
  }
88
101
  }
@@ -1,4 +1,5 @@
1
- import type { Provider } from '@fluojs/di';
1
+ import type { Token } from '@fluojs/core';
2
+ import { type Provider } from '@fluojs/di';
2
3
  import type { HealthIndicator, HealthIndicatorResult } from '../types.js';
3
4
  interface PrismaClientLike {
4
5
  $executeRaw?: (...args: unknown[]) => Promise<unknown>;
@@ -6,28 +7,60 @@ interface PrismaClientLike {
6
7
  $queryRaw?: (...args: unknown[]) => Promise<unknown>;
7
8
  $queryRawUnsafe?: (query: string) => Promise<unknown>;
8
9
  }
10
+ interface PrismaLifecycleSnapshotLike {
11
+ details?: Record<string, unknown>;
12
+ health: {
13
+ reason?: string;
14
+ status: 'healthy' | 'degraded' | 'unhealthy';
15
+ };
16
+ readiness: {
17
+ reason?: string;
18
+ status: 'ready' | 'not-ready';
19
+ };
20
+ }
21
+ interface PrismaServiceLike {
22
+ createPlatformStatusSnapshot?: () => PrismaLifecycleSnapshotLike;
23
+ current?: () => PrismaClientLike | unknown;
24
+ }
9
25
  /** Options for probing Prisma-backed database connectivity. */
10
26
  export interface PrismaHealthIndicatorOptions {
27
+ /** Raw Prisma client to probe when no lifecycle-aware service facade is supplied. */
11
28
  client?: PrismaClientLike;
29
+ /** Explicit raw-client token to resolve when using `createPrismaHealthIndicatorProvider(...)`. */
30
+ clientToken?: Token;
31
+ /** Indicator result key override. Defaults to the key passed to `check(...)`, then `prisma`. */
12
32
  key?: string;
33
+ /** Named Prisma registration to resolve when using `createPrismaHealthIndicatorProvider(...)`. */
34
+ name?: string;
35
+ /** Custom ping callback for manual probes or tests. Lifecycle state is only mapped when `service` is available. */
13
36
  ping?: () => Promise<unknown> | unknown;
37
+ /** Lifecycle-aware Prisma service/facade handle, usually resolved from `getPrismaServiceToken(name)`. */
38
+ service?: PrismaServiceLike;
39
+ /** Explicit Prisma service token to resolve when using `createPrismaHealthIndicatorProvider(...)`. */
40
+ serviceToken?: Token;
41
+ /** Maximum time to wait for the ping operation. Defaults to `2_000` ms. */
14
42
  timeoutMs?: number;
15
43
  }
16
44
  /**
17
45
  * Create a Prisma health indicator.
18
46
  *
19
- * @param options Optional Prisma client, ping callback, timeout, and key override.
20
- * @returns A health indicator that executes a lightweight Prisma round trip.
47
+ * @param options Optional lifecycle-aware service facade, Prisma client, ping callback, timeout, and key override.
48
+ * @returns A health indicator that checks Prisma lifecycle state before executing a lightweight round trip.
21
49
  */
22
50
  export declare function createPrismaHealthIndicator(options?: PrismaHealthIndicatorOptions): HealthIndicator;
23
51
  /**
24
- * Create a Terminus indicator provider collection entry that resolves a Prisma client from DI.
52
+ * Create a Terminus indicator provider collection entry that resolves Prisma from DI.
53
+ *
54
+ * The provider prefers `getPrismaServiceToken(options.name)` so `@fluojs/prisma`
55
+ * lifecycle snapshots participate in health/readiness diagnostics. It falls back
56
+ * to the matching raw client token for compatibility with manual provider graphs.
57
+ * Explicit `serviceToken` and `clientToken` values override the name-derived tokens.
25
58
  *
26
- * @param options Optional timeout, key override, or custom ping callback.
59
+ * @param options Optional name hint, explicit tokens, timeout, key override, or custom ping callback.
27
60
  * @returns A factory provider with a unique internal DI token for `TerminusModule` indicatorProviders.
28
61
  */
29
- export declare function createPrismaHealthIndicatorProvider(options?: Omit<PrismaHealthIndicatorOptions, 'client'>): Provider;
30
- /** Health indicator that probes Prisma connectivity with a trivial query. */
62
+ export declare function createPrismaHealthIndicatorProvider(options?: Omit<PrismaHealthIndicatorOptions, 'client' | 'service'>): Provider;
63
+ /** Health indicator that maps Prisma lifecycle status and probes connectivity with a trivial query. */
31
64
  export declare class PrismaHealthIndicator implements HealthIndicator {
32
65
  private readonly options;
33
66
  readonly key: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/indicators/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAI1E,UAAU,gBAAgB;IACxB,WAAW,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,SAAS,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,+DAA+D;AAC/D,MAAM,WAAW,4BAA4B;IAC3C,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAuCD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,4BAAiC,GAAG,eAAe,CAEvG;AAED;;;;;GAKG;AACH,wBAAgB,mCAAmC,CAAC,OAAO,GAAE,IAAI,CAAC,4BAA4B,EAAE,QAAQ,CAAM,GAAG,QAAQ,CAQxH;AAED,6EAA6E;AAC7E,qBAAa,qBAAsB,YAAW,eAAe;IAG/C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEJ,OAAO,GAAE,4BAAiC;IAIjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAczD"}
1
+ {"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/indicators/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAK1E,UAAU,gBAAgB;IACxB,WAAW,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,SAAS,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,UAAU,2BAA2B;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,MAAM,EAAE;QACN,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,CAAC;KAC9C,CAAC;IACF,SAAS,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC;KAC/B,CAAC;CACH;AAED,UAAU,iBAAiB;IACzB,4BAA4B,CAAC,EAAE,MAAM,2BAA2B,CAAC;IACjE,OAAO,CAAC,EAAE,MAAM,gBAAgB,GAAG,OAAO,CAAC;CAC5C;AAED,+DAA+D;AAC/D,MAAM,WAAW,4BAA4B;IAC3C,qFAAqF;IACrF,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,kGAAkG;IAClG,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,gGAAgG;IAChG,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kGAAkG;IAClG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mHAAmH;IACnH,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACxC,yGAAyG;IACzG,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,sGAAsG;IACtG,YAAY,CAAC,EAAE,KAAK,CAAC;IACrB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+HD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,4BAAiC,GAAG,eAAe,CAEvG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mCAAmC,CACjD,OAAO,GAAE,IAAI,CAAC,4BAA4B,EAAE,QAAQ,GAAG,SAAS,CAAM,GACrE,QAAQ,CA8BV;AAED,uGAAuG;AACvG,qBAAa,qBAAsB,YAAW,eAAe;IAG/C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;gBAEJ,OAAO,GAAE,4BAAiC;IAIjE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CA2BzD"}
@@ -1,5 +1,7 @@
1
- import { createDownResult, createUpResult, resolveIndicatorKey, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
1
+ import { optional } from '@fluojs/di';
2
+ import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
2
3
  const PRISMA_CLIENT = Symbol.for('fluo.prisma.client');
4
+ const PRISMA_SERVICE = Symbol.for('fluo.prisma.service');
3
5
 
4
6
  /** Options for probing Prisma-backed database connectivity. */
5
7
 
@@ -9,7 +11,7 @@ async function runPrismaPing(options) {
9
11
  await options.ping();
10
12
  return;
11
13
  }
12
- const client = options.client;
14
+ const client = resolveCurrentPrismaClient(options.service) ?? options.client;
13
15
  if (!client) {
14
16
  throw new Error('Prisma indicator requires either a client or ping callback.');
15
17
  }
@@ -31,36 +33,108 @@ async function runPrismaPing(options) {
31
33
  }
32
34
  throw new Error('Prisma indicator requires a client with query/execute capabilities or a ping callback.');
33
35
  }
36
+ function normalizePrismaRegistrationName(name) {
37
+ if (name === undefined) {
38
+ return undefined;
39
+ }
40
+ const normalizedName = name.trim();
41
+ if (normalizedName.length === 0) {
42
+ throw new Error('Prisma health indicator registration name must be a non-empty string when provided.');
43
+ }
44
+ return normalizedName;
45
+ }
46
+ function getPrismaClientToken(name) {
47
+ const normalizedName = normalizePrismaRegistrationName(name);
48
+ return normalizedName === undefined ? PRISMA_CLIENT : Symbol.for(`fluo.prisma.client:${normalizedName}`);
49
+ }
50
+ function getPrismaServiceToken(name) {
51
+ const normalizedName = normalizePrismaRegistrationName(name);
52
+ return normalizedName === undefined ? PRISMA_SERVICE : Symbol.for(`fluo.prisma.service:${normalizedName}`);
53
+ }
54
+ function resolveCurrentPrismaClient(service) {
55
+ if (!service || typeof service.current !== 'function') {
56
+ return undefined;
57
+ }
58
+ return service.current();
59
+ }
60
+ function createPrismaLifecycleSnapshot(service) {
61
+ if (!service || typeof service.createPlatformStatusSnapshot !== 'function') {
62
+ return undefined;
63
+ }
64
+ return service.createPlatformStatusSnapshot();
65
+ }
66
+ function createPrismaLifecycleDownResult(indicatorKey, snapshot) {
67
+ const healthStatus = snapshot.health.status;
68
+ const readinessStatus = snapshot.readiness.status;
69
+ if (healthStatus === 'healthy' && readinessStatus === 'ready') {
70
+ return undefined;
71
+ }
72
+ const message = snapshot.readiness.reason ?? snapshot.health.reason ?? `Prisma lifecycle reported health=${healthStatus} readiness=${readinessStatus}.`;
73
+ return createDownResult(indicatorKey, message, {
74
+ details: snapshot.details,
75
+ healthStatus,
76
+ readinessStatus
77
+ });
78
+ }
79
+ function createPrismaLifecycleUpDetails(snapshot) {
80
+ if (!snapshot) {
81
+ return {};
82
+ }
83
+ return {
84
+ details: snapshot.details,
85
+ healthStatus: snapshot.health.status,
86
+ readinessStatus: snapshot.readiness.status
87
+ };
88
+ }
89
+ function toPrismaService(value) {
90
+ return typeof value === 'object' && value !== null ? value : undefined;
91
+ }
34
92
 
35
93
  /**
36
94
  * Create a Prisma health indicator.
37
95
  *
38
- * @param options Optional Prisma client, ping callback, timeout, and key override.
39
- * @returns A health indicator that executes a lightweight Prisma round trip.
96
+ * @param options Optional lifecycle-aware service facade, Prisma client, ping callback, timeout, and key override.
97
+ * @returns A health indicator that checks Prisma lifecycle state before executing a lightweight round trip.
40
98
  */
41
99
  export function createPrismaHealthIndicator(options = {}) {
42
100
  return new PrismaHealthIndicator(options);
43
101
  }
44
102
 
45
103
  /**
46
- * Create a Terminus indicator provider collection entry that resolves a Prisma client from DI.
104
+ * Create a Terminus indicator provider collection entry that resolves Prisma from DI.
47
105
  *
48
- * @param options Optional timeout, key override, or custom ping callback.
106
+ * The provider prefers `getPrismaServiceToken(options.name)` so `@fluojs/prisma`
107
+ * lifecycle snapshots participate in health/readiness diagnostics. It falls back
108
+ * to the matching raw client token for compatibility with manual provider graphs.
109
+ * Explicit `serviceToken` and `clientToken` values override the name-derived tokens.
110
+ *
111
+ * @param options Optional name hint, explicit tokens, timeout, key override, or custom ping callback.
49
112
  * @returns A factory provider with a unique internal DI token for `TerminusModule` indicatorProviders.
50
113
  */
51
114
  export function createPrismaHealthIndicatorProvider(options = {}) {
52
115
  const indicatorProviderToken = Symbol('fluo.terminus.prisma-health-indicator');
116
+ const hasExplicitServiceToken = options.serviceToken !== undefined;
117
+ const hasExplicitClientToken = options.clientToken !== undefined;
118
+ const serviceToken = hasExplicitServiceToken || !hasExplicitClientToken ? options.serviceToken ?? getPrismaServiceToken(options.name) : undefined;
119
+ const clientToken = hasExplicitClientToken || !hasExplicitServiceToken ? options.clientToken ?? getPrismaClientToken(options.name) : undefined;
120
+ const inject = [...(serviceToken === undefined ? [] : [optional(serviceToken)]), ...(clientToken === undefined ? [] : [optional(clientToken)])];
53
121
  return {
54
- inject: [PRISMA_CLIENT],
122
+ inject,
55
123
  provide: indicatorProviderToken,
56
- useFactory: client => new PrismaHealthIndicator({
57
- ...options,
58
- client: client
59
- })
124
+ useFactory: (...resolvedDependencies) => {
125
+ const resolvedService = serviceToken === undefined ? undefined : resolvedDependencies[0];
126
+ const resolvedClientIndex = serviceToken === undefined ? 0 : 1;
127
+ const resolvedClient = clientToken === undefined ? undefined : resolvedDependencies[resolvedClientIndex];
128
+ return new PrismaHealthIndicator({
129
+ ...options,
130
+ client: resolvedClient,
131
+ service: toPrismaService(resolvedService)
132
+ });
133
+ }
60
134
  };
61
135
  }
62
136
 
63
- /** Health indicator that probes Prisma connectivity with a trivial query. */
137
+ /** Health indicator that maps Prisma lifecycle status and probes connectivity with a trivial query. */
64
138
  export class PrismaHealthIndicator {
65
139
  key;
66
140
  constructor(options = {}) {
@@ -69,11 +143,19 @@ export class PrismaHealthIndicator {
69
143
  }
70
144
  async check(key) {
71
145
  const indicatorKey = resolveIndicatorKey('prisma', this.options.key ?? key);
72
- const timeoutMs = this.options.timeoutMs ?? DEFAULT_PRISMA_TIMEOUT_MS;
73
146
  try {
147
+ const timeoutMs = resolveIndicatorTimeoutMs(this.options.timeoutMs, DEFAULT_PRISMA_TIMEOUT_MS, indicatorKey);
148
+ const snapshot = createPrismaLifecycleSnapshot(this.options.service);
149
+ const lifecycleDownResult = snapshot ? createPrismaLifecycleDownResult(indicatorKey, snapshot) : undefined;
150
+ if (lifecycleDownResult) {
151
+ throwHealthCheckError('Prisma health check failed.', lifecycleDownResult);
152
+ }
74
153
  await withIndicatorTimeout(runPrismaPing(this.options), timeoutMs, indicatorKey);
75
- return createUpResult(indicatorKey);
154
+ return createUpResult(indicatorKey, createPrismaLifecycleUpDetails(snapshot));
76
155
  } catch (error) {
156
+ if (error instanceof Error && error.name === 'HealthCheckError') {
157
+ throw error;
158
+ }
77
159
  throwHealthCheckError('Prisma health check failed.', createDownResult(indicatorKey, error instanceof Error ? error.message : 'Prisma health check failed.'));
78
160
  }
79
161
  }
@@ -1,5 +1,5 @@
1
1
  import { createRedisPlatformStatusSnapshot, getRedisClientToken, getRedisComponentId } from '@fluojs/redis';
2
- import { createDownResult, createUpResult, resolveIndicatorKey, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
2
+ import { createDownResult, createUpResult, resolveIndicatorKey, resolveIndicatorTimeoutMs, throwHealthCheckError, withIndicatorTimeout } from './utils.js';
3
3
 
4
4
  /**
5
5
  * Options for probing Redis connectivity.
@@ -98,8 +98,8 @@ export class RedisHealthIndicator {
98
98
  }
99
99
  async check(key) {
100
100
  const indicatorKey = resolveIndicatorKey('redis', this.options.key ?? key);
101
- const timeoutMs = this.options.timeoutMs ?? DEFAULT_REDIS_TIMEOUT_MS;
102
101
  try {
102
+ const timeoutMs = resolveIndicatorTimeoutMs(this.options.timeoutMs, DEFAULT_REDIS_TIMEOUT_MS, indicatorKey);
103
103
  const lifecycleDownResult = createRedisLifecycleDownResult(indicatorKey, this.options);
104
104
  if (lifecycleDownResult) {
105
105
  throwHealthCheckError('Redis health check failed.', lifecycleDownResult);
@@ -3,6 +3,16 @@ import type { HealthIndicatorResult } from '../types.js';
3
3
  export interface IndicatorTimeoutOptions {
4
4
  timeoutMs?: number;
5
5
  }
6
+ /**
7
+ * Resolve an indicator timeout budget before it reaches timeout scheduling.
8
+ *
9
+ * @param timeoutMs Caller-provided timeout budget in milliseconds.
10
+ * @param defaultTimeoutMs Default timeout budget used when `timeoutMs` is omitted.
11
+ * @param indicatorName Indicator name used in timeout validation errors.
12
+ * @returns A positive integer timeout budget in milliseconds.
13
+ * @throws {Error} When the resolved timeout is zero, negative, `NaN`, or infinite.
14
+ */
15
+ export declare function resolveIndicatorTimeoutMs(timeoutMs: number | undefined, defaultTimeoutMs: number, indicatorName: string): number;
6
16
  /**
7
17
  * Create an `up` indicator result payload.
8
18
  *
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/indicators/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,qBAAqB,CAOxG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,qBAAqB,CAQvB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,KAAK,CAE3F"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/indicators/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,6EAA6E;AAC7E,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,EAAE,MAAM,EACxB,aAAa,EAAE,MAAM,GACpB,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,qBAAqB,CAOxG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,qBAAqB,CAQvB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,KAAK,CAE3F"}
@@ -2,6 +2,26 @@ import { HealthCheckError } from '../errors.js';
2
2
 
3
3
  /** Timeout settings shared by indicators that call external dependencies. */
4
4
 
5
+ function normalizePositiveFiniteTimeoutMs(timeoutMs, indicatorName) {
6
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
7
+ throw new Error(`${indicatorName} health indicator timeoutMs must be a positive finite number.`);
8
+ }
9
+ return Math.max(1, Math.floor(timeoutMs));
10
+ }
11
+
12
+ /**
13
+ * Resolve an indicator timeout budget before it reaches timeout scheduling.
14
+ *
15
+ * @param timeoutMs Caller-provided timeout budget in milliseconds.
16
+ * @param defaultTimeoutMs Default timeout budget used when `timeoutMs` is omitted.
17
+ * @param indicatorName Indicator name used in timeout validation errors.
18
+ * @returns A positive integer timeout budget in milliseconds.
19
+ * @throws {Error} When the resolved timeout is zero, negative, `NaN`, or infinite.
20
+ */
21
+ export function resolveIndicatorTimeoutMs(timeoutMs, defaultTimeoutMs, indicatorName) {
22
+ return normalizePositiveFiniteTimeoutMs(timeoutMs ?? defaultTimeoutMs, indicatorName);
23
+ }
24
+
5
25
  /**
6
26
  * Create an `up` indicator result payload.
7
27
  *
@@ -45,10 +65,16 @@ export function createDownResult(key, message, details = {}) {
45
65
  * @returns The original promise result when it finishes in time.
46
66
  */
47
67
  export function withIndicatorTimeout(promise, timeoutMs, indicatorName) {
68
+ let normalizedTimeoutMs;
69
+ try {
70
+ normalizedTimeoutMs = normalizePositiveFiniteTimeoutMs(timeoutMs, indicatorName);
71
+ } catch (error) {
72
+ return Promise.reject(error);
73
+ }
48
74
  return new Promise((resolve, reject) => {
49
75
  const timer = setTimeout(() => {
50
- reject(new Error(`${indicatorName} health indicator timed out after ${String(timeoutMs)}ms.`));
51
- }, timeoutMs);
76
+ reject(new Error(`${indicatorName} health indicator timed out after ${String(normalizedTimeoutMs)}ms.`));
77
+ }, normalizedTimeoutMs);
52
78
  promise.then(value => {
53
79
  clearTimeout(timer);
54
80
  resolve(value);
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAA4D,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAyQlH,uFAAuF;AACvF,qBAAa,cAAc;IACzB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,qBAA0B,GAAG,UAAU;CAGhE"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,UAAU,EAKhB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAA4D,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA6RlH,uFAAuF;AACvF,qBAAa,cAAc;IACzB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,qBAA0B,GAAG,UAAU;CAGhE"}
package/dist/module.js CHANGED
@@ -83,7 +83,7 @@ function createPlatformReadinessDiagnostic(readiness) {
83
83
  };
84
84
  }
85
85
  function createPlatformDiagnosticCollisionKey(diagnosticKey, seenKeys) {
86
- const baseKey = `${diagnosticKey}-duplicate-key-error`;
86
+ const baseKey = `${diagnosticKey}-user-key-collision`;
87
87
  if (!seenKeys.has(baseKey)) {
88
88
  return baseKey;
89
89
  }
@@ -95,42 +95,51 @@ function createPlatformDiagnosticCollisionKey(diagnosticKey, seenKeys) {
95
95
  }
96
96
  return candidate;
97
97
  }
98
- function appendPlatformDiagnostic(entries, existingKeys, diagnosticKey, diagnostic) {
98
+ function appendPlatformDiagnostic(entries, collisionEntries, diagnosticKey, diagnostic) {
99
99
  if (diagnostic === undefined) {
100
100
  return;
101
101
  }
102
- if (!existingKeys.has(diagnosticKey) && !(diagnosticKey in entries)) {
103
- entries[diagnosticKey] = diagnostic;
104
- return;
102
+ if (diagnosticKey in collisionEntries) {
103
+ const collisionKey = createPlatformDiagnosticCollisionKey(diagnosticKey, new Set([...Object.keys(entries), ...Object.keys(collisionEntries)]));
104
+ entries[collisionKey] = {
105
+ message: `User health result key "${diagnosticKey}" is reserved for Terminus platform diagnostics; the platform diagnostic remains available under the reserved key.`,
106
+ status: 'down'
107
+ };
105
108
  }
106
- const collisionKey = createPlatformDiagnosticCollisionKey(diagnosticKey, new Set([...existingKeys, ...Object.keys(entries)]));
107
- entries[collisionKey] = {
108
- message: `Platform diagnostic key "${diagnosticKey}" collided with an existing health result key.`,
109
- status: 'down'
110
- };
109
+ entries[diagnosticKey] = diagnostic;
110
+ }
111
+ function withoutKeys(entries, keys) {
112
+ return Object.fromEntries(Object.entries(entries).filter(([key]) => !keys.has(key)));
113
+ }
114
+ function remapContributors(contributors, reservedKeys, platformDiagnosticKeys) {
115
+ return contributors.filter(key => !reservedKeys.has(key) && !platformDiagnosticKeys.includes(key));
111
116
  }
112
117
  function withPlatformDiagnostics(report, health, readiness) {
113
118
  const platformDiagnostics = {};
114
119
  const healthDiagnostic = createPlatformHealthDiagnostic(health);
115
120
  const readinessDiagnostic = createPlatformReadinessDiagnostic(readiness);
116
- const existingKeys = new Set(Object.keys(report.details));
117
- appendPlatformDiagnostic(platformDiagnostics, existingKeys, 'fluo-platform-health', healthDiagnostic);
118
- appendPlatformDiagnostic(platformDiagnostics, existingKeys, 'fluo-platform-readiness', readinessDiagnostic);
121
+ appendPlatformDiagnostic(platformDiagnostics, report.details, 'fluo-platform-health', healthDiagnostic);
122
+ appendPlatformDiagnostic(platformDiagnostics, report.details, 'fluo-platform-readiness', readinessDiagnostic);
119
123
  const platformDiagnosticKeys = Object.keys(platformDiagnostics);
124
+ const platformReservedKeys = new Set([...(healthDiagnostic ? ['fluo-platform-health'] : []), ...(readinessDiagnostic ? ['fluo-platform-readiness'] : [])]);
125
+ const reportDetails = withoutKeys(report.details, platformReservedKeys);
126
+ const reportInfo = withoutKeys(report.info, platformReservedKeys);
127
+ const reportError = withoutKeys(report.error, platformReservedKeys);
120
128
  return {
121
129
  ...report,
122
130
  contributors: {
123
- down: [...report.contributors.down, ...platformDiagnosticKeys],
124
- up: [...report.contributors.up]
131
+ down: [...remapContributors(report.contributors.down, platformReservedKeys, platformDiagnosticKeys), ...platformDiagnosticKeys],
132
+ up: remapContributors(report.contributors.up, platformReservedKeys, platformDiagnosticKeys)
125
133
  },
126
134
  details: {
127
- ...report.details,
135
+ ...reportDetails,
128
136
  ...platformDiagnostics
129
137
  },
130
138
  error: {
131
- ...report.error,
139
+ ...reportError,
132
140
  ...platformDiagnostics
133
141
  },
142
+ info: reportInfo,
134
143
  platform: {
135
144
  health,
136
145
  readiness
@@ -159,7 +168,7 @@ function createTerminusRuntimeModule(options = {}) {
159
168
  const TERMINUS_READINESS_REGISTRAR = Symbol('fluo.terminus.readiness-registrar');
160
169
  class TerminusRuntimeModule {}
161
170
  return defineModule(TerminusRuntimeModule, {
162
- exports: [TERMINUS_HEALTH_INDICATORS, TerminusHealthService],
171
+ exports: [TERMINUS_HEALTH_INDICATORS, TERMINUS_INDICATOR_PROVIDER_TOKENS, TerminusHealthService],
163
172
  imports: [healthModule],
164
173
  providers: [...createTerminusProviders({
165
174
  execution: options.execution,
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "liveness",
10
10
  "health-check"
11
11
  ],
12
- "version": "1.0.4",
12
+ "version": "1.1.0",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -44,15 +44,15 @@
44
44
  "dist"
45
45
  ],
46
46
  "dependencies": {
47
- "@fluojs/core": "^1.0.3",
48
- "@fluojs/di": "^1.1.0",
49
- "@fluojs/http": "^1.1.0",
50
- "@fluojs/runtime": "^1.1.6"
47
+ "@fluojs/core": "^1.1.0",
48
+ "@fluojs/di": "^2.0.0",
49
+ "@fluojs/http": "^2.0.1",
50
+ "@fluojs/runtime": "^2.0.1"
51
51
  },
52
52
  "peerDependencies": {
53
- "@fluojs/drizzle": "^1.0.2",
54
- "@fluojs/prisma": "^1.0.2",
55
- "@fluojs/redis": "^1.0.1"
53
+ "@fluojs/drizzle": "^1.1.1",
54
+ "@fluojs/prisma": "^1.1.1",
55
+ "@fluojs/redis": "^1.1.0"
56
56
  },
57
57
  "peerDependenciesMeta": {
58
58
  "@fluojs/drizzle": {
@@ -67,7 +67,7 @@
67
67
  },
68
68
  "devDependencies": {
69
69
  "vitest": "^3.2.4",
70
- "@fluojs/testing": "^1.0.5"
70
+ "@fluojs/testing": "^2.0.0"
71
71
  },
72
72
  "scripts": {
73
73
  "prebuild": "node ../../tooling/scripts/clean-dist.mjs",