feat(polling-contracts): add generic polling interface with TypeScript types and Zod schemas
- Add @timesafari/polling-contracts package with comprehensive type definitions - Implement GenericPollingRequest, PollingResult, and PollingScheduleConfig interfaces - Add Zod schemas for StarredProjectsRequest/Response and DeepLinkParams validation - Include calculateBackoffDelay utility with unified retry policy (exponential, linear, fixed) - Add OutboxPressureManager for storage pressure controls and back-pressure signals - Implement TelemetryManager with cardinality budgets and PII redaction - Add ClockSyncManager for JWT timestamp validation and skew tolerance - Include comprehensive unit tests with Jest snapshots and race condition testing - Add JWT_ID_PATTERN regex for canonical JWT ID format validation - Support idempotency with X-Idempotency-Key enforcement - Implement watermark CAS (Compare-and-Swap) for race condition prevention This establishes the foundation for the new generic polling system where host apps define request/response schemas and the plugin provides robust polling logic.
This commit is contained in:
144
packages/polling-contracts/src/outbox-pressure.ts
Normal file
144
packages/polling-contracts/src/outbox-pressure.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Outbox pressure management with telemetry
|
||||
*/
|
||||
|
||||
import { OutboxPressureConfig, TelemetryMetrics } from './types';
|
||||
import { DEFAULT_CONFIG } from './constants';
|
||||
|
||||
export class OutboxPressureManager {
|
||||
private config: OutboxPressureConfig;
|
||||
private metrics: TelemetryMetrics;
|
||||
|
||||
constructor(config: Partial<OutboxPressureConfig> = {}) {
|
||||
this.config = {
|
||||
maxUndelivered: config.maxUndelivered ?? DEFAULT_CONFIG.maxUndelivered,
|
||||
cleanupIntervalMs: config.cleanupIntervalMs ?? DEFAULT_CONFIG.cleanupIntervalMs,
|
||||
backpressureThreshold: config.backpressureThreshold ?? DEFAULT_CONFIG.backpressureThreshold,
|
||||
evictionPolicy: config.evictionPolicy ?? 'fifo'
|
||||
};
|
||||
|
||||
this.metrics = {
|
||||
'starred_projects_outbox_size': 0,
|
||||
'starred_projects_outbox_backpressure_active': 0
|
||||
} as TelemetryMetrics;
|
||||
}
|
||||
|
||||
async checkStoragePressure(undeliveredCount: number): Promise<boolean> {
|
||||
// Update metrics
|
||||
this.metrics['starred_projects_outbox_size'] = undeliveredCount;
|
||||
|
||||
const pressureRatio = undeliveredCount / this.config.maxUndelivered;
|
||||
const backpressureActive = pressureRatio >= this.config.backpressureThreshold;
|
||||
|
||||
// Update backpressure metric
|
||||
this.metrics['starred_projects_outbox_backpressure_active'] = backpressureActive ? 1 : 0;
|
||||
|
||||
if (pressureRatio >= 1.0) {
|
||||
// Critical: Drop oldest notifications to make room
|
||||
const evictCount = undeliveredCount - this.config.maxUndelivered;
|
||||
await this.evictNotifications(evictCount);
|
||||
return true; // Backpressure active
|
||||
}
|
||||
|
||||
return backpressureActive;
|
||||
}
|
||||
|
||||
async evictNotifications(count: number): Promise<void> {
|
||||
if (count <= 0) return;
|
||||
|
||||
// Simulate eviction based on policy
|
||||
switch (this.config.evictionPolicy) {
|
||||
case 'fifo':
|
||||
await this.evictFIFO(count);
|
||||
break;
|
||||
case 'lifo':
|
||||
await this.evictLIFO(count);
|
||||
break;
|
||||
case 'priority':
|
||||
await this.evictByPriority(count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async evictFIFO(count: number): Promise<void> {
|
||||
// Simulate: DELETE FROM notification_outbox
|
||||
// WHERE delivered_at IS NULL
|
||||
// ORDER BY created_at ASC
|
||||
// LIMIT count
|
||||
console.log(`Evicting ${count} oldest notifications (FIFO)`);
|
||||
}
|
||||
|
||||
private async evictLIFO(count: number): Promise<void> {
|
||||
// Simulate: DELETE FROM notification_outbox
|
||||
// WHERE delivered_at IS NULL
|
||||
// ORDER BY created_at DESC
|
||||
// LIMIT count
|
||||
console.log(`Evicting ${count} newest notifications (LIFO)`);
|
||||
}
|
||||
|
||||
private async evictByPriority(count: number): Promise<void> {
|
||||
// Simulate: DELETE FROM notification_outbox
|
||||
// WHERE delivered_at IS NULL
|
||||
// ORDER BY priority ASC, created_at ASC
|
||||
// LIMIT count
|
||||
console.log(`Evicting ${count} lowest priority notifications`);
|
||||
}
|
||||
|
||||
async cleanupDeliveredNotifications(): Promise<void> {
|
||||
// Simulate: DELETE FROM notification_outbox
|
||||
// WHERE delivered_at IS NOT NULL
|
||||
// AND delivered_at < datetime('now', '-${cleanupIntervalMs / 1000} seconds')
|
||||
console.log(`Cleaning up delivered notifications older than ${this.config.cleanupIntervalMs}ms`);
|
||||
}
|
||||
|
||||
getMetrics(): TelemetryMetrics {
|
||||
return { ...this.metrics };
|
||||
}
|
||||
|
||||
getConfig(): OutboxPressureConfig {
|
||||
return { ...this.config };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create default outbox pressure manager
|
||||
*/
|
||||
export function createDefaultOutboxPressureManager(): OutboxPressureManager {
|
||||
return new OutboxPressureManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create outbox pressure manager with custom config
|
||||
*/
|
||||
export function createOutboxPressureManager(config: Partial<OutboxPressureConfig>): OutboxPressureManager {
|
||||
return new OutboxPressureManager(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outbox pressure configuration presets
|
||||
*/
|
||||
export const OUTBOX_PRESSURE_PRESETS = {
|
||||
// Conservative: Low memory usage, frequent cleanup
|
||||
conservative: {
|
||||
maxUndelivered: 500,
|
||||
backpressureThreshold: 0.7,
|
||||
cleanupIntervalMs: 1800000, // 30 minutes
|
||||
evictionPolicy: 'fifo' as const
|
||||
},
|
||||
|
||||
// Balanced: Good performance, reasonable memory usage
|
||||
balanced: {
|
||||
maxUndelivered: 1000,
|
||||
backpressureThreshold: 0.8,
|
||||
cleanupIntervalMs: 3600000, // 1 hour
|
||||
evictionPolicy: 'fifo' as const
|
||||
},
|
||||
|
||||
// Aggressive: High throughput, more memory usage
|
||||
aggressive: {
|
||||
maxUndelivered: 2000,
|
||||
backpressureThreshold: 0.9,
|
||||
cleanupIntervalMs: 7200000, // 2 hours
|
||||
evictionPolicy: 'priority' as const
|
||||
}
|
||||
} as const;
|
||||
Reference in New Issue
Block a user