@cleocode/lafs-protocol 1.0.0 → 1.2.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.
@@ -0,0 +1,249 @@
1
+ /**
2
+ * LAFS Circuit Breaker Module
3
+ *
4
+ * Provides circuit breaker pattern for resilient service calls
5
+ */
6
+ export class CircuitBreakerError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = 'CircuitBreakerError';
10
+ }
11
+ }
12
+ /**
13
+ * Circuit breaker for protecting against cascading failures
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { CircuitBreaker } from '@cleocode/lafs-protocol/circuit-breaker';
18
+ *
19
+ * const breaker = new CircuitBreaker({
20
+ * name: 'external-api',
21
+ * failureThreshold: 5,
22
+ * resetTimeout: 30000
23
+ * });
24
+ *
25
+ * try {
26
+ * const result = await breaker.execute(async () => {
27
+ * return await externalApi.call();
28
+ * });
29
+ * } catch (error) {
30
+ * if (error instanceof CircuitBreakerError) {
31
+ * console.log('Circuit breaker is open');
32
+ * }
33
+ * }
34
+ * ```
35
+ */
36
+ export class CircuitBreaker {
37
+ config;
38
+ state = 'CLOSED';
39
+ failures = 0;
40
+ successes = 0;
41
+ lastFailureTime;
42
+ consecutiveSuccesses = 0;
43
+ totalCalls = 0;
44
+ halfOpenCalls = 0;
45
+ resetTimer;
46
+ constructor(config) {
47
+ this.config = config;
48
+ this.config = {
49
+ failureThreshold: 5,
50
+ resetTimeout: 30000,
51
+ halfOpenMaxCalls: 3,
52
+ successThreshold: 2,
53
+ ...config
54
+ };
55
+ }
56
+ /**
57
+ * Execute a function with circuit breaker protection
58
+ */
59
+ async execute(fn) {
60
+ this.totalCalls++;
61
+ if (this.state === 'OPEN') {
62
+ if (this.shouldAttemptReset()) {
63
+ this.transitionTo('HALF_OPEN');
64
+ }
65
+ else {
66
+ throw new CircuitBreakerError(`Circuit breaker '${this.config.name}' is OPEN`);
67
+ }
68
+ }
69
+ if (this.state === 'HALF_OPEN') {
70
+ if (this.halfOpenCalls >= (this.config.halfOpenMaxCalls || 3)) {
71
+ throw new CircuitBreakerError(`Circuit breaker '${this.config.name}' is HALF_OPEN (max calls reached)`);
72
+ }
73
+ this.halfOpenCalls++;
74
+ }
75
+ try {
76
+ const result = await fn();
77
+ this.onSuccess();
78
+ return result;
79
+ }
80
+ catch (error) {
81
+ this.onFailure();
82
+ throw error;
83
+ }
84
+ }
85
+ /**
86
+ * Get current circuit breaker state
87
+ */
88
+ getState() {
89
+ return this.state;
90
+ }
91
+ /**
92
+ * Get circuit breaker metrics
93
+ */
94
+ getMetrics() {
95
+ return {
96
+ state: this.state,
97
+ failures: this.failures,
98
+ successes: this.successes,
99
+ lastFailureTime: this.lastFailureTime,
100
+ consecutiveSuccesses: this.consecutiveSuccesses,
101
+ totalCalls: this.totalCalls
102
+ };
103
+ }
104
+ /**
105
+ * Manually open the circuit breaker
106
+ */
107
+ forceOpen() {
108
+ this.transitionTo('OPEN');
109
+ }
110
+ /**
111
+ * Manually close the circuit breaker
112
+ */
113
+ forceClose() {
114
+ this.transitionTo('CLOSED');
115
+ this.reset();
116
+ }
117
+ onSuccess() {
118
+ this.successes++;
119
+ this.consecutiveSuccesses++;
120
+ if (this.state === 'HALF_OPEN') {
121
+ if (this.consecutiveSuccesses >= (this.config.successThreshold || 2)) {
122
+ this.transitionTo('CLOSED');
123
+ this.reset();
124
+ }
125
+ }
126
+ }
127
+ onFailure() {
128
+ this.failures++;
129
+ this.consecutiveSuccesses = 0;
130
+ this.lastFailureTime = new Date();
131
+ if (this.state === 'HALF_OPEN') {
132
+ this.transitionTo('OPEN');
133
+ this.scheduleReset();
134
+ }
135
+ else if (this.state === 'CLOSED') {
136
+ if (this.failures >= (this.config.failureThreshold || 5)) {
137
+ this.transitionTo('OPEN');
138
+ this.scheduleReset();
139
+ }
140
+ }
141
+ }
142
+ transitionTo(newState) {
143
+ console.log(`Circuit breaker '${this.config.name}': ${this.state} -> ${newState}`);
144
+ this.state = newState;
145
+ if (newState === 'HALF_OPEN') {
146
+ this.halfOpenCalls = 0;
147
+ }
148
+ }
149
+ shouldAttemptReset() {
150
+ if (!this.lastFailureTime)
151
+ return true;
152
+ const elapsed = Date.now() - this.lastFailureTime.getTime();
153
+ return elapsed >= (this.config.resetTimeout || 30000);
154
+ }
155
+ scheduleReset() {
156
+ if (this.resetTimer) {
157
+ clearTimeout(this.resetTimer);
158
+ }
159
+ this.resetTimer = setTimeout(() => {
160
+ if (this.state === 'OPEN') {
161
+ this.transitionTo('HALF_OPEN');
162
+ }
163
+ }, this.config.resetTimeout || 30000);
164
+ }
165
+ reset() {
166
+ this.failures = 0;
167
+ this.consecutiveSuccesses = 0;
168
+ this.halfOpenCalls = 0;
169
+ if (this.resetTimer) {
170
+ clearTimeout(this.resetTimer);
171
+ this.resetTimer = undefined;
172
+ }
173
+ }
174
+ }
175
+ /**
176
+ * Circuit breaker registry for managing multiple breakers
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * const registry = new CircuitBreakerRegistry();
181
+ *
182
+ * registry.add('payment-api', {
183
+ * failureThreshold: 3,
184
+ * resetTimeout: 60000
185
+ * });
186
+ *
187
+ * const paymentBreaker = registry.get('payment-api');
188
+ * ```
189
+ */
190
+ export class CircuitBreakerRegistry {
191
+ breakers = new Map();
192
+ add(name, config) {
193
+ const breaker = new CircuitBreaker({ ...config, name });
194
+ this.breakers.set(name, breaker);
195
+ return breaker;
196
+ }
197
+ get(name) {
198
+ return this.breakers.get(name);
199
+ }
200
+ getOrCreate(name, config) {
201
+ let breaker = this.breakers.get(name);
202
+ if (!breaker) {
203
+ breaker = this.add(name, config);
204
+ }
205
+ return breaker;
206
+ }
207
+ getAllMetrics() {
208
+ const metrics = {};
209
+ this.breakers.forEach((breaker, name) => {
210
+ metrics[name] = breaker.getMetrics();
211
+ });
212
+ return metrics;
213
+ }
214
+ resetAll() {
215
+ this.breakers.forEach(breaker => breaker.forceClose());
216
+ }
217
+ }
218
+ /**
219
+ * Create a circuit breaker middleware for Express
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * app.use('/external-api', circuitBreakerMiddleware({
224
+ * name: 'external-api',
225
+ * failureThreshold: 5
226
+ * }));
227
+ * ```
228
+ */
229
+ export function circuitBreakerMiddleware(config) {
230
+ const breaker = new CircuitBreaker(config);
231
+ return async (req, res, next) => {
232
+ try {
233
+ await breaker.execute(async () => {
234
+ next();
235
+ });
236
+ }
237
+ catch (error) {
238
+ if (error instanceof CircuitBreakerError) {
239
+ res.status(503).json({
240
+ error: 'Service temporarily unavailable',
241
+ reason: 'Circuit breaker is open'
242
+ });
243
+ }
244
+ else {
245
+ throw error;
246
+ }
247
+ }
248
+ };
249
+ }
@@ -2,6 +2,8 @@ import type { FlagInput } from "./types.js";
2
2
  export interface FlagResolution {
3
3
  format: "json" | "human";
4
4
  source: "flag" | "project" | "user" | "default";
5
+ /** When true, suppress non-essential output for scripting */
6
+ quiet: boolean;
5
7
  }
6
8
  export declare class LAFSFlagError extends Error {
7
9
  code: string;
@@ -10,20 +10,21 @@ export function resolveOutputFormat(input) {
10
10
  if (input.humanFlag && input.jsonFlag) {
11
11
  throw new LAFSFlagError("E_FORMAT_CONFLICT", "Cannot combine --human and --json in the same invocation.");
12
12
  }
13
+ const quiet = input.quiet ?? false;
13
14
  if (input.requestedFormat) {
14
- return { format: input.requestedFormat, source: "flag" };
15
+ return { format: input.requestedFormat, source: "flag", quiet };
15
16
  }
16
17
  if (input.humanFlag) {
17
- return { format: "human", source: "flag" };
18
+ return { format: "human", source: "flag", quiet };
18
19
  }
19
20
  if (input.jsonFlag) {
20
- return { format: "json", source: "flag" };
21
+ return { format: "json", source: "flag", quiet };
21
22
  }
22
23
  if (input.projectDefault) {
23
- return { format: input.projectDefault, source: "project" };
24
+ return { format: input.projectDefault, source: "project", quiet };
24
25
  }
25
26
  if (input.userDefault) {
26
- return { format: input.userDefault, source: "user" };
27
+ return { format: input.userDefault, source: "user", quiet };
27
28
  }
28
- return { format: "json", source: "default" };
29
+ return { format: "json", source: "default", quiet };
29
30
  }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * LAFS Health Check Module
3
+ *
4
+ * Provides health check endpoints for monitoring and orchestration
5
+ */
6
+ export interface HealthCheckConfig {
7
+ path?: string;
8
+ checks?: HealthCheckFunction[];
9
+ }
10
+ export type HealthCheckFunction = () => Promise<HealthCheckResult> | HealthCheckResult;
11
+ export interface HealthCheckResult {
12
+ name: string;
13
+ status: 'ok' | 'warning' | 'error';
14
+ message?: string;
15
+ duration?: number;
16
+ }
17
+ export interface HealthStatus {
18
+ status: 'healthy' | 'degraded' | 'unhealthy';
19
+ timestamp: string;
20
+ version: string;
21
+ uptime: number;
22
+ checks: HealthCheckResult[];
23
+ }
24
+ /**
25
+ * Health check middleware for Express applications
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * import express from 'express';
30
+ * import { healthCheck } from '@cleocode/lafs-protocol/health';
31
+ *
32
+ * const app = express();
33
+ *
34
+ * // Basic health check
35
+ * app.use('/health', healthCheck());
36
+ *
37
+ * // Custom health checks
38
+ * app.use('/health', healthCheck({
39
+ * checks: [
40
+ * async () => ({
41
+ * name: 'database',
42
+ * status: await checkDatabase() ? 'ok' : 'error'
43
+ * })
44
+ * ]
45
+ * }));
46
+ * ```
47
+ */
48
+ export declare function healthCheck(config?: HealthCheckConfig): (req: any, res: any) => Promise<void>;
49
+ /**
50
+ * Create a database health check
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const dbCheck = createDatabaseHealthCheck({
55
+ * checkConnection: async () => await db.ping()
56
+ * });
57
+ *
58
+ * app.use('/health', healthCheck({
59
+ * checks: [dbCheck]
60
+ * }));
61
+ * ```
62
+ */
63
+ export declare function createDatabaseHealthCheck(config: {
64
+ checkConnection: () => Promise<boolean>;
65
+ name?: string;
66
+ }): HealthCheckFunction;
67
+ /**
68
+ * Create an external service health check
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const apiCheck = createExternalServiceHealthCheck({
73
+ * name: 'payment-api',
74
+ * url: 'https://api.payment.com/health',
75
+ * timeout: 5000
76
+ * });
77
+ * ```
78
+ */
79
+ export declare function createExternalServiceHealthCheck(config: {
80
+ name: string;
81
+ url: string;
82
+ timeout?: number;
83
+ }): HealthCheckFunction;
84
+ /**
85
+ * Liveness probe - basic check that service is running
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * app.get('/health/live', livenessProbe());
90
+ * ```
91
+ */
92
+ export declare function livenessProbe(): (req: any, res: any) => void;
93
+ /**
94
+ * Readiness probe - check that service is ready to accept traffic
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * app.get('/health/ready', readinessProbe({
99
+ * checks: [dbCheck, cacheCheck]
100
+ * }));
101
+ * ```
102
+ */
103
+ export declare function readinessProbe(config?: {
104
+ checks?: HealthCheckFunction[];
105
+ }): (req: any, res: any) => Promise<void>;
@@ -0,0 +1,211 @@
1
+ /**
2
+ * LAFS Health Check Module
3
+ *
4
+ * Provides health check endpoints for monitoring and orchestration
5
+ */
6
+ /**
7
+ * Health check middleware for Express applications
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import express from 'express';
12
+ * import { healthCheck } from '@cleocode/lafs-protocol/health';
13
+ *
14
+ * const app = express();
15
+ *
16
+ * // Basic health check
17
+ * app.use('/health', healthCheck());
18
+ *
19
+ * // Custom health checks
20
+ * app.use('/health', healthCheck({
21
+ * checks: [
22
+ * async () => ({
23
+ * name: 'database',
24
+ * status: await checkDatabase() ? 'ok' : 'error'
25
+ * })
26
+ * ]
27
+ * }));
28
+ * ```
29
+ */
30
+ export function healthCheck(config = {}) {
31
+ const { path = '/health', checks = [] } = config;
32
+ const startTime = Date.now();
33
+ return async (req, res) => {
34
+ const timestamp = new Date().toISOString();
35
+ const checkResults = [];
36
+ // Run all health checks
37
+ for (const check of checks) {
38
+ const start = Date.now();
39
+ try {
40
+ const result = await check();
41
+ result.duration = Date.now() - start;
42
+ checkResults.push(result);
43
+ }
44
+ catch (error) {
45
+ checkResults.push({
46
+ name: 'unknown',
47
+ status: 'error',
48
+ message: error instanceof Error ? error.message : 'Check failed',
49
+ duration: Date.now() - start
50
+ });
51
+ }
52
+ }
53
+ // Add default checks
54
+ checkResults.push({
55
+ name: 'envelopeValidation',
56
+ status: 'ok'
57
+ });
58
+ checkResults.push({
59
+ name: 'tokenBudgets',
60
+ status: 'ok'
61
+ });
62
+ // Determine overall status
63
+ const hasErrors = checkResults.some(c => c.status === 'error');
64
+ const hasWarnings = checkResults.some(c => c.status === 'warning');
65
+ const status = hasErrors
66
+ ? 'unhealthy'
67
+ : hasWarnings
68
+ ? 'degraded'
69
+ : 'healthy';
70
+ const health = {
71
+ status,
72
+ timestamp,
73
+ version: process.env.npm_package_version || '1.1.0',
74
+ uptime: Math.floor((Date.now() - startTime) / 1000),
75
+ checks: checkResults
76
+ };
77
+ const statusCode = status === 'healthy' ? 200 : status === 'degraded' ? 200 : 503;
78
+ res.status(statusCode).json(health);
79
+ };
80
+ }
81
+ /**
82
+ * Create a database health check
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const dbCheck = createDatabaseHealthCheck({
87
+ * checkConnection: async () => await db.ping()
88
+ * });
89
+ *
90
+ * app.use('/health', healthCheck({
91
+ * checks: [dbCheck]
92
+ * }));
93
+ * ```
94
+ */
95
+ export function createDatabaseHealthCheck(config) {
96
+ return async () => {
97
+ try {
98
+ const isConnected = await config.checkConnection();
99
+ return {
100
+ name: config.name || 'database',
101
+ status: isConnected ? 'ok' : 'error',
102
+ message: isConnected ? 'Connected' : 'Connection failed'
103
+ };
104
+ }
105
+ catch (error) {
106
+ return {
107
+ name: config.name || 'database',
108
+ status: 'error',
109
+ message: error instanceof Error ? error.message : 'Database check failed'
110
+ };
111
+ }
112
+ };
113
+ }
114
+ /**
115
+ * Create an external service health check
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * const apiCheck = createExternalServiceHealthCheck({
120
+ * name: 'payment-api',
121
+ * url: 'https://api.payment.com/health',
122
+ * timeout: 5000
123
+ * });
124
+ * ```
125
+ */
126
+ export function createExternalServiceHealthCheck(config) {
127
+ return async () => {
128
+ const start = Date.now();
129
+ try {
130
+ const controller = new AbortController();
131
+ const timeout = setTimeout(() => controller.abort(), config.timeout || 5000);
132
+ const response = await fetch(config.url, {
133
+ signal: controller.signal
134
+ });
135
+ clearTimeout(timeout);
136
+ return {
137
+ name: config.name,
138
+ status: response.ok ? 'ok' : 'error',
139
+ message: response.ok ? 'Service healthy' : `HTTP ${response.status}`,
140
+ duration: Date.now() - start
141
+ };
142
+ }
143
+ catch (error) {
144
+ return {
145
+ name: config.name,
146
+ status: 'error',
147
+ message: error instanceof Error ? error.message : 'Service unreachable',
148
+ duration: Date.now() - start
149
+ };
150
+ }
151
+ };
152
+ }
153
+ /**
154
+ * Liveness probe - basic check that service is running
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * app.get('/health/live', livenessProbe());
159
+ * ```
160
+ */
161
+ export function livenessProbe() {
162
+ return (req, res) => {
163
+ res.status(200).json({
164
+ status: 'alive',
165
+ timestamp: new Date().toISOString()
166
+ });
167
+ };
168
+ }
169
+ /**
170
+ * Readiness probe - check that service is ready to accept traffic
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * app.get('/health/ready', readinessProbe({
175
+ * checks: [dbCheck, cacheCheck]
176
+ * }));
177
+ * ```
178
+ */
179
+ export function readinessProbe(config = {}) {
180
+ return async (req, res) => {
181
+ const checkResults = [];
182
+ if (config.checks) {
183
+ for (const check of config.checks) {
184
+ try {
185
+ const result = await check();
186
+ checkResults.push(result);
187
+ }
188
+ catch (error) {
189
+ checkResults.push({
190
+ name: 'unknown',
191
+ status: 'error',
192
+ message: error instanceof Error ? error.message : 'Check failed'
193
+ });
194
+ }
195
+ }
196
+ }
197
+ const hasErrors = checkResults.some(c => c.status === 'error');
198
+ if (hasErrors) {
199
+ res.status(503).json({
200
+ status: 'not ready',
201
+ checks: checkResults
202
+ });
203
+ }
204
+ else {
205
+ res.status(200).json({
206
+ status: 'ready',
207
+ checks: checkResults
208
+ });
209
+ }
210
+ };
211
+ }
@@ -7,3 +7,7 @@ export * from "./tokenEstimator.js";
7
7
  export * from "./budgetEnforcement.js";
8
8
  export * from "./mcpAdapter.js";
9
9
  export * from "./discovery.js";
10
+ export * from "./health/index.js";
11
+ export * from "./shutdown/index.js";
12
+ export * from "./circuit-breaker/index.js";
13
+ export * from "./a2a/index.js";
package/dist/src/index.js CHANGED
@@ -7,3 +7,9 @@ export * from "./tokenEstimator.js";
7
7
  export * from "./budgetEnforcement.js";
8
8
  export * from "./mcpAdapter.js";
9
9
  export * from "./discovery.js";
10
+ // Operations & Reliability
11
+ export * from "./health/index.js";
12
+ export * from "./shutdown/index.js";
13
+ export * from "./circuit-breaker/index.js";
14
+ // A2A Integration
15
+ export * from "./a2a/index.js";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * LAFS Graceful Shutdown Module
3
+ *
4
+ * Handles graceful shutdown of LAFS servers
5
+ */
6
+ import { Server } from 'http';
7
+ export interface GracefulShutdownConfig {
8
+ timeout?: number;
9
+ signals?: NodeJS.Signals[];
10
+ onShutdown?: () => Promise<void> | void;
11
+ onClose?: () => Promise<void> | void;
12
+ }
13
+ export interface ShutdownState {
14
+ isShuttingDown: boolean;
15
+ activeConnections: number;
16
+ shutdownStartTime?: Date;
17
+ }
18
+ /**
19
+ * Enable graceful shutdown for an HTTP server
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import express from 'express';
24
+ * import { gracefulShutdown } from '@cleocode/lafs-protocol/shutdown';
25
+ *
26
+ * const app = express();
27
+ * const server = app.listen(3000);
28
+ *
29
+ * gracefulShutdown(server, {
30
+ * timeout: 30000,
31
+ * signals: ['SIGTERM', 'SIGINT'],
32
+ * onShutdown: async () => {
33
+ * console.log('Shutting down...');
34
+ * await db.close();
35
+ * }
36
+ * });
37
+ * ```
38
+ */
39
+ export declare function gracefulShutdown(server: Server, config?: GracefulShutdownConfig): void;
40
+ /**
41
+ * Check if server is shutting down
42
+ */
43
+ export declare function isShuttingDown(): boolean;
44
+ /**
45
+ * Get shutdown state
46
+ */
47
+ export declare function getShutdownState(): ShutdownState;
48
+ /**
49
+ * Force immediate shutdown (emergency use only)
50
+ */
51
+ export declare function forceShutdown(exitCode?: number): void;
52
+ /**
53
+ * Middleware to reject requests during shutdown
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * app.use(shutdownMiddleware());
58
+ * ```
59
+ */
60
+ export declare function shutdownMiddleware(): (req: any, res: any, next: any) => void;
61
+ /**
62
+ * Wait for shutdown to complete
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * await waitForShutdown();
67
+ * ```
68
+ */
69
+ export declare function waitForShutdown(): Promise<void>;