@fluojs/terminus 1.0.0-beta.1 → 1.0.0-beta.3

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
@@ -111,7 +111,9 @@ TerminusModule.forRoot({
111
111
  - 준비 상태(readiness)와 관련된 인디케이터가 실패하면 `/ready`는 HTTP `503`을 반환합니다.
112
112
  - 응답 본문은 `status`, `contributors`, `info`, `error`, `details`를 포함한 구조화된 JSON 객체입니다.
113
113
  - 하나의 인디케이터가 여러 keyed entry를 반환할 수도 있으며, 이 경우 `/health`는 모든 entry를 `details`와 `contributors.up` / `contributors.down` 요약에 그대로 반영합니다.
114
+ - 지원하지 않는 status, 빈 결과, 객체가 아닌 인디케이터 결과는 조용히 버려지지 않고 `down` 진단으로 보고됩니다.
114
115
  - 같은 실행에서 이미 보고된 key를 다른 인디케이터가 다시 사용하면, Terminus는 먼저 기록된 entry를 유지하고 데이터를 조용히 덮어쓰는 대신 결정적인 `*-duplicate-key-error` contributor를 추가합니다.
116
+ - 플랫폼 health/readiness 실패는 `/health` 응답에서 결정적인 `fluo-platform-health`, `fluo-platform-readiness` contributor로 노출됩니다.
115
117
 
116
118
  ## 공개 API 개요
117
119
 
package/README.md CHANGED
@@ -111,7 +111,9 @@ When an indicator fails, it throws a `HealthCheckError`. The `TerminusHealthServ
111
111
  - `/ready` returns HTTP `503` if any indicator associated with readiness fails.
112
112
  - The response body contains a structured JSON object with `status`, `contributors`, `info`, `error`, and `details`.
113
113
  - Indicators may emit multiple keyed entries in a single check result; `/health` preserves every keyed entry in `details` and in the `contributors.up` / `contributors.down` summaries.
114
+ - Unsupported, empty, or non-object indicator results are reported as `down` diagnostics instead of being silently discarded.
114
115
  - 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.
116
+ - Platform health/readiness failures are surfaced as deterministic `fluo-platform-health` and `fluo-platform-readiness` contributors in `/health` responses.
115
117
 
116
118
  ## Public API Overview
117
119
 
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import type { HealthIndicatorResult } from './types.js';
2
+ /**
3
+ * Represents the health check error.
4
+ */
2
5
  export declare class HealthCheckError extends Error {
3
6
  readonly causes: HealthIndicatorResult;
4
7
  constructor(message: string, causes: HealthIndicatorResult);
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;gBAE3B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB;CAK3D"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;gBAE3B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB;CAK3D"}
package/dist/errors.js CHANGED
@@ -1,3 +1,6 @@
1
+ /**
2
+ * Represents the health check error.
3
+ */
1
4
  export class HealthCheckError extends Error {
2
5
  causes;
3
6
  constructor(message, causes) {
@@ -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;AA0LpB;;;;;;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;AAsOpB;;;;;;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"}
@@ -37,17 +37,47 @@ function hasIndicatorStatus(value) {
37
37
  const status = value.status;
38
38
  return status === 'up' || status === 'down';
39
39
  }
40
+ function createUnsupportedEntryMessage(entryKey) {
41
+ return entryKey.trim().length > 0 ? `Indicator returned an unsupported status value for result key "${entryKey}".` : 'Indicator returned an unsupported status value for an empty result key.';
42
+ }
40
43
  function normalizeIndicatorResult(key, result) {
41
- const normalizedEntries = Object.entries(result).filter(([, entryValue]) => hasIndicatorStatus(entryValue));
42
- if (normalizedEntries.length > 0) {
43
- return Object.fromEntries(normalizedEntries);
44
+ if (typeof result !== 'object' || result === null || Array.isArray(result)) {
45
+ return [[key, {
46
+ message: 'Indicator returned a non-object health result.',
47
+ status: 'down'
48
+ }]];
44
49
  }
45
- return {
46
- [key]: {
47
- message: 'Indicator returned an unsupported status value.',
50
+ const normalizedEntries = [];
51
+ const seenKeys = new Set();
52
+ const duplicateKeys = [];
53
+ const entries = Object.entries(result);
54
+ if (entries.length === 0) {
55
+ return [[key, {
56
+ message: 'Indicator returned no health result entries.',
48
57
  status: 'down'
58
+ }]];
59
+ }
60
+ for (const [entryKey, entryValue] of entries) {
61
+ const normalizedKey = hasIndicatorStatus(entryValue) || entryKey.trim().length > 0 ? entryKey : key;
62
+ if (seenKeys.has(normalizedKey)) {
63
+ duplicateKeys.push(normalizedKey);
64
+ continue;
49
65
  }
50
- };
66
+ seenKeys.add(normalizedKey);
67
+ if (hasIndicatorStatus(entryValue)) {
68
+ normalizedEntries.push([normalizedKey, entryValue]);
69
+ continue;
70
+ }
71
+ normalizedEntries.push([normalizedKey, {
72
+ message: createUnsupportedEntryMessage(entryKey),
73
+ status: 'down'
74
+ }]);
75
+ }
76
+ if (duplicateKeys.length > 0) {
77
+ const duplicateFailure = createDuplicateKeyFailure(key, duplicateKeys, seenKeys);
78
+ normalizedEntries.push(duplicateFailure);
79
+ }
80
+ return normalizedEntries;
51
81
  }
52
82
  function inferIndicatorKey(indicator, index) {
53
83
  const candidateKey = indicator.key;
@@ -85,13 +115,13 @@ async function runIndicator(indicator, index, executionOptions) {
85
115
  try {
86
116
  const result = indicatorTimeoutMs === undefined ? await indicator.check(key) : await withTimeout(indicator.check(key), indicatorTimeoutMs);
87
117
  return {
88
- entries: Object.entries(normalizeIndicatorResult(key, result)),
118
+ entries: normalizeIndicatorResult(key, result),
89
119
  indicatorKey: key
90
120
  };
91
121
  } catch (error) {
92
122
  if (error instanceof HealthCheckError) {
93
123
  return {
94
- entries: Object.entries(normalizeIndicatorResult(key, error.causes)),
124
+ entries: normalizeIndicatorResult(key, error.causes),
95
125
  indicatorKey: key
96
126
  };
97
127
  }
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,UAAU,EAGhB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAgLzE,uFAAuF;AACvF,qBAAa,cAAc;IACzB;;;;;;;;;;;;OAYG;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":"AAEA,OAAO,EAGL,KAAK,UAAU,EAGhB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,KAAK,EAA4D,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAuRlH,uFAAuF;AACvF,qBAAa,cAAc;IACzB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,qBAA0B,GAAG,UAAU;CAGhE"}
package/dist/module.js CHANGED
@@ -62,6 +62,85 @@ function createTerminusProviders(options = {}) {
62
62
  }
63
63
  }];
64
64
  }
65
+ function createPlatformHealthDiagnostic(health) {
66
+ if (health.status === 'healthy') {
67
+ return undefined;
68
+ }
69
+ return {
70
+ checks: health.checks,
71
+ message: health.reason ?? `Platform health reported ${health.status}.`,
72
+ platformStatus: health.status,
73
+ status: 'down'
74
+ };
75
+ }
76
+ function createPlatformReadinessDiagnostic(readiness) {
77
+ if (readiness.status === 'ready') {
78
+ return undefined;
79
+ }
80
+ return {
81
+ checks: readiness.checks,
82
+ critical: readiness.critical,
83
+ message: readiness.reason ?? `Platform readiness reported ${readiness.status}.`,
84
+ platformStatus: readiness.status,
85
+ status: 'down'
86
+ };
87
+ }
88
+ function createPlatformDiagnosticCollisionKey(diagnosticKey, seenKeys) {
89
+ const baseKey = `${diagnosticKey}-duplicate-key-error`;
90
+ if (!seenKeys.has(baseKey)) {
91
+ return baseKey;
92
+ }
93
+ let suffix = 2;
94
+ let candidate = `${baseKey}-${String(suffix)}`;
95
+ while (seenKeys.has(candidate)) {
96
+ suffix += 1;
97
+ candidate = `${baseKey}-${String(suffix)}`;
98
+ }
99
+ return candidate;
100
+ }
101
+ function appendPlatformDiagnostic(entries, existingKeys, diagnosticKey, diagnostic) {
102
+ if (diagnostic === undefined) {
103
+ return;
104
+ }
105
+ if (!existingKeys.has(diagnosticKey) && !(diagnosticKey in entries)) {
106
+ entries[diagnosticKey] = diagnostic;
107
+ return;
108
+ }
109
+ const collisionKey = createPlatformDiagnosticCollisionKey(diagnosticKey, new Set([...existingKeys, ...Object.keys(entries)]));
110
+ entries[collisionKey] = {
111
+ message: `Platform diagnostic key "${diagnosticKey}" collided with an existing health result key.`,
112
+ status: 'down'
113
+ };
114
+ }
115
+ function withPlatformDiagnostics(report, health, readiness) {
116
+ const platformDiagnostics = {};
117
+ const healthDiagnostic = createPlatformHealthDiagnostic(health);
118
+ const readinessDiagnostic = createPlatformReadinessDiagnostic(readiness);
119
+ const existingKeys = new Set(Object.keys(report.details));
120
+ appendPlatformDiagnostic(platformDiagnostics, existingKeys, 'fluo-platform-health', healthDiagnostic);
121
+ appendPlatformDiagnostic(platformDiagnostics, existingKeys, 'fluo-platform-readiness', readinessDiagnostic);
122
+ const platformDiagnosticKeys = Object.keys(platformDiagnostics);
123
+ return {
124
+ ...report,
125
+ contributors: {
126
+ down: [...report.contributors.down, ...platformDiagnosticKeys],
127
+ up: [...report.contributors.up]
128
+ },
129
+ details: {
130
+ ...report.details,
131
+ ...platformDiagnostics
132
+ },
133
+ error: {
134
+ ...report.error,
135
+ ...platformDiagnostics
136
+ },
137
+ platform: {
138
+ health,
139
+ readiness
140
+ },
141
+ status: report.status === 'ok' && platformDiagnosticKeys.length === 0 ? 'ok' : 'error'
142
+ };
143
+ }
65
144
  function createTerminusRuntimeModule(options = {}) {
66
145
  const readinessChecks = [...(options.readinessChecks ?? [])];
67
146
  const healthModule = createHealthModule({
@@ -69,17 +148,10 @@ function createTerminusRuntimeModule(options = {}) {
69
148
  const healthService = await ctx.container.resolve(TerminusHealthService);
70
149
  const platformShell = await ctx.container.resolve(PLATFORM_SHELL);
71
150
  const [report, readiness, health] = await Promise.all([healthService.check(), platformShell.ready(), platformShell.health()]);
72
- const status = report.status === 'ok' && health.status === 'healthy' && readiness.status === 'ready' ? 'ok' : 'error';
151
+ const reportWithPlatform = withPlatformDiagnostics(report, health, readiness);
73
152
  return {
74
- body: {
75
- ...report,
76
- platform: {
77
- health,
78
- readiness
79
- },
80
- status
81
- },
82
- statusCode: status === 'ok' ? 200 : 503
153
+ body: reportWithPlatform,
154
+ statusCode: reportWithPlatform.status === 'ok' ? 200 : 503
83
155
  };
84
156
  },
85
157
  path: options.path
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "liveness",
10
10
  "health-check"
11
11
  ],
12
- "version": "1.0.0-beta.1",
12
+ "version": "1.0.0-beta.3",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -40,15 +40,15 @@
40
40
  "dist"
41
41
  ],
42
42
  "dependencies": {
43
- "@fluojs/core": "^1.0.0-beta.1",
44
- "@fluojs/runtime": "^1.0.0-beta.1",
45
- "@fluojs/di": "^1.0.0-beta.1",
46
- "@fluojs/http": "^1.0.0-beta.1"
43
+ "@fluojs/di": "^1.0.0-beta.4",
44
+ "@fluojs/http": "^1.0.0-beta.3",
45
+ "@fluojs/core": "^1.0.0-beta.2",
46
+ "@fluojs/runtime": "^1.0.0-beta.4"
47
47
  },
48
48
  "peerDependencies": {
49
- "@fluojs/redis": "^1.0.0-beta.1",
50
- "@fluojs/drizzle": "^1.0.0-beta.1",
51
- "@fluojs/prisma": "^1.0.0-beta.1"
49
+ "@fluojs/drizzle": "^1.0.0-beta.2",
50
+ "@fluojs/prisma": "^1.0.0-beta.2",
51
+ "@fluojs/redis": "^1.0.0-beta.2"
52
52
  },
53
53
  "peerDependenciesMeta": {
54
54
  "@fluojs/drizzle": {