@open-operational-state/emitter 0.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.md ADDED
@@ -0,0 +1,72 @@
1
+ # @open-operational-state/emitter
2
+
3
+ Serialization emitters for [Open Operational State](https://github.com/open-operational-state). Convert core model `Snapshot` objects to spec-conformant wire formats.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @open-operational-state/emitter
9
+ ```
10
+
11
+ ## API
12
+
13
+ ### `emitHealthResponse( snapshot )`
14
+
15
+ Convert a `Snapshot` to an `application/health+json` payload.
16
+
17
+ ```js
18
+ import { emitHealthResponse } from '@open-operational-state/emitter';
19
+
20
+ const payload = emitHealthResponse( snapshot );
21
+ // Serve as: Content-Type: application/health+json
22
+ ```
23
+
24
+ ### `emitServiceStatus( snapshot )`
25
+
26
+ Convert a `Snapshot` to an `application/status+json` payload.
27
+
28
+ ```js
29
+ import { emitServiceStatus } from '@open-operational-state/emitter';
30
+
31
+ const payload = emitServiceStatus( snapshot );
32
+ // Serve as: Content-Type: application/status+json
33
+ ```
34
+
35
+ ### `suggestHttpStatus( condition )`
36
+
37
+ Get the recommended HTTP status code for a condition value.
38
+
39
+ ```js
40
+ import { suggestHttpStatus } from '@open-operational-state/emitter';
41
+
42
+ suggestHttpStatus( 'operational' ); // 200
43
+ suggestHttpStatus( 'degraded' ); // 200
44
+ suggestHttpStatus( 'down' ); // 503
45
+ suggestHttpStatus( 'maintenance' ); // 503
46
+ ```
47
+
48
+ ### `suggestHeaders( snapshot, serialization )`
49
+
50
+ Get recommended HTTP headers for a response.
51
+
52
+ ```js
53
+ import { suggestHeaders } from '@open-operational-state/emitter';
54
+
55
+ const headers = suggestHeaders( snapshot, 'application/health+json' );
56
+ // { 'Content-Type': 'application/health+json', 'Cache-Control': 'no-cache, ...' }
57
+ ```
58
+
59
+ ### Round-Trip Guarantee
60
+
61
+ The emitter maintains the round-trip invariant:
62
+
63
+ ```
64
+ parse → normalize → emit → parse → normalize
65
+ ```
66
+
67
+ No semantic drift, no loss of required data, no illegal state introduced. This is enforced by golden round-trip tests.
68
+
69
+ ## Dependencies
70
+
71
+ - `@open-operational-state/types`
72
+ - `@open-operational-state/core`
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Emitter Tests
3
+ *
4
+ * Round-trip tests: parse → emit → parse, verify structural identity.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=emitter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitter.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/emitter.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Emitter Tests
3
+ *
4
+ * Round-trip tests: parse → emit → parse, verify structural identity.
5
+ */
6
+ import { describe, it, expect } from 'bun:test';
7
+ import { resolve, join } from 'node:path';
8
+ import { readFileSync, existsSync } from 'node:fs';
9
+ import { normalizeSnapshot } from '@open-operational-state/core';
10
+ import { emitHealthResponse } from '../health-response.js';
11
+ import { emitServiceStatus } from '../service-status.js';
12
+ import { suggestHttpStatus, suggestHeaders } from '../http.js';
13
+ const FIXTURE_ROOT = process.env.OOS_FIXTURE_ROOT
14
+ || resolve(import.meta.dir, '../../../../..', 'status-conformance');
15
+ function loadFixture(relPath) {
16
+ const fullPath = join(FIXTURE_ROOT, relPath);
17
+ if (!existsSync(fullPath)) {
18
+ throw new Error(`Fixture not found: ${fullPath}`);
19
+ }
20
+ return JSON.parse(readFileSync(fullPath, 'utf-8'));
21
+ }
22
+ // ---------------------------------------------------------------------------
23
+ // Health response round-trip
24
+ // ---------------------------------------------------------------------------
25
+ describe('emitHealthResponse', () => {
26
+ it('emits a valid health response from a snapshot', () => {
27
+ const snapshot = {
28
+ condition: 'operational',
29
+ profiles: ['health'],
30
+ subject: { id: 'test-service', description: 'Test Service' },
31
+ timing: { observed: '2026-04-02T19:30:00Z' },
32
+ provenance: 'self-reported',
33
+ };
34
+ const payload = emitHealthResponse(snapshot);
35
+ expect(payload.condition).toBe('operational');
36
+ expect(payload.profiles).toEqual(['health']);
37
+ expect(payload.subject.id).toBe('test-service');
38
+ expect(payload.timing?.observed).toBe('2026-04-02T19:30:00Z');
39
+ expect(payload.provenance).toBe('self-reported');
40
+ });
41
+ it('round-trips a health fixture: emit → normalize', () => {
42
+ const fixture = loadFixture('fixtures/profiles/health/positive-degraded-with-components.json');
43
+ const input = fixture.input;
44
+ const snapshot = normalizeSnapshot(input);
45
+ // Emit
46
+ const payload = emitHealthResponse(snapshot);
47
+ // Re-normalize
48
+ const roundTripped = normalizeSnapshot(payload);
49
+ expect(roundTripped.condition).toBe(snapshot.condition);
50
+ expect(roundTripped.subject.id).toBe(snapshot.subject.id);
51
+ expect(roundTripped.provenance).toBe(snapshot.provenance);
52
+ });
53
+ it('includes checks from snapshot', () => {
54
+ const snapshot = {
55
+ condition: 'degraded',
56
+ profiles: ['health'],
57
+ subject: { id: 'test' },
58
+ checks: {
59
+ database: { condition: 'operational', role: 'dependency' },
60
+ cache: { condition: 'down', role: 'component' },
61
+ },
62
+ };
63
+ const payload = emitHealthResponse(snapshot);
64
+ expect(payload.checks).toBeDefined();
65
+ expect(Object.keys(payload.checks)).toEqual(['database', 'cache']);
66
+ });
67
+ });
68
+ // ---------------------------------------------------------------------------
69
+ // Service status round-trip
70
+ // ---------------------------------------------------------------------------
71
+ describe('emitServiceStatus', () => {
72
+ it('emits a valid service-status payload', () => {
73
+ const snapshot = {
74
+ condition: 'degraded',
75
+ profiles: ['status'],
76
+ subject: { id: 'payment-platform', description: 'Payment Platform' },
77
+ timing: { observed: '2026-04-02T19:30:00Z' },
78
+ provenance: 'self-reported',
79
+ evidence: { type: 'error-rate', detail: '7.2% errors' },
80
+ components: [
81
+ { id: 'api', condition: 'operational' },
82
+ { id: 'cache', condition: 'down' },
83
+ ],
84
+ dependencies: [
85
+ { id: 'database', condition: 'operational' },
86
+ ],
87
+ };
88
+ const payload = emitServiceStatus(snapshot);
89
+ expect(payload.condition).toBe('degraded');
90
+ expect(payload.components?.length).toBe(2);
91
+ expect(payload.dependencies?.length).toBe(1);
92
+ expect(payload.evidence?.type).toBe('error-rate');
93
+ });
94
+ it('converts flat checks to components/dependencies', () => {
95
+ const snapshot = {
96
+ condition: 'operational',
97
+ profiles: ['status'],
98
+ subject: { id: 'test' },
99
+ checks: {
100
+ db: { condition: 'operational', role: 'dependency' },
101
+ worker: { condition: 'operational', role: 'component' },
102
+ },
103
+ };
104
+ const payload = emitServiceStatus(snapshot);
105
+ expect(payload.components?.length).toBe(1);
106
+ expect(payload.dependencies?.length).toBe(1);
107
+ expect(payload.components[0].id).toBe('worker');
108
+ expect(payload.dependencies[0].id).toBe('db');
109
+ });
110
+ });
111
+ // ---------------------------------------------------------------------------
112
+ // HTTP helpers
113
+ // ---------------------------------------------------------------------------
114
+ describe('suggestHttpStatus', () => {
115
+ it('returns 200 for operational', () => {
116
+ expect(suggestHttpStatus('operational')).toBe(200);
117
+ });
118
+ it('returns 200 for degraded', () => {
119
+ expect(suggestHttpStatus('degraded')).toBe(200);
120
+ });
121
+ it('returns 503 for down', () => {
122
+ expect(suggestHttpStatus('down')).toBe(503);
123
+ });
124
+ it('returns 503 for maintenance', () => {
125
+ expect(suggestHttpStatus('maintenance')).toBe(503);
126
+ });
127
+ it('returns 200 for alive', () => {
128
+ expect(suggestHttpStatus('alive')).toBe(200);
129
+ });
130
+ });
131
+ describe('suggestHeaders', () => {
132
+ it('returns correct content type', () => {
133
+ const snapshot = {
134
+ condition: 'operational',
135
+ profiles: ['health'],
136
+ subject: { id: 'test' },
137
+ };
138
+ const headers = suggestHeaders(snapshot, 'application/health+json');
139
+ expect(headers['Content-Type']).toBe('application/health+json');
140
+ expect(headers['Cache-Control']).toContain('no-cache');
141
+ });
142
+ });
143
+ //# sourceMappingURL=emitter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitter.test.js","sourceRoot":"","sources":["../../src/__tests__/emitter.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Golden Round-Trip Invariant Tests
3
+ *
4
+ * Enforces the fundamental guarantee:
5
+ * parse → normalize → emit → parse → normalize
6
+ *
7
+ * Must guarantee:
8
+ * - No semantic drift
9
+ * - No loss of required data
10
+ * - No illegal state introduced
11
+ *
12
+ * This is the long-term safety net. If these tests break,
13
+ * something fundamental has drifted.
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=round-trip.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"round-trip.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/round-trip.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Golden Round-Trip Invariant Tests
3
+ *
4
+ * Enforces the fundamental guarantee:
5
+ * parse → normalize → emit → parse → normalize
6
+ *
7
+ * Must guarantee:
8
+ * - No semantic drift
9
+ * - No loss of required data
10
+ * - No illegal state introduced
11
+ *
12
+ * This is the long-term safety net. If these tests break,
13
+ * something fundamental has drifted.
14
+ */
15
+ import { describe, it, expect } from 'bun:test';
16
+ import { resolve, join } from 'node:path';
17
+ import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
18
+ import { normalizeSnapshot, validateSnapshot } from '@open-operational-state/core';
19
+ import { parseHealthCheckDraft } from '@open-operational-state/parser';
20
+ import { emitHealthResponse, emitServiceStatus } from '@open-operational-state/emitter';
21
+ const FIXTURE_ROOT = process.env.OOS_FIXTURE_ROOT
22
+ || resolve(import.meta.dir, '../../../../..', 'status-conformance');
23
+ // ---------------------------------------------------------------------------
24
+ // Helpers
25
+ // ---------------------------------------------------------------------------
26
+ function findFixtures(dirPath) {
27
+ if (!existsSync(dirPath)) {
28
+ return [];
29
+ }
30
+ const files = [];
31
+ for (const entry of readdirSync(dirPath)) {
32
+ const fullPath = join(dirPath, entry);
33
+ const stat = statSync(fullPath);
34
+ if (stat.isDirectory()) {
35
+ files.push(...findFixtures(fullPath));
36
+ }
37
+ else if (entry.endsWith('.json')) {
38
+ files.push(fullPath);
39
+ }
40
+ }
41
+ return files;
42
+ }
43
+ function loadJson(path) {
44
+ return JSON.parse(readFileSync(path, 'utf-8'));
45
+ }
46
+ /**
47
+ * Assert that two snapshots are semantically equivalent
48
+ * for the purposes of round-trip invariance.
49
+ *
50
+ * Checks required fields and structural shape.
51
+ * Timing is excluded from round-trip checks because emit
52
+ * may add/strip timing fields not present in the original.
53
+ */
54
+ function assertSemanticEquivalence(original, roundTripped, label) {
55
+ // Condition must be preserved
56
+ expect(roundTripped.condition).toBe(original.condition);
57
+ // Subject identity must be preserved
58
+ expect(roundTripped.subject.id).toBe(original.subject.id);
59
+ // Subject description must be preserved if present
60
+ if (original.subject.description) {
61
+ expect(roundTripped.subject.description).toBe(original.subject.description);
62
+ }
63
+ // Profiles must be preserved
64
+ expect(roundTripped.profiles).toEqual(original.profiles);
65
+ // Provenance must be preserved if present
66
+ if (original.provenance) {
67
+ expect(roundTripped.provenance).toBe(original.provenance);
68
+ }
69
+ // Evidence type must be preserved if present
70
+ if (original.evidence) {
71
+ expect(roundTripped.evidence?.type).toBe(original.evidence.type);
72
+ }
73
+ // Check count must be preserved
74
+ if (original.checks) {
75
+ expect(Object.keys(roundTripped.checks || {}).length)
76
+ .toBe(Object.keys(original.checks).length);
77
+ // Each check's condition must be preserved
78
+ for (const [key, check] of Object.entries(original.checks)) {
79
+ expect(roundTripped.checks?.[key]?.condition).toBe(check.condition);
80
+ }
81
+ }
82
+ // The round-tripped snapshot must still be valid
83
+ const validation = validateSnapshot(roundTripped);
84
+ // If original was valid, round-trip must be valid
85
+ const originalValidation = validateSnapshot(original);
86
+ if (originalValidation.valid) {
87
+ expect(validation.valid).toBe(true);
88
+ }
89
+ }
90
+ // ---------------------------------------------------------------------------
91
+ // Round-trip: health-response (parse → emit → parse)
92
+ // ---------------------------------------------------------------------------
93
+ describe('round-trip: health-response', () => {
94
+ // Core positive fixtures through health-response path
95
+ const corePositive = findFixtures(join(FIXTURE_ROOT, 'fixtures', 'core'))
96
+ .filter((f) => f.includes('positive'));
97
+ const profilePositive = findFixtures(join(FIXTURE_ROOT, 'fixtures', 'profiles'))
98
+ .filter((f) => f.includes('positive'));
99
+ const allPositive = [...corePositive, ...profilePositive];
100
+ for (const fixturePath of allPositive) {
101
+ const name = fixturePath.split('/').slice(-2).join('/');
102
+ it(`round-trips: ${name}`, () => {
103
+ const fixture = loadJson(fixturePath);
104
+ const input = fixture.input;
105
+ // Step 1: normalize input → Snapshot
106
+ const original = normalizeSnapshot(input);
107
+ // Step 2: emit health-response
108
+ const emitted = emitHealthResponse(original);
109
+ // Step 3: re-parse the emitted payload
110
+ const roundTripped = normalizeSnapshot(emitted);
111
+ // Step 4: verify semantic equivalence
112
+ assertSemanticEquivalence(original, roundTripped, name);
113
+ });
114
+ }
115
+ });
116
+ // ---------------------------------------------------------------------------
117
+ // Round-trip: service-status (parse → emit → parse)
118
+ // ---------------------------------------------------------------------------
119
+ describe('round-trip: service-status', () => {
120
+ it('round-trips a rich snapshot through service-status format', () => {
121
+ const original = {
122
+ condition: 'degraded',
123
+ profiles: ['status'],
124
+ subject: { id: 'payment-platform', description: 'Payment Platform' },
125
+ timing: { observed: '2026-04-02T19:30:00Z' },
126
+ provenance: 'self-reported',
127
+ evidence: { type: 'error-rate', detail: '7.2% error rate' },
128
+ components: [
129
+ { id: 'api', condition: 'operational' },
130
+ { id: 'cache', condition: 'down', evidence: { type: 'connectivity', detail: 'Connection refused' } },
131
+ ],
132
+ dependencies: [
133
+ { id: 'database', condition: 'operational' },
134
+ ],
135
+ };
136
+ // Emit
137
+ const emitted = emitServiceStatus(original);
138
+ // Re-parse
139
+ const roundTripped = normalizeSnapshot(emitted);
140
+ // Verify
141
+ expect(roundTripped.condition).toBe('degraded');
142
+ expect(roundTripped.subject.id).toBe('payment-platform');
143
+ expect(roundTripped.provenance).toBe('self-reported');
144
+ expect(roundTripped.profiles).toEqual(['status']);
145
+ });
146
+ it('converts flat checks → components → flat checks without loss', () => {
147
+ const original = {
148
+ condition: 'operational',
149
+ profiles: ['health'],
150
+ subject: { id: 'test' },
151
+ checks: {
152
+ db: { condition: 'operational', role: 'dependency' },
153
+ worker: { condition: 'operational', role: 'component' },
154
+ },
155
+ };
156
+ // Emit as service-status (converts checks → components/dependencies)
157
+ const emitted = emitServiceStatus(original);
158
+ expect(emitted.components?.length).toBe(1);
159
+ expect(emitted.dependencies?.length).toBe(1);
160
+ // Emit as health-response (keeps checks flat)
161
+ const healthEmitted = emitHealthResponse(original);
162
+ const healthRoundTripped = normalizeSnapshot(healthEmitted);
163
+ expect(Object.keys(healthRoundTripped.checks || {}).length).toBe(2);
164
+ });
165
+ });
166
+ // ---------------------------------------------------------------------------
167
+ // Round-trip: health-check-draft adapter → emit → parse
168
+ // ---------------------------------------------------------------------------
169
+ describe('round-trip: adapter → emit → re-parse', () => {
170
+ const adapterFixtures = findFixtures(join(FIXTURE_ROOT, 'adapters', 'health-check-draft')).filter((f) => f.includes('positive'));
171
+ for (const fixturePath of adapterFixtures) {
172
+ const name = fixturePath.split('/').slice(-1)[0];
173
+ it(`adapter round-trip: ${name}`, () => {
174
+ const fixture = loadJson(fixturePath);
175
+ const input = fixture.input;
176
+ // Step 1: parse through adapter
177
+ const adapted = parseHealthCheckDraft(input.body);
178
+ // Step 2: emit as health-response
179
+ const emitted = emitHealthResponse(adapted);
180
+ // Step 3: re-parse the emitted payload
181
+ const roundTripped = normalizeSnapshot(emitted);
182
+ // Step 4: verify the adapter output survived round-trip
183
+ expect(roundTripped.condition).toBe(adapted.condition);
184
+ expect(roundTripped.subject.id).toBe(adapted.subject.id);
185
+ expect(roundTripped.provenance).toBe(adapted.provenance);
186
+ // Must still validate — round-trip must not INTRODUCE new errors
187
+ const adaptedValidation = validateSnapshot(adapted);
188
+ const validation = validateSnapshot(roundTripped);
189
+ if (adaptedValidation.valid) {
190
+ expect(validation.valid).toBe(true);
191
+ }
192
+ else {
193
+ expect(validation.errors.length).toBeLessThanOrEqual(adaptedValidation.errors.length);
194
+ }
195
+ });
196
+ }
197
+ });
198
+ // ---------------------------------------------------------------------------
199
+ // Illegal state detection
200
+ // ---------------------------------------------------------------------------
201
+ describe('round-trip: no illegal state introduced', () => {
202
+ it('emitting then re-parsing does not create empty required fields', () => {
203
+ const original = {
204
+ condition: 'operational',
205
+ profiles: ['health'],
206
+ subject: { id: 'svc-1' },
207
+ timing: { observed: '2026-04-02T19:30:00Z' },
208
+ provenance: 'self-reported',
209
+ };
210
+ const emitted = emitHealthResponse(original);
211
+ const roundTripped = normalizeSnapshot(emitted);
212
+ // Required fields must not become empty
213
+ expect(roundTripped.condition).toBeTruthy();
214
+ expect(roundTripped.subject.id).toBeTruthy();
215
+ expect(roundTripped.profiles.length).toBeGreaterThan(0);
216
+ });
217
+ it('does not silently coerce categorical to orderable', () => {
218
+ const original = {
219
+ condition: 'maintenance',
220
+ profiles: ['health'],
221
+ subject: { id: 'svc-1' },
222
+ timing: { observed: '2026-04-02T19:30:00Z' },
223
+ };
224
+ const emitted = emitHealthResponse(original);
225
+ const roundTripped = normalizeSnapshot(emitted);
226
+ // 'maintenance' must remain 'maintenance', not get coerced
227
+ expect(roundTripped.condition).toBe('maintenance');
228
+ });
229
+ it('does not silently coerce extension values', () => {
230
+ const original = {
231
+ condition: 'x-acme-draining',
232
+ profiles: ['health'],
233
+ subject: { id: 'svc-1' },
234
+ timing: { observed: '2026-04-02T19:30:00Z' },
235
+ };
236
+ const emitted = emitHealthResponse(original);
237
+ const roundTripped = normalizeSnapshot(emitted);
238
+ // Extension values must survive untouched
239
+ expect(roundTripped.condition).toBe('x-acme-draining');
240
+ });
241
+ });
242
+ //# sourceMappingURL=round-trip.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"round-trip.test.js","sourceRoot":"","sources":["../../src/__tests__/round-trip.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Health Response Emitter
3
+ *
4
+ * Converts a Snapshot → application/health+json wire format.
5
+ */
6
+ import type { Snapshot } from '@open-operational-state/types';
7
+ export interface HealthResponsePayload {
8
+ condition: string;
9
+ profiles: string[];
10
+ subject: {
11
+ id: string;
12
+ description?: string;
13
+ };
14
+ timing?: {
15
+ observed?: string;
16
+ reported?: string;
17
+ stateChanged?: string;
18
+ };
19
+ provenance?: string;
20
+ evidence?: {
21
+ type: string;
22
+ detail?: string;
23
+ };
24
+ checks?: Record<string, unknown>;
25
+ links?: Record<string, string>;
26
+ }
27
+ /**
28
+ * Emit a Snapshot as an application/health+json payload.
29
+ */
30
+ export declare function emitHealthResponse(snapshot: Snapshot): HealthResponsePayload;
31
+ //# sourceMappingURL=health-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-response.d.ts","sourceRoot":"","sources":["../src/health-response.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAgC,MAAM,+BAA+B,CAAC;AAM5F,MAAM,WAAW,qBAAqB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE;QACL,EAAE,EAAE,MAAM,CAAC;QACX,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,MAAM,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAOD;;GAEG;AACH,wBAAgB,kBAAkB,CAC9B,QAAQ,EAAE,QAAQ,GACnB,qBAAqB,CAuCvB"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Health Response Emitter
3
+ *
4
+ * Converts a Snapshot → application/health+json wire format.
5
+ */
6
+ // ---------------------------------------------------------------------------
7
+ // Public API
8
+ // ---------------------------------------------------------------------------
9
+ /**
10
+ * Emit a Snapshot as an application/health+json payload.
11
+ */
12
+ export function emitHealthResponse(snapshot) {
13
+ const payload = {
14
+ condition: snapshot.condition,
15
+ profiles: [...snapshot.profiles],
16
+ subject: {
17
+ id: snapshot.subject.id,
18
+ ...(snapshot.subject.description ? { description: snapshot.subject.description } : {}),
19
+ },
20
+ };
21
+ // Timing
22
+ if (snapshot.timing) {
23
+ payload.timing = { ...snapshot.timing };
24
+ }
25
+ // Provenance
26
+ if (snapshot.provenance) {
27
+ payload.provenance = snapshot.provenance;
28
+ }
29
+ // Evidence
30
+ if (snapshot.evidence) {
31
+ const evidence = { type: snapshot.evidence.type };
32
+ if (snapshot.evidence.detail) {
33
+ evidence.detail = snapshot.evidence.detail;
34
+ }
35
+ payload.evidence = evidence;
36
+ }
37
+ // Checks — merge from flat checks or convert from nested arrays
38
+ const checks = buildChecks(snapshot);
39
+ if (checks && Object.keys(checks).length > 0) {
40
+ payload.checks = checks;
41
+ }
42
+ // Links
43
+ if (snapshot.links && Object.keys(snapshot.links).length > 0) {
44
+ payload.links = { ...snapshot.links };
45
+ }
46
+ return payload;
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // Internal
50
+ // ---------------------------------------------------------------------------
51
+ function buildChecks(snapshot) {
52
+ const result = {};
53
+ // Flat checks (native health-response format)
54
+ if (snapshot.checks) {
55
+ for (const [key, check] of Object.entries(snapshot.checks)) {
56
+ result[key] = buildCheckEntry(check);
57
+ }
58
+ }
59
+ // Nested components → flat checks
60
+ if (snapshot.components) {
61
+ for (const comp of snapshot.components) {
62
+ const entry = {
63
+ condition: comp.condition,
64
+ role: 'component',
65
+ };
66
+ if (comp.timing) {
67
+ entry.timing = comp.timing;
68
+ }
69
+ if (comp.provenance) {
70
+ entry.provenance = comp.provenance;
71
+ }
72
+ if (comp.evidence) {
73
+ entry.evidence = comp.evidence;
74
+ }
75
+ result[comp.id] = entry;
76
+ }
77
+ }
78
+ // Nested dependencies → flat checks
79
+ if (snapshot.dependencies) {
80
+ for (const dep of snapshot.dependencies) {
81
+ const entry = {
82
+ condition: dep.condition,
83
+ role: 'dependency',
84
+ };
85
+ if (dep.timing) {
86
+ entry.timing = dep.timing;
87
+ }
88
+ if (dep.provenance) {
89
+ entry.provenance = dep.provenance;
90
+ }
91
+ if (dep.evidence) {
92
+ entry.evidence = dep.evidence;
93
+ }
94
+ result[dep.id] = entry;
95
+ }
96
+ }
97
+ return Object.keys(result).length > 0 ? result : undefined;
98
+ }
99
+ function buildCheckEntry(check) {
100
+ const entry = {
101
+ condition: check.condition,
102
+ };
103
+ if (check.role) {
104
+ entry.role = check.role;
105
+ }
106
+ if (check.timing) {
107
+ entry.timing = check.timing;
108
+ }
109
+ if (check.provenance) {
110
+ entry.provenance = check.provenance;
111
+ }
112
+ if (check.evidence) {
113
+ entry.evidence = check.evidence;
114
+ }
115
+ return entry;
116
+ }
117
+ //# sourceMappingURL=health-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-response.js","sourceRoot":"","sources":["../src/health-response.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA8BH,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAC9B,QAAkB;IAElB,MAAM,OAAO,GAA0B;QACnC,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,QAAQ,EAAE,CAAE,GAAG,QAAQ,CAAC,QAAQ,CAAE;QAClC,OAAO,EAAE;YACL,EAAE,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE;YACvB,GAAG,CAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAE;SAC3F;KACJ,CAAC;IAEF,SAAS;IACT,IAAK,QAAQ,CAAC,MAAM,EAAG,CAAC;QACpB,OAAO,CAAC,MAAM,GAAG,EAAE,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;IAC5C,CAAC;IAED,aAAa;IACb,IAAK,QAAQ,CAAC,UAAU,EAAG,CAAC;QACxB,OAAO,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;IAC7C,CAAC;IAED,WAAW;IACX,IAAK,QAAQ,CAAC,QAAQ,EAAG,CAAC;QACtB,MAAM,QAAQ,GAAsC,EAAE,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACrF,IAAK,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAG,CAAC;YAAC,QAAS,CAAC,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAC,CAAC;QAChF,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAChC,CAAC;IAED,gEAAgE;IAChE,MAAM,MAAM,GAAG,WAAW,CAAE,QAAQ,CAAE,CAAC;IACvC,IAAK,MAAM,IAAI,MAAM,CAAC,IAAI,CAAE,MAAM,CAAE,CAAC,MAAM,GAAG,CAAC,EAAG,CAAC;QAC/C,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;IAC5B,CAAC;IAED,QAAQ;IACR,IAAK,QAAQ,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAE,QAAQ,CAAC,KAAK,CAAE,CAAC,MAAM,GAAG,CAAC,EAAG,CAAC;QAC/D,OAAO,CAAC,KAAK,GAAG,EAAE,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC;IAC1C,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E,SAAS,WAAW,CAAE,QAAkB;IACpC,MAAM,MAAM,GAA4B,EAAE,CAAC;IAE3C,8CAA8C;IAC9C,IAAK,QAAQ,CAAC,MAAM,EAAG,CAAC;QACpB,KAAM,MAAM,CAAE,GAAG,EAAE,KAAK,CAAE,IAAI,MAAM,CAAC,OAAO,CAAE,QAAQ,CAAC,MAAM,CAAE,EAAG,CAAC;YAC/D,MAAM,CAAC,GAAG,CAAC,GAAG,eAAe,CAAE,KAAK,CAAE,CAAC;QAC3C,CAAC;IACL,CAAC;IAED,kCAAkC;IAClC,IAAK,QAAQ,CAAC,UAAU,EAAG,CAAC;QACxB,KAAM,MAAM,IAAI,IAAI,QAAQ,CAAC,UAAU,EAAG,CAAC;YACvC,MAAM,KAAK,GAA4B;gBACnC,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,IAAI,EAAE,WAAW;aACpB,CAAC;YACF,IAAK,IAAI,CAAC,MAAM,EAAG,CAAC;gBAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;YAAC,CAAC;YAClD,IAAK,IAAI,CAAC,UAAU,EAAG,CAAC;gBAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;YAAC,CAAC;YAC9D,IAAK,IAAI,CAAC,QAAQ,EAAG,CAAC;gBAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;YAAC,CAAC;YACxD,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC;QAC5B,CAAC;IACL,CAAC;IAED,oCAAoC;IACpC,IAAK,QAAQ,CAAC,YAAY,EAAG,CAAC;QAC1B,KAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,YAAY,EAAG,CAAC;YACxC,MAAM,KAAK,GAA4B;gBACnC,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,IAAI,EAAE,YAAY;aACrB,CAAC;YACF,IAAK,GAAG,CAAC,MAAM,EAAG,CAAC;gBAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;YAAC,CAAC;YAChD,IAAK,GAAG,CAAC,UAAU,EAAG,CAAC;gBAAC,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC;YAAC,CAAC;YAC5D,IAAK,GAAG,CAAC,QAAQ,EAAG,CAAC;gBAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;YAAC,CAAC;YACtD,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC;QAC3B,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAE,MAAM,CAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACjE,CAAC;AAED,SAAS,eAAe,CAAE,KAAiB;IACvC,MAAM,KAAK,GAA4B;QACnC,SAAS,EAAE,KAAK,CAAC,SAAS;KAC7B,CAAC;IACF,IAAK,KAAK,CAAC,IAAI,EAAG,CAAC;QAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IAAC,CAAC;IAC9C,IAAK,KAAK,CAAC,MAAM,EAAG,CAAC;QAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAAC,CAAC;IACpD,IAAK,KAAK,CAAC,UAAU,EAAG,CAAC;QAAC,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IAAC,CAAC;IAChE,IAAK,KAAK,CAAC,QAAQ,EAAG,CAAC;QAAC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;IAAC,CAAC;IAC1D,OAAO,KAAK,CAAC;AACjB,CAAC"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * HTTP Response Helpers
3
+ *
4
+ * Suggest HTTP status codes and headers for operational-state responses
5
+ * per the serialization spec rules.
6
+ */
7
+ import type { Snapshot } from '@open-operational-state/types';
8
+ /**
9
+ * Returns the recommended HTTP status code for a given condition value,
10
+ * per the serialization spec HTTP Response Requirements.
11
+ */
12
+ export declare function suggestHttpStatus(condition: string): number;
13
+ /**
14
+ * Returns recommended HTTP headers for an operational-state response.
15
+ */
16
+ export declare function suggestHeaders(snapshot: Snapshot, serialization: 'application/health+json' | 'application/status+json'): Record<string, string>;
17
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAM9D;;;GAGG;AACH,wBAAgB,iBAAiB,CAAE,SAAS,EAAE,MAAM,GAAI,MAAM,CA2B7D;AAMD;;GAEG;AACH,wBAAgB,cAAc,CAC1B,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,yBAAyB,GAAG,yBAAyB,GACrE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAaxB"}
package/dist/http.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * HTTP Response Helpers
3
+ *
4
+ * Suggest HTTP status codes and headers for operational-state responses
5
+ * per the serialization spec rules.
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // HTTP status code suggestion
9
+ // ---------------------------------------------------------------------------
10
+ /**
11
+ * Returns the recommended HTTP status code for a given condition value,
12
+ * per the serialization spec HTTP Response Requirements.
13
+ */
14
+ export function suggestHttpStatus(condition) {
15
+ switch (condition) {
16
+ // Good states → 200
17
+ case 'operational':
18
+ case 'alive':
19
+ case 'ready':
20
+ case 'degraded':
21
+ case 'partial-outage':
22
+ case 'major-outage':
23
+ case 'initializing':
24
+ return 200;
25
+ // Down / unreachable / not-ready → 503
26
+ case 'down':
27
+ case 'unreachable':
28
+ case 'not-ready':
29
+ case 'maintenance':
30
+ return 503;
31
+ // Unknown → 200 (service is responding)
32
+ case 'unknown':
33
+ return 200;
34
+ default:
35
+ // Extension values → 200 (service is responding)
36
+ return 200;
37
+ }
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Header suggestion
41
+ // ---------------------------------------------------------------------------
42
+ /**
43
+ * Returns recommended HTTP headers for an operational-state response.
44
+ */
45
+ export function suggestHeaders(snapshot, serialization) {
46
+ const headers = {
47
+ 'Content-Type': serialization,
48
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
49
+ };
50
+ // Retry-After for maintenance
51
+ if (snapshot.condition === 'maintenance' && snapshot.timing?.stateChanged) {
52
+ // If we know when maintenance started, suggest checking back in 5 minutes
53
+ headers['Retry-After'] = '300';
54
+ }
55
+ return headers;
56
+ }
57
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,8EAA8E;AAC9E,8BAA8B;AAC9B,8EAA8E;AAE9E;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAE,SAAiB;IAChD,QAAS,SAAS,EAAG,CAAC;QAClB,oBAAoB;QACpB,KAAK,aAAa,CAAC;QACnB,KAAK,OAAO,CAAC;QACb,KAAK,OAAO,CAAC;QACb,KAAK,UAAU,CAAC;QAChB,KAAK,gBAAgB,CAAC;QACtB,KAAK,cAAc,CAAC;QACpB,KAAK,cAAc;YACf,OAAO,GAAG,CAAC;QAEf,uCAAuC;QACvC,KAAK,MAAM,CAAC;QACZ,KAAK,aAAa,CAAC;QACnB,KAAK,WAAW,CAAC;QACjB,KAAK,aAAa;YACd,OAAO,GAAG,CAAC;QAEf,wCAAwC;QACxC,KAAK,SAAS;YACV,OAAO,GAAG,CAAC;QAEf;YACI,iDAAiD;YACjD,OAAO,GAAG,CAAC;IACnB,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E;;GAEG;AACH,MAAM,UAAU,cAAc,CAC1B,QAAkB,EAClB,aAAoE;IAEpE,MAAM,OAAO,GAA2B;QACpC,cAAc,EAAE,aAAa;QAC7B,eAAe,EAAE,qCAAqC;KACzD,CAAC;IAEF,8BAA8B;IAC9B,IAAK,QAAQ,CAAC,SAAS,KAAK,aAAa,IAAI,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAG,CAAC;QAC1E,0EAA0E;QAC1E,OAAO,CAAC,aAAa,CAAC,GAAG,KAAK,CAAC;IACnC,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @open-operational-state/emitter
3
+ *
4
+ * Serialization emitters — convert core model Snapshots to wire formats.
5
+ *
6
+ * Depends on @open-operational-state/types and @open-operational-state/core.
7
+ */
8
+ export { emitHealthResponse } from './health-response.js';
9
+ export type { HealthResponsePayload } from './health-response.js';
10
+ export { emitServiceStatus } from './service-status.js';
11
+ export type { ServiceStatusPayload } from './service-status.js';
12
+ export { suggestHttpStatus, suggestHeaders } from './http.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @open-operational-state/emitter
3
+ *
4
+ * Serialization emitters — convert core model Snapshots to wire formats.
5
+ *
6
+ * Depends on @open-operational-state/types and @open-operational-state/core.
7
+ */
8
+ export { emitHealthResponse } from './health-response.js';
9
+ export { emitServiceStatus } from './service-status.js';
10
+ export { suggestHttpStatus, suggestHeaders } from './http.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxD,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Service Status Emitter
3
+ *
4
+ * Converts a Snapshot → application/status+json wire format.
5
+ */
6
+ import type { Snapshot, ComponentEntry, DependencyEntry } from '@open-operational-state/types';
7
+ export interface ServiceStatusPayload {
8
+ condition: string;
9
+ profiles: string[];
10
+ subject: {
11
+ id: string;
12
+ description?: string;
13
+ version?: string;
14
+ contact?: string;
15
+ };
16
+ timing?: {
17
+ observed?: string;
18
+ reported?: string;
19
+ stateChanged?: string;
20
+ };
21
+ provenance?: string;
22
+ evidence?: {
23
+ type: string;
24
+ detail?: string;
25
+ };
26
+ scope?: {
27
+ type: string;
28
+ identifier?: string;
29
+ };
30
+ components?: ComponentEntry[];
31
+ dependencies?: DependencyEntry[];
32
+ incidents?: unknown[];
33
+ links?: Record<string, string>;
34
+ }
35
+ /**
36
+ * Emit a Snapshot as an application/status+json payload.
37
+ */
38
+ export declare function emitServiceStatus(snapshot: Snapshot): ServiceStatusPayload;
39
+ //# sourceMappingURL=service-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-status.d.ts","sourceRoot":"","sources":["../src/service-status.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAM/F,MAAM,WAAW,oBAAoB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE;QACL,EAAE,EAAE,MAAM,CAAC;QACX,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,MAAM,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE;QACP,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,KAAK,CAAC,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,YAAY,CAAC,EAAE,eAAe,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAMD;;GAEG;AACH,wBAAgB,iBAAiB,CAAE,QAAQ,EAAE,QAAQ,GAAI,oBAAoB,CA8E5E"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Service Status Emitter
3
+ *
4
+ * Converts a Snapshot → application/status+json wire format.
5
+ */
6
+ // ---------------------------------------------------------------------------
7
+ // Public API
8
+ // ---------------------------------------------------------------------------
9
+ /**
10
+ * Emit a Snapshot as an application/status+json payload.
11
+ */
12
+ export function emitServiceStatus(snapshot) {
13
+ const payload = {
14
+ condition: snapshot.condition,
15
+ profiles: [...snapshot.profiles],
16
+ subject: {
17
+ id: snapshot.subject.id,
18
+ ...(snapshot.subject.description ? { description: snapshot.subject.description } : {}),
19
+ ...(snapshot.subject.version ? { version: snapshot.subject.version } : {}),
20
+ ...(snapshot.subject.contact ? { contact: snapshot.subject.contact } : {}),
21
+ },
22
+ };
23
+ // Timing
24
+ if (snapshot.timing) {
25
+ payload.timing = { ...snapshot.timing };
26
+ }
27
+ // Provenance
28
+ if (snapshot.provenance) {
29
+ payload.provenance = snapshot.provenance;
30
+ }
31
+ // Evidence
32
+ if (snapshot.evidence) {
33
+ payload.evidence = {
34
+ type: snapshot.evidence.type,
35
+ ...(snapshot.evidence.detail ? { detail: snapshot.evidence.detail } : {}),
36
+ };
37
+ }
38
+ // Scope
39
+ if (snapshot.scope) {
40
+ payload.scope = { ...snapshot.scope };
41
+ }
42
+ // Components — from nested array or convert from flat checks
43
+ if (snapshot.components) {
44
+ payload.components = snapshot.components.map(cloneComponent);
45
+ }
46
+ else if (snapshot.checks) {
47
+ payload.components = [];
48
+ payload.dependencies = [];
49
+ for (const [key, check] of Object.entries(snapshot.checks)) {
50
+ if (check.role === 'dependency') {
51
+ payload.dependencies.push({
52
+ id: key,
53
+ condition: check.condition,
54
+ ...(check.timing ? { timing: check.timing } : {}),
55
+ ...(check.provenance ? { provenance: check.provenance } : {}),
56
+ ...(check.evidence ? { evidence: check.evidence } : {}),
57
+ });
58
+ }
59
+ else {
60
+ payload.components.push({
61
+ id: key,
62
+ condition: check.condition,
63
+ ...(check.timing ? { timing: check.timing } : {}),
64
+ ...(check.provenance ? { provenance: check.provenance } : {}),
65
+ ...(check.evidence ? { evidence: check.evidence } : {}),
66
+ });
67
+ }
68
+ }
69
+ }
70
+ // Dependencies (from nested array)
71
+ if (snapshot.dependencies) {
72
+ payload.dependencies = snapshot.dependencies.map((dep) => ({ ...dep }));
73
+ }
74
+ // Incidents
75
+ if (snapshot.incidents && snapshot.incidents.length > 0) {
76
+ payload.incidents = snapshot.incidents;
77
+ }
78
+ // Links
79
+ if (snapshot.links && Object.keys(snapshot.links).length > 0) {
80
+ payload.links = { ...snapshot.links };
81
+ }
82
+ return payload;
83
+ }
84
+ // ---------------------------------------------------------------------------
85
+ // Internal
86
+ // ---------------------------------------------------------------------------
87
+ function cloneComponent(comp) {
88
+ const clone = {
89
+ id: comp.id,
90
+ condition: comp.condition,
91
+ };
92
+ if (comp.description) {
93
+ clone.description = comp.description;
94
+ }
95
+ if (comp.criticality) {
96
+ clone.criticality = comp.criticality;
97
+ }
98
+ if (comp.timing) {
99
+ clone.timing = { ...comp.timing };
100
+ }
101
+ if (comp.provenance) {
102
+ clone.provenance = comp.provenance;
103
+ }
104
+ if (comp.evidence) {
105
+ clone.evidence = { ...comp.evidence };
106
+ }
107
+ if (comp.components) {
108
+ clone.components = comp.components.map(cloneComponent);
109
+ }
110
+ return clone;
111
+ }
112
+ //# sourceMappingURL=service-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-status.js","sourceRoot":"","sources":["../src/service-status.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@open-operational-state/emitter",
3
+ "version": "0.1.0",
4
+ "description": "Serialization emitters for Open Operational State wire formats",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/open-operational-state/status-tooling.git",
24
+ "directory": "packages/emitter"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "clean": "rm -rf dist",
29
+ "typecheck": "tsc --noEmit",
30
+ "test": "bun test"
31
+ },
32
+ "dependencies": {
33
+ "@open-operational-state/types": "^0.1.0",
34
+ "@open-operational-state/core": "^0.1.0"
35
+ },
36
+ "devDependencies": {
37
+ "@open-operational-state/parser": "^0.1.0",
38
+ "typescript": "^5.8.0"
39
+ }
40
+ }