@marktoflow/integrations 2.0.4-alpha.1 → 2.0.4
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 +2 -2
- package/dist/adapters/claude-code.d.ts +34 -0
- package/dist/adapters/claude-code.d.ts.map +1 -0
- package/dist/adapters/claude-code.js +89 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/github-copilot-types.d.ts +2 -2
- package/dist/adapters/github-copilot-workflow.d.ts +2 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/reliability/index.d.ts +1 -3
- package/dist/reliability/index.d.ts.map +1 -1
- package/dist/reliability/index.js +1 -3
- package/dist/reliability/index.js.map +1 -1
- package/dist/reliability/wrapper.d.ts +0 -9
- package/dist/reliability/wrapper.d.ts.map +1 -1
- package/dist/reliability/wrapper.js +12 -62
- package/dist/reliability/wrapper.js.map +1 -1
- package/dist/services/base-client.d.ts.map +1 -1
- package/dist/services/base-client.js +3 -25
- package/dist/services/base-client.js.map +1 -1
- package/dist/services/discord.d.ts.map +1 -1
- package/dist/services/discord.js +0 -6
- package/dist/services/discord.js.map +1 -1
- package/dist/services/gmail.d.ts.map +1 -1
- package/dist/services/gmail.js +47 -65
- package/dist/services/gmail.js.map +1 -1
- package/dist/services/google-calendar.js +5 -9
- package/dist/services/google-calendar.js.map +1 -1
- package/dist/services/google-docs.js +5 -9
- package/dist/services/google-docs.js.map +1 -1
- package/dist/services/google-drive.js +5 -9
- package/dist/services/google-drive.js.map +1 -1
- package/dist/services/google-sheets.js +5 -9
- package/dist/services/google-sheets.js.map +1 -1
- package/dist/services/http.d.ts.map +1 -1
- package/dist/services/http.js +1 -15
- package/dist/services/http.js.map +1 -1
- package/dist/services/mailchimp.d.ts.map +1 -1
- package/dist/services/mailchimp.js +0 -3
- package/dist/services/mailchimp.js.map +1 -1
- package/dist/services/outlook.d.ts.map +1 -1
- package/dist/services/outlook.js +11 -14
- package/dist/services/outlook.js.map +1 -1
- package/dist/services/shopify.d.ts.map +1 -1
- package/dist/services/shopify.js +0 -3
- package/dist/services/shopify.js.map +1 -1
- package/dist/services/trello.d.ts.map +1 -1
- package/dist/services/trello.js +1 -7
- package/dist/services/trello.js.map +1 -1
- package/package.json +3 -6
- package/dist/adapters/claude-agent-hooks.d.ts +0 -176
- package/dist/reliability/circuit-breaker.d.ts +0 -67
- package/dist/reliability/circuit-breaker.d.ts.map +0 -1
- package/dist/reliability/circuit-breaker.js +0 -164
- package/dist/reliability/circuit-breaker.js.map +0 -1
- package/dist/reliability/rate-limiter.d.ts +0 -62
- package/dist/reliability/rate-limiter.d.ts.map +0 -1
- package/dist/reliability/rate-limiter.js +0 -194
- package/dist/reliability/rate-limiter.js.map +0 -1
- package/dist/services/github.d.ts +0 -3
- package/dist/services/gmail-trigger.d.ts +0 -92
- package/dist/services/gmail.d.ts +0 -116
- package/dist/services/google-calendar.d.ts +0 -220
- package/dist/services/google-docs.d.ts +0 -197
- package/dist/services/google-drive.d.ts +0 -149
- package/dist/services/google-sheets.d.ts +0 -165
- package/dist/services/http.d.ts +0 -120
- package/dist/services/jira.d.ts +0 -3
- package/dist/services/linear.d.ts +0 -163
- package/dist/services/mysql.d.ts +0 -91
- package/dist/services/outlook-trigger.d.ts +0 -121
- package/dist/services/outlook.d.ts +0 -237
- package/dist/services/postgres.d.ts +0 -83
- package/dist/services/rss.d.ts +0 -57
- package/dist/services/rss.d.ts.map +0 -1
- package/dist/services/rss.js +0 -190
- package/dist/services/rss.js.map +0 -1
- package/dist/services/slack-socket.d.ts +0 -18
- package/dist/services/slack.d.ts +0 -3
- package/dist/services/whatsapp.d.ts +0 -311
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Agent SDK Hooks for marktoflow
|
|
3
|
-
*
|
|
4
|
-
* Provides pre-built hook configurations for common use cases:
|
|
5
|
-
* - Audit logging
|
|
6
|
-
* - Cost tracking
|
|
7
|
-
* - Approval workflows
|
|
8
|
-
* - File change monitoring
|
|
9
|
-
* - Security enforcement
|
|
10
|
-
*/
|
|
11
|
-
import { HookCallback, HookEvent, ToolPermissionHandler } from './claude-agent-types.js';
|
|
12
|
-
/**
|
|
13
|
-
* Audit log entry
|
|
14
|
-
*/
|
|
15
|
-
export interface AuditLogEntry {
|
|
16
|
-
timestamp: Date;
|
|
17
|
-
event: HookEvent;
|
|
18
|
-
toolName?: string;
|
|
19
|
-
toolInput?: Record<string, unknown>;
|
|
20
|
-
toolResponse?: string;
|
|
21
|
-
sessionId?: string;
|
|
22
|
-
error?: string;
|
|
23
|
-
duration?: number;
|
|
24
|
-
metadata?: Record<string, unknown>;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Audit logger interface
|
|
28
|
-
*/
|
|
29
|
-
export interface AuditLogger {
|
|
30
|
-
log(entry: AuditLogEntry): Promise<void>;
|
|
31
|
-
flush(): Promise<void>;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Console audit logger (default)
|
|
35
|
-
*/
|
|
36
|
-
export declare class ConsoleAuditLogger implements AuditLogger {
|
|
37
|
-
private prefix;
|
|
38
|
-
constructor(prefix?: string);
|
|
39
|
-
log(entry: AuditLogEntry): Promise<void>;
|
|
40
|
-
flush(): Promise<void>;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* File audit logger
|
|
44
|
-
*/
|
|
45
|
-
export declare class FileAuditLogger implements AuditLogger {
|
|
46
|
-
private filePath;
|
|
47
|
-
private buffer;
|
|
48
|
-
private bufferSize;
|
|
49
|
-
constructor(filePath: string, bufferSize?: number);
|
|
50
|
-
log(entry: AuditLogEntry): Promise<void>;
|
|
51
|
-
flush(): Promise<void>;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Create audit logging hooks
|
|
55
|
-
*/
|
|
56
|
-
export declare function createAuditHooks(logger?: AuditLogger): Partial<Record<HookEvent, HookCallback[]>>;
|
|
57
|
-
/**
|
|
58
|
-
* Cost tracker for monitoring spending
|
|
59
|
-
*/
|
|
60
|
-
export interface CostTracker {
|
|
61
|
-
/** Current total cost in USD */
|
|
62
|
-
totalCostUsd: number;
|
|
63
|
-
/** Cost per model */
|
|
64
|
-
costByModel: Record<string, number>;
|
|
65
|
-
/** Tool execution counts */
|
|
66
|
-
toolCounts: Record<string, number>;
|
|
67
|
-
/** Start time */
|
|
68
|
-
startTime: Date;
|
|
69
|
-
/** Number of API calls */
|
|
70
|
-
apiCalls: number;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Cost tracking callback
|
|
74
|
-
*/
|
|
75
|
-
export type CostCallback = (tracker: CostTracker) => void | Promise<void>;
|
|
76
|
-
/**
|
|
77
|
-
* Create a cost tracker
|
|
78
|
-
*/
|
|
79
|
-
export declare function createCostTracker(): CostTracker;
|
|
80
|
-
/**
|
|
81
|
-
* Create cost tracking hooks
|
|
82
|
-
*/
|
|
83
|
-
export declare function createCostTrackingHooks(tracker: CostTracker, callbacks?: {
|
|
84
|
-
onToolUse?: CostCallback;
|
|
85
|
-
onBudgetWarning?: CostCallback;
|
|
86
|
-
budgetWarningThreshold?: number;
|
|
87
|
-
}): Partial<Record<HookEvent, HookCallback[]>>;
|
|
88
|
-
/**
|
|
89
|
-
* Approval request
|
|
90
|
-
*/
|
|
91
|
-
export interface ApprovalRequest {
|
|
92
|
-
toolName: string;
|
|
93
|
-
toolInput: Record<string, unknown>;
|
|
94
|
-
reason?: string;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Approval handler
|
|
98
|
-
*/
|
|
99
|
-
export type ApprovalHandler = (request: ApprovalRequest) => Promise<boolean>;
|
|
100
|
-
/**
|
|
101
|
-
* Create approval workflow hooks
|
|
102
|
-
*/
|
|
103
|
-
export declare function createApprovalHooks(approvalHandler: ApprovalHandler, toolsRequiringApproval?: string[]): Partial<Record<HookEvent, HookCallback[]>>;
|
|
104
|
-
/**
|
|
105
|
-
* File change record
|
|
106
|
-
*/
|
|
107
|
-
export interface FileChange {
|
|
108
|
-
timestamp: Date;
|
|
109
|
-
operation: 'read' | 'write' | 'edit';
|
|
110
|
-
filePath: string;
|
|
111
|
-
toolInput?: Record<string, unknown>;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* File change callback
|
|
115
|
-
*/
|
|
116
|
-
export type FileChangeCallback = (change: FileChange) => void | Promise<void>;
|
|
117
|
-
/**
|
|
118
|
-
* Create file monitoring hooks
|
|
119
|
-
*/
|
|
120
|
-
export declare function createFileMonitoringHooks(callback: FileChangeCallback): Partial<Record<HookEvent, HookCallback[]>>;
|
|
121
|
-
/**
|
|
122
|
-
* Security policy for tool execution
|
|
123
|
-
*/
|
|
124
|
-
export interface SecurityPolicy {
|
|
125
|
-
/** Blocked file patterns (glob) */
|
|
126
|
-
blockedPaths?: string[];
|
|
127
|
-
/** Blocked commands (regex) */
|
|
128
|
-
blockedCommands?: string[];
|
|
129
|
-
/** Allowed working directories */
|
|
130
|
-
allowedDirectories?: string[];
|
|
131
|
-
/** Maximum file size for writes (bytes) */
|
|
132
|
-
maxFileSize?: number;
|
|
133
|
-
/** Block network access */
|
|
134
|
-
blockNetwork?: boolean;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Create a security-enforcing permission handler
|
|
138
|
-
*/
|
|
139
|
-
export declare function createSecurityPermissionHandler(policy: SecurityPolicy): ToolPermissionHandler;
|
|
140
|
-
/**
|
|
141
|
-
* Preset hook configurations for common use cases
|
|
142
|
-
*/
|
|
143
|
-
export declare const PresetHooks: {
|
|
144
|
-
/**
|
|
145
|
-
* Development mode - permissive with logging
|
|
146
|
-
*/
|
|
147
|
-
development: (logger?: AuditLogger) => {
|
|
148
|
-
PreToolUse?: HookCallback[] | undefined;
|
|
149
|
-
PostToolUse?: HookCallback[] | undefined;
|
|
150
|
-
PostToolUseFailure?: HookCallback[] | undefined;
|
|
151
|
-
PermissionRequest?: HookCallback[] | undefined;
|
|
152
|
-
SessionStart?: HookCallback[] | undefined;
|
|
153
|
-
SessionEnd?: HookCallback[] | undefined;
|
|
154
|
-
Stop?: HookCallback[] | undefined;
|
|
155
|
-
};
|
|
156
|
-
/**
|
|
157
|
-
* Production mode - strict with approval and monitoring
|
|
158
|
-
*/
|
|
159
|
-
production: (approvalHandler: ApprovalHandler, logger?: AuditLogger) => Partial<Record<HookEvent, HookCallback[]>>;
|
|
160
|
-
/**
|
|
161
|
-
* CI/CD mode - automated with cost tracking
|
|
162
|
-
*/
|
|
163
|
-
cicd: (costTracker: CostTracker, maxBudget?: number) => Partial<Record<HookEvent, HookCallback[]>>;
|
|
164
|
-
/**
|
|
165
|
-
* Secure mode - strict security with file monitoring
|
|
166
|
-
*/
|
|
167
|
-
secure: (policy: SecurityPolicy, fileChangeCallback?: FileChangeCallback) => {
|
|
168
|
-
hooks: Partial<Record<HookEvent, HookCallback[]>>;
|
|
169
|
-
canUseTool: ToolPermissionHandler;
|
|
170
|
-
};
|
|
171
|
-
};
|
|
172
|
-
/**
|
|
173
|
-
* Merge multiple hook configurations
|
|
174
|
-
*/
|
|
175
|
-
export declare function mergeHooks(...hookConfigs: Array<Partial<Record<HookEvent, HookCallback[]>>>): Partial<Record<HookEvent, HookCallback[]>>;
|
|
176
|
-
//# sourceMappingURL=claude-agent-hooks.d.ts.map
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Circuit Breaker for integration reliability.
|
|
3
|
-
*
|
|
4
|
-
* Prevents cascading failures by tracking error rates per service
|
|
5
|
-
* and short-circuiting requests when a service is unhealthy.
|
|
6
|
-
*
|
|
7
|
-
* States:
|
|
8
|
-
* - CLOSED: Normal operation, requests pass through
|
|
9
|
-
* - OPEN: Service is failing, requests are rejected immediately
|
|
10
|
-
* - HALF_OPEN: Testing if service has recovered (limited requests allowed)
|
|
11
|
-
*/
|
|
12
|
-
export type CircuitState = 'closed' | 'open' | 'half_open';
|
|
13
|
-
export interface CircuitBreakerOptions {
|
|
14
|
-
/** Number of failures before opening circuit (default: 5) */
|
|
15
|
-
failureThreshold?: number;
|
|
16
|
-
/** Time in ms before attempting recovery (default: 30000) */
|
|
17
|
-
resetTimeout?: number;
|
|
18
|
-
/** Number of successful requests in half-open to close circuit (default: 2) */
|
|
19
|
-
successThreshold?: number;
|
|
20
|
-
/** Time window in ms for counting failures (default: 60000) */
|
|
21
|
-
failureWindow?: number;
|
|
22
|
-
/** Called when circuit state changes */
|
|
23
|
-
onStateChange?: (service: string, from: CircuitState, to: CircuitState) => void;
|
|
24
|
-
}
|
|
25
|
-
export declare class CircuitBreakerRegistry {
|
|
26
|
-
private circuits;
|
|
27
|
-
private options;
|
|
28
|
-
private onStateChange?;
|
|
29
|
-
constructor(options?: CircuitBreakerOptions);
|
|
30
|
-
/**
|
|
31
|
-
* Check if a request to the given service should be allowed.
|
|
32
|
-
* Throws immediately if circuit is open.
|
|
33
|
-
*/
|
|
34
|
-
allowRequest(service: string): void;
|
|
35
|
-
/**
|
|
36
|
-
* Record a successful request.
|
|
37
|
-
*/
|
|
38
|
-
recordSuccess(service: string): void;
|
|
39
|
-
/**
|
|
40
|
-
* Record a failed request.
|
|
41
|
-
*/
|
|
42
|
-
recordFailure(service: string): void;
|
|
43
|
-
/**
|
|
44
|
-
* Get current state of a service's circuit.
|
|
45
|
-
*/
|
|
46
|
-
getState(service: string): CircuitState;
|
|
47
|
-
/**
|
|
48
|
-
* Get stats for all circuits.
|
|
49
|
-
*/
|
|
50
|
-
getStats(): Record<string, {
|
|
51
|
-
state: CircuitState;
|
|
52
|
-
recentFailures: number;
|
|
53
|
-
}>;
|
|
54
|
-
/**
|
|
55
|
-
* Reset a specific service's circuit to closed.
|
|
56
|
-
*/
|
|
57
|
-
reset(service: string): void;
|
|
58
|
-
/**
|
|
59
|
-
* Reset all circuits.
|
|
60
|
-
*/
|
|
61
|
-
resetAll(): void;
|
|
62
|
-
private getCircuit;
|
|
63
|
-
private transition;
|
|
64
|
-
}
|
|
65
|
-
export declare function getCircuitBreakerRegistry(options?: CircuitBreakerOptions): CircuitBreakerRegistry;
|
|
66
|
-
export declare function resetCircuitBreakerRegistry(): void;
|
|
67
|
-
//# sourceMappingURL=circuit-breaker.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/reliability/circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,MAAM,WAAW,qBAAqB;IACpC,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,KAAK,IAAI,CAAC;CACjF;AAiBD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,OAAO,CAAyD;IACxE,OAAO,CAAC,aAAa,CAAC,CAAyC;gBAEnD,OAAO,GAAE,qBAA0B;IAU/C;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IA4BnC;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAWpC;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAsBpC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY;IAIvC;;OAEG;IACH,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,YAAY,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC;IAc3E;;OAEG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5B;;OAEG;IACH,QAAQ,IAAI,IAAI;IAIhB,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,UAAU;CAgBnB;AAKD,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,sBAAsB,CAKjG;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAGlD"}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Circuit Breaker for integration reliability.
|
|
3
|
-
*
|
|
4
|
-
* Prevents cascading failures by tracking error rates per service
|
|
5
|
-
* and short-circuiting requests when a service is unhealthy.
|
|
6
|
-
*
|
|
7
|
-
* States:
|
|
8
|
-
* - CLOSED: Normal operation, requests pass through
|
|
9
|
-
* - OPEN: Service is failing, requests are rejected immediately
|
|
10
|
-
* - HALF_OPEN: Testing if service has recovered (limited requests allowed)
|
|
11
|
-
*/
|
|
12
|
-
import { IntegrationRequestError } from './errors.js';
|
|
13
|
-
const DEFAULT_OPTIONS = {
|
|
14
|
-
failureThreshold: 5,
|
|
15
|
-
resetTimeout: 30_000,
|
|
16
|
-
successThreshold: 2,
|
|
17
|
-
failureWindow: 60_000,
|
|
18
|
-
};
|
|
19
|
-
export class CircuitBreakerRegistry {
|
|
20
|
-
circuits = new Map();
|
|
21
|
-
options;
|
|
22
|
-
onStateChange;
|
|
23
|
-
constructor(options = {}) {
|
|
24
|
-
this.options = {
|
|
25
|
-
failureThreshold: options.failureThreshold ?? DEFAULT_OPTIONS.failureThreshold,
|
|
26
|
-
resetTimeout: options.resetTimeout ?? DEFAULT_OPTIONS.resetTimeout,
|
|
27
|
-
successThreshold: options.successThreshold ?? DEFAULT_OPTIONS.successThreshold,
|
|
28
|
-
failureWindow: options.failureWindow ?? DEFAULT_OPTIONS.failureWindow,
|
|
29
|
-
};
|
|
30
|
-
this.onStateChange = options.onStateChange;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Check if a request to the given service should be allowed.
|
|
34
|
-
* Throws immediately if circuit is open.
|
|
35
|
-
*/
|
|
36
|
-
allowRequest(service) {
|
|
37
|
-
const circuit = this.getCircuit(service);
|
|
38
|
-
const now = Date.now();
|
|
39
|
-
switch (circuit.state) {
|
|
40
|
-
case 'closed':
|
|
41
|
-
return; // Allow
|
|
42
|
-
case 'open': {
|
|
43
|
-
// Check if reset timeout has elapsed
|
|
44
|
-
if (now - circuit.openedAt >= this.options.resetTimeout) {
|
|
45
|
-
this.transition(service, circuit, 'half_open');
|
|
46
|
-
return; // Allow probe request
|
|
47
|
-
}
|
|
48
|
-
throw new IntegrationRequestError({
|
|
49
|
-
service,
|
|
50
|
-
action: 'circuit_breaker',
|
|
51
|
-
message: `Circuit breaker is open for ${service} — too many recent failures. Retry after ${Math.ceil((circuit.openedAt + this.options.resetTimeout - now) / 1000)}s`,
|
|
52
|
-
retryable: true,
|
|
53
|
-
retryAfter: Math.ceil((circuit.openedAt + this.options.resetTimeout - now) / 1000),
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
case 'half_open':
|
|
57
|
-
return; // Allow probe request
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Record a successful request.
|
|
62
|
-
*/
|
|
63
|
-
recordSuccess(service) {
|
|
64
|
-
const circuit = this.getCircuit(service);
|
|
65
|
-
if (circuit.state === 'half_open') {
|
|
66
|
-
circuit.successes++;
|
|
67
|
-
if (circuit.successes >= this.options.successThreshold) {
|
|
68
|
-
this.transition(service, circuit, 'closed');
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Record a failed request.
|
|
74
|
-
*/
|
|
75
|
-
recordFailure(service) {
|
|
76
|
-
const circuit = this.getCircuit(service);
|
|
77
|
-
const now = Date.now();
|
|
78
|
-
if (circuit.state === 'half_open') {
|
|
79
|
-
// Any failure in half-open reopens the circuit
|
|
80
|
-
this.transition(service, circuit, 'open');
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
// Add failure timestamp and prune old ones
|
|
84
|
-
circuit.failures.push(now);
|
|
85
|
-
circuit.failures = circuit.failures.filter((t) => now - t < this.options.failureWindow);
|
|
86
|
-
circuit.lastFailureAt = now;
|
|
87
|
-
if (circuit.failures.length >= this.options.failureThreshold) {
|
|
88
|
-
this.transition(service, circuit, 'open');
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Get current state of a service's circuit.
|
|
93
|
-
*/
|
|
94
|
-
getState(service) {
|
|
95
|
-
return this.getCircuit(service).state;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Get stats for all circuits.
|
|
99
|
-
*/
|
|
100
|
-
getStats() {
|
|
101
|
-
const stats = {};
|
|
102
|
-
const now = Date.now();
|
|
103
|
-
for (const [service, circuit] of this.circuits) {
|
|
104
|
-
const recentFailures = circuit.failures.filter((t) => now - t < this.options.failureWindow).length;
|
|
105
|
-
stats[service] = { state: circuit.state, recentFailures };
|
|
106
|
-
}
|
|
107
|
-
return stats;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Reset a specific service's circuit to closed.
|
|
111
|
-
*/
|
|
112
|
-
reset(service) {
|
|
113
|
-
this.circuits.delete(service);
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Reset all circuits.
|
|
117
|
-
*/
|
|
118
|
-
resetAll() {
|
|
119
|
-
this.circuits.clear();
|
|
120
|
-
}
|
|
121
|
-
getCircuit(service) {
|
|
122
|
-
let circuit = this.circuits.get(service);
|
|
123
|
-
if (!circuit) {
|
|
124
|
-
circuit = {
|
|
125
|
-
state: 'closed',
|
|
126
|
-
failures: [],
|
|
127
|
-
successes: 0,
|
|
128
|
-
lastFailureAt: 0,
|
|
129
|
-
openedAt: 0,
|
|
130
|
-
};
|
|
131
|
-
this.circuits.set(service, circuit);
|
|
132
|
-
}
|
|
133
|
-
return circuit;
|
|
134
|
-
}
|
|
135
|
-
transition(service, circuit, to) {
|
|
136
|
-
const from = circuit.state;
|
|
137
|
-
circuit.state = to;
|
|
138
|
-
if (to === 'open') {
|
|
139
|
-
circuit.openedAt = Date.now();
|
|
140
|
-
circuit.successes = 0;
|
|
141
|
-
}
|
|
142
|
-
else if (to === 'closed') {
|
|
143
|
-
circuit.failures = [];
|
|
144
|
-
circuit.successes = 0;
|
|
145
|
-
}
|
|
146
|
-
else if (to === 'half_open') {
|
|
147
|
-
circuit.successes = 0;
|
|
148
|
-
}
|
|
149
|
-
this.onStateChange?.(service, from, to);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
/** Global circuit breaker registry shared across all integrations */
|
|
153
|
-
let _globalRegistry;
|
|
154
|
-
export function getCircuitBreakerRegistry(options) {
|
|
155
|
-
if (!_globalRegistry) {
|
|
156
|
-
_globalRegistry = new CircuitBreakerRegistry(options);
|
|
157
|
-
}
|
|
158
|
-
return _globalRegistry;
|
|
159
|
-
}
|
|
160
|
-
export function resetCircuitBreakerRegistry() {
|
|
161
|
-
_globalRegistry?.resetAll();
|
|
162
|
-
_globalRegistry = undefined;
|
|
163
|
-
}
|
|
164
|
-
//# sourceMappingURL=circuit-breaker.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../src/reliability/circuit-breaker.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Proactive rate limiter for integration reliability.
|
|
3
|
-
*
|
|
4
|
-
* Prevents 429 errors by tracking request rates per service
|
|
5
|
-
* and queuing/delaying requests that would exceed known limits.
|
|
6
|
-
*
|
|
7
|
-
* Uses a token bucket algorithm with configurable refill rates.
|
|
8
|
-
*/
|
|
9
|
-
export interface RateLimitConfig {
|
|
10
|
-
/** Maximum requests per window */
|
|
11
|
-
maxRequests: number;
|
|
12
|
-
/** Window duration in ms */
|
|
13
|
-
windowMs: number;
|
|
14
|
-
/** Strategy when limit is reached: 'queue' waits, 'reject' throws (default: 'queue') */
|
|
15
|
-
strategy?: 'queue' | 'reject';
|
|
16
|
-
/** Maximum queue size before rejecting (default: 100) */
|
|
17
|
-
maxQueueSize?: number;
|
|
18
|
-
}
|
|
19
|
-
/** Well-known rate limits for popular services */
|
|
20
|
-
export declare const KNOWN_RATE_LIMITS: Record<string, RateLimitConfig>;
|
|
21
|
-
export declare class RateLimiterRegistry {
|
|
22
|
-
private buckets;
|
|
23
|
-
private configs;
|
|
24
|
-
private timers;
|
|
25
|
-
constructor(overrides?: Record<string, RateLimitConfig>);
|
|
26
|
-
/**
|
|
27
|
-
* Configure rate limit for a specific service.
|
|
28
|
-
*/
|
|
29
|
-
configure(service: string, config: RateLimitConfig): void;
|
|
30
|
-
/**
|
|
31
|
-
* Acquire a token for the given service.
|
|
32
|
-
* Resolves immediately if tokens available, queues or rejects otherwise.
|
|
33
|
-
*/
|
|
34
|
-
acquire(service: string): Promise<void>;
|
|
35
|
-
/**
|
|
36
|
-
* Release a token (optional — for manual flow control).
|
|
37
|
-
*/
|
|
38
|
-
release(service: string): void;
|
|
39
|
-
/**
|
|
40
|
-
* Get current rate limit status for a service.
|
|
41
|
-
*/
|
|
42
|
-
getStatus(service: string): {
|
|
43
|
-
available: number;
|
|
44
|
-
max: number;
|
|
45
|
-
queued: number;
|
|
46
|
-
refillRate: number;
|
|
47
|
-
} | null;
|
|
48
|
-
/**
|
|
49
|
-
* Update limits based on response headers (Retry-After, X-RateLimit-*).
|
|
50
|
-
*/
|
|
51
|
-
updateFromHeaders(service: string, headers: Record<string, string>): void;
|
|
52
|
-
/**
|
|
53
|
-
* Clean up timers.
|
|
54
|
-
*/
|
|
55
|
-
destroy(): void;
|
|
56
|
-
private refill;
|
|
57
|
-
private drainQueue;
|
|
58
|
-
private ensureDrainTimer;
|
|
59
|
-
}
|
|
60
|
-
export declare function getRateLimiterRegistry(overrides?: Record<string, RateLimitConfig>): RateLimiterRegistry;
|
|
61
|
-
export declare function resetRateLimiterRegistry(): void;
|
|
62
|
-
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/reliability/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC9B,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,kDAAkD;AAClD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAW7D,CAAC;AAUF,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,OAAO,CAAgD;IAC/D,OAAO,CAAC,MAAM,CAAqD;gBAEvD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC;IAYvD;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI;IAmBzD;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuC7C;;OAEG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO9B;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG;QAC1B,SAAS,EAAE,MAAM,CAAC;QAClB,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,IAAI;IAYR;;OAEG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI;IAazE;;OAEG;IACH,OAAO,IAAI,IAAI;IAef,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,gBAAgB;CAiBzB;AAKD,wBAAgB,sBAAsB,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,mBAAmB,CAKvG;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C"}
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Proactive rate limiter for integration reliability.
|
|
3
|
-
*
|
|
4
|
-
* Prevents 429 errors by tracking request rates per service
|
|
5
|
-
* and queuing/delaying requests that would exceed known limits.
|
|
6
|
-
*
|
|
7
|
-
* Uses a token bucket algorithm with configurable refill rates.
|
|
8
|
-
*/
|
|
9
|
-
import { IntegrationRequestError } from './errors.js';
|
|
10
|
-
/** Well-known rate limits for popular services */
|
|
11
|
-
export const KNOWN_RATE_LIMITS = {
|
|
12
|
-
slack: { maxRequests: 50, windowMs: 60_000 },
|
|
13
|
-
github: { maxRequests: 5000, windowMs: 3_600_000 },
|
|
14
|
-
gmail: { maxRequests: 250, windowMs: 1_000 },
|
|
15
|
-
discord: { maxRequests: 50, windowMs: 1_000 },
|
|
16
|
-
notion: { maxRequests: 3, windowMs: 1_000 },
|
|
17
|
-
linear: { maxRequests: 50, windowMs: 60_000 },
|
|
18
|
-
stripe: { maxRequests: 100, windowMs: 1_000 },
|
|
19
|
-
sendgrid: { maxRequests: 600, windowMs: 60_000 },
|
|
20
|
-
trello: { maxRequests: 100, windowMs: 10_000 },
|
|
21
|
-
shopify: { maxRequests: 40, windowMs: 1_000 },
|
|
22
|
-
};
|
|
23
|
-
export class RateLimiterRegistry {
|
|
24
|
-
buckets = new Map();
|
|
25
|
-
configs = new Map();
|
|
26
|
-
timers = new Map();
|
|
27
|
-
constructor(overrides) {
|
|
28
|
-
// Load known defaults, allow overrides
|
|
29
|
-
for (const [service, config] of Object.entries(KNOWN_RATE_LIMITS)) {
|
|
30
|
-
this.configure(service, config);
|
|
31
|
-
}
|
|
32
|
-
if (overrides) {
|
|
33
|
-
for (const [service, config] of Object.entries(overrides)) {
|
|
34
|
-
this.configure(service, config);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Configure rate limit for a specific service.
|
|
40
|
-
*/
|
|
41
|
-
configure(service, config) {
|
|
42
|
-
const full = {
|
|
43
|
-
maxRequests: config.maxRequests,
|
|
44
|
-
windowMs: config.windowMs,
|
|
45
|
-
strategy: config.strategy ?? 'queue',
|
|
46
|
-
maxQueueSize: config.maxQueueSize ?? 100,
|
|
47
|
-
};
|
|
48
|
-
this.configs.set(service, full);
|
|
49
|
-
const refillRate = config.maxRequests / config.windowMs;
|
|
50
|
-
this.buckets.set(service, {
|
|
51
|
-
tokens: config.maxRequests,
|
|
52
|
-
maxTokens: config.maxRequests,
|
|
53
|
-
refillRate,
|
|
54
|
-
lastRefillAt: Date.now(),
|
|
55
|
-
queue: [],
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Acquire a token for the given service.
|
|
60
|
-
* Resolves immediately if tokens available, queues or rejects otherwise.
|
|
61
|
-
*/
|
|
62
|
-
async acquire(service) {
|
|
63
|
-
const bucket = this.buckets.get(service);
|
|
64
|
-
if (!bucket)
|
|
65
|
-
return; // No rate limit configured — allow
|
|
66
|
-
this.refill(bucket);
|
|
67
|
-
if (bucket.tokens >= 1) {
|
|
68
|
-
bucket.tokens -= 1;
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const config = this.configs.get(service);
|
|
72
|
-
if (config.strategy === 'reject') {
|
|
73
|
-
throw new IntegrationRequestError({
|
|
74
|
-
service,
|
|
75
|
-
action: 'rate_limiter',
|
|
76
|
-
message: `Rate limit reached for ${service} (${config.maxRequests} requests per ${config.windowMs}ms)`,
|
|
77
|
-
retryable: true,
|
|
78
|
-
retryAfter: Math.ceil((1 / bucket.refillRate) / 1000),
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
// Queue strategy — wait for a token
|
|
82
|
-
if (bucket.queue.length >= config.maxQueueSize) {
|
|
83
|
-
throw new IntegrationRequestError({
|
|
84
|
-
service,
|
|
85
|
-
action: 'rate_limiter',
|
|
86
|
-
message: `Rate limit queue full for ${service} (${config.maxQueueSize} pending requests)`,
|
|
87
|
-
retryable: true,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return new Promise((resolve, reject) => {
|
|
91
|
-
bucket.queue.push({ resolve, reject });
|
|
92
|
-
this.ensureDrainTimer(service, bucket);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Release a token (optional — for manual flow control).
|
|
97
|
-
*/
|
|
98
|
-
release(service) {
|
|
99
|
-
const bucket = this.buckets.get(service);
|
|
100
|
-
if (!bucket)
|
|
101
|
-
return;
|
|
102
|
-
bucket.tokens = Math.min(bucket.tokens + 1, bucket.maxTokens);
|
|
103
|
-
this.drainQueue(bucket);
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Get current rate limit status for a service.
|
|
107
|
-
*/
|
|
108
|
-
getStatus(service) {
|
|
109
|
-
const bucket = this.buckets.get(service);
|
|
110
|
-
if (!bucket)
|
|
111
|
-
return null;
|
|
112
|
-
this.refill(bucket);
|
|
113
|
-
return {
|
|
114
|
-
available: Math.floor(bucket.tokens),
|
|
115
|
-
max: bucket.maxTokens,
|
|
116
|
-
queued: bucket.queue.length,
|
|
117
|
-
refillRate: bucket.refillRate * 1000, // tokens per second
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Update limits based on response headers (Retry-After, X-RateLimit-*).
|
|
122
|
-
*/
|
|
123
|
-
updateFromHeaders(service, headers) {
|
|
124
|
-
const remaining = headers['x-ratelimit-remaining'] ?? headers['X-RateLimit-Remaining'];
|
|
125
|
-
if (remaining !== undefined) {
|
|
126
|
-
const bucket = this.buckets.get(service);
|
|
127
|
-
if (bucket) {
|
|
128
|
-
const count = parseInt(remaining, 10);
|
|
129
|
-
if (!isNaN(count)) {
|
|
130
|
-
bucket.tokens = Math.min(count, bucket.maxTokens);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Clean up timers.
|
|
137
|
-
*/
|
|
138
|
-
destroy() {
|
|
139
|
-
for (const timer of this.timers.values()) {
|
|
140
|
-
clearInterval(timer);
|
|
141
|
-
}
|
|
142
|
-
this.timers.clear();
|
|
143
|
-
// Reject all queued requests
|
|
144
|
-
for (const bucket of this.buckets.values()) {
|
|
145
|
-
for (const waiter of bucket.queue) {
|
|
146
|
-
waiter.reject(new Error('Rate limiter destroyed'));
|
|
147
|
-
}
|
|
148
|
-
bucket.queue = [];
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
refill(bucket) {
|
|
152
|
-
const now = Date.now();
|
|
153
|
-
const elapsed = now - bucket.lastRefillAt;
|
|
154
|
-
const tokensToAdd = elapsed * bucket.refillRate;
|
|
155
|
-
bucket.tokens = Math.min(bucket.tokens + tokensToAdd, bucket.maxTokens);
|
|
156
|
-
bucket.lastRefillAt = now;
|
|
157
|
-
}
|
|
158
|
-
drainQueue(bucket) {
|
|
159
|
-
while (bucket.queue.length > 0 && bucket.tokens >= 1) {
|
|
160
|
-
bucket.tokens -= 1;
|
|
161
|
-
const waiter = bucket.queue.shift();
|
|
162
|
-
waiter.resolve();
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
ensureDrainTimer(service, bucket) {
|
|
166
|
-
if (this.timers.has(service))
|
|
167
|
-
return;
|
|
168
|
-
const intervalMs = Math.max(10, Math.ceil(1 / bucket.refillRate));
|
|
169
|
-
const timer = setInterval(() => {
|
|
170
|
-
this.refill(bucket);
|
|
171
|
-
this.drainQueue(bucket);
|
|
172
|
-
if (bucket.queue.length === 0) {
|
|
173
|
-
clearInterval(timer);
|
|
174
|
-
this.timers.delete(service);
|
|
175
|
-
}
|
|
176
|
-
}, intervalMs);
|
|
177
|
-
if (timer.unref)
|
|
178
|
-
timer.unref();
|
|
179
|
-
this.timers.set(service, timer);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
/** Global rate limiter registry */
|
|
183
|
-
let _globalLimiter;
|
|
184
|
-
export function getRateLimiterRegistry(overrides) {
|
|
185
|
-
if (!_globalLimiter) {
|
|
186
|
-
_globalLimiter = new RateLimiterRegistry(overrides);
|
|
187
|
-
}
|
|
188
|
-
return _globalLimiter;
|
|
189
|
-
}
|
|
190
|
-
export function resetRateLimiterRegistry() {
|
|
191
|
-
_globalLimiter?.destroy();
|
|
192
|
-
_globalLimiter = undefined;
|
|
193
|
-
}
|
|
194
|
-
//# sourceMappingURL=rate-limiter.js.map
|