@aiassesstech/nole 0.4.15 → 0.6.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.
Files changed (59) hide show
  1. package/agent/AGENTS.md +18 -0
  2. package/dist/intel/intel-store.d.ts +62 -0
  3. package/dist/intel/intel-store.d.ts.map +1 -0
  4. package/dist/intel/intel-store.js +242 -0
  5. package/dist/intel/intel-store.js.map +1 -0
  6. package/dist/intel/provenance.d.ts +16 -0
  7. package/dist/intel/provenance.d.ts.map +1 -0
  8. package/dist/intel/provenance.js +23 -0
  9. package/dist/intel/provenance.js.map +1 -0
  10. package/dist/intel/sensitivity-scanner.d.ts +25 -0
  11. package/dist/intel/sensitivity-scanner.d.ts.map +1 -0
  12. package/dist/intel/sensitivity-scanner.js +37 -0
  13. package/dist/intel/sensitivity-scanner.js.map +1 -0
  14. package/dist/pipeline/prospect-store.js +2 -2
  15. package/dist/pipeline/prospect-store.js.map +1 -1
  16. package/dist/plugin.d.ts.map +1 -1
  17. package/dist/plugin.js +53 -30
  18. package/dist/plugin.js.map +1 -1
  19. package/dist/resilience/circuit-breaker.d.ts +46 -0
  20. package/dist/resilience/circuit-breaker.d.ts.map +1 -0
  21. package/dist/resilience/circuit-breaker.js +102 -0
  22. package/dist/resilience/circuit-breaker.js.map +1 -0
  23. package/dist/resilience/index.d.ts +8 -0
  24. package/dist/resilience/index.d.ts.map +1 -0
  25. package/dist/resilience/index.js +8 -0
  26. package/dist/resilience/index.js.map +1 -0
  27. package/dist/resilience/post-queue.d.ts +41 -0
  28. package/dist/resilience/post-queue.d.ts.map +1 -0
  29. package/dist/resilience/post-queue.js +106 -0
  30. package/dist/resilience/post-queue.js.map +1 -0
  31. package/dist/resilience/queue-drain.d.ts +35 -0
  32. package/dist/resilience/queue-drain.d.ts.map +1 -0
  33. package/dist/resilience/queue-drain.js +92 -0
  34. package/dist/resilience/queue-drain.js.map +1 -0
  35. package/dist/resilience/resilient-client.d.ts +54 -0
  36. package/dist/resilience/resilient-client.d.ts.map +1 -0
  37. package/dist/resilience/resilient-client.js +120 -0
  38. package/dist/resilience/resilient-client.js.map +1 -0
  39. package/dist/resilience/retry.d.ts +22 -0
  40. package/dist/resilience/retry.d.ts.map +1 -0
  41. package/dist/resilience/retry.js +68 -0
  42. package/dist/resilience/retry.js.map +1 -0
  43. package/dist/resilience/service-configs.d.ts +14 -0
  44. package/dist/resilience/service-configs.d.ts.map +1 -0
  45. package/dist/resilience/service-configs.js +45 -0
  46. package/dist/resilience/service-configs.js.map +1 -0
  47. package/dist/resilience/service-registry.d.ts +39 -0
  48. package/dist/resilience/service-registry.d.ts.map +1 -0
  49. package/dist/resilience/service-registry.js +82 -0
  50. package/dist/resilience/service-registry.js.map +1 -0
  51. package/dist/store/json-store.d.ts.map +1 -1
  52. package/dist/store/json-store.js +6 -7
  53. package/dist/store/json-store.js.map +1 -1
  54. package/dist/tools/nole-intel.d.ts +53 -0
  55. package/dist/tools/nole-intel.d.ts.map +1 -0
  56. package/dist/tools/nole-intel.js +178 -0
  57. package/dist/tools/nole-intel.js.map +1 -0
  58. package/openclaw.plugin.json +15 -7
  59. package/package.json +3 -3
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Circuit Breaker — Three-state pattern for external service protection.
3
+ *
4
+ * CLOSED → requests pass through, failures counted
5
+ * OPEN → requests rejected immediately, cooldown timer running
6
+ * HALF-OPEN → single probe request allowed, success closes, failure reopens
7
+ *
8
+ * @see SPEC-nole-operational-resilience.md §3
9
+ */
10
+ export class CircuitBreaker {
11
+ config;
12
+ state = 'closed';
13
+ failures = [];
14
+ lastStateChange = Date.now();
15
+ currentCooldownMs;
16
+ consecutiveOpenings = 0;
17
+ probeAllowed = false;
18
+ constructor(config) {
19
+ this.config = config;
20
+ this.currentCooldownMs = config.cooldownMs;
21
+ }
22
+ canExecute() {
23
+ this.pruneOldFailures();
24
+ switch (this.state) {
25
+ case 'closed':
26
+ return true;
27
+ case 'open': {
28
+ const elapsed = Date.now() - this.lastStateChange;
29
+ if (elapsed >= this.currentCooldownMs) {
30
+ this.transition('half-open');
31
+ this.probeAllowed = false;
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ case 'half-open':
37
+ return false;
38
+ }
39
+ }
40
+ recordSuccess() {
41
+ if (this.state === 'half-open') {
42
+ this.consecutiveOpenings = 0;
43
+ this.currentCooldownMs = this.config.cooldownMs;
44
+ this.transition('closed');
45
+ this.failures = [];
46
+ }
47
+ }
48
+ recordFailure(error) {
49
+ const ts = Date.now();
50
+ switch (this.state) {
51
+ case 'closed':
52
+ this.failures.push({ timestamp: ts, error });
53
+ this.pruneOldFailures();
54
+ if (this.failures.length >= this.config.failureThreshold) {
55
+ this.consecutiveOpenings++;
56
+ this.currentCooldownMs = Math.min(this.config.cooldownMs * Math.pow(this.config.cooldownMultiplier, this.consecutiveOpenings - 1), this.config.maxCooldownMs);
57
+ this.transition('open');
58
+ }
59
+ break;
60
+ case 'half-open':
61
+ this.consecutiveOpenings++;
62
+ this.currentCooldownMs = Math.min(this.currentCooldownMs * this.config.cooldownMultiplier, this.config.maxCooldownMs);
63
+ this.transition('open');
64
+ break;
65
+ }
66
+ }
67
+ isFailureStatus(status) {
68
+ return this.config.failureStatusCodes.includes(status);
69
+ }
70
+ getState() {
71
+ if (this.state === 'open') {
72
+ const elapsed = Date.now() - this.lastStateChange;
73
+ if (elapsed >= this.currentCooldownMs) {
74
+ this.transition('half-open');
75
+ }
76
+ }
77
+ return this.state;
78
+ }
79
+ getStatus() {
80
+ this.pruneOldFailures();
81
+ return {
82
+ state: this.getState(),
83
+ failureCount: this.failures.length,
84
+ consecutiveOpenings: this.consecutiveOpenings,
85
+ currentCooldownMs: this.currentCooldownMs,
86
+ lastStateChange: this.lastStateChange,
87
+ recentFailures: this.failures.slice(-3).map(f => f.error),
88
+ };
89
+ }
90
+ transition(to) {
91
+ const from = this.state;
92
+ this.state = to;
93
+ this.lastStateChange = Date.now();
94
+ this.probeAllowed = false;
95
+ this.config.onStateChange?.(from, to, this.config.name);
96
+ }
97
+ pruneOldFailures() {
98
+ const cutoff = Date.now() - this.config.failureWindowMs;
99
+ this.failures = this.failures.filter(f => f.timestamp > cutoff);
100
+ }
101
+ }
102
+ //# sourceMappingURL=circuit-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../src/resilience/circuit-breaker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { CircuitBreaker, type CircuitBreakerConfig, type CircuitState } from './circuit-breaker.js';
2
+ export { type RetryConfig, DEFAULT_RETRY_CONFIG, calculateDelay, isRetryable, extractRetryAfter, sleep, } from './retry.js';
3
+ export { ResilientClient, CircuitOpenError, type ResilientClientConfig, type ResilientResponse, } from './resilient-client.js';
4
+ export { ServiceRegistry, type ServiceHealth, type ServiceStatus, type RegistrySnapshot, } from './service-registry.js';
5
+ export { PostQueue, type QueuedPost, type PostPlatform, type PostQueueConfig, DEFAULT_QUEUE_CONFIG, } from './post-queue.js';
6
+ export { QueueDrain, type PostSender, type QueueDrainConfig, DEFAULT_DRAIN_CONFIG, } from './queue-drain.js';
7
+ export { PLATFORM_API_BREAKER, X_API_BREAKER, MOLTBOOK_API_BREAKER, BLOCKCHAIN_BREAKER, } from './service-configs.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resilience/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpG,OAAO,EACL,KAAK,WAAW,EAChB,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,KAAK,GACN,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,GACvB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,eAAe,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,SAAS,EACT,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,EACV,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { CircuitBreaker } from './circuit-breaker.js';
2
+ export { DEFAULT_RETRY_CONFIG, calculateDelay, isRetryable, extractRetryAfter, sleep, } from './retry.js';
3
+ export { ResilientClient, CircuitOpenError, } from './resilient-client.js';
4
+ export { ServiceRegistry, } from './service-registry.js';
5
+ export { PostQueue, DEFAULT_QUEUE_CONFIG, } from './post-queue.js';
6
+ export { QueueDrain, DEFAULT_DRAIN_CONFIG, } from './queue-drain.js';
7
+ export { PLATFORM_API_BREAKER, X_API_BREAKER, MOLTBOOK_API_BREAKER, BLOCKCHAIN_BREAKER, } from './service-configs.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resilience/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAgD,MAAM,sBAAsB,CAAC;AAEpG,OAAO,EAEL,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,KAAK,GACN,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,eAAe,EACf,gBAAgB,GAGjB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,eAAe,GAIhB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,SAAS,EAIT,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,EAGV,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * PostQueue — Durable queue for social media posts during outages.
3
+ *
4
+ * When X/Twitter or MoltBook circuit is open, posts are queued to
5
+ * a JSON file and drained when the service recovers.
6
+ *
7
+ * @see SPEC-nole-operational-resilience.md §7
8
+ */
9
+ export type PostPlatform = 'x-twitter' | 'moltbook';
10
+ export interface QueuedPost {
11
+ id: string;
12
+ platform: PostPlatform;
13
+ content: string;
14
+ metadata: Record<string, unknown>;
15
+ queuedAt: number;
16
+ retries: number;
17
+ lastRetryAt: number | null;
18
+ error: string | null;
19
+ }
20
+ export interface PostQueueConfig {
21
+ dataDir: string;
22
+ maxQueueSize: number;
23
+ maxRetries: number;
24
+ maxAgeMs: number;
25
+ }
26
+ export declare const DEFAULT_QUEUE_CONFIG: PostQueueConfig;
27
+ export declare class PostQueue {
28
+ private config;
29
+ private queue;
30
+ private filePath;
31
+ constructor(config: PostQueueConfig);
32
+ enqueue(platform: PostPlatform, content: string, metadata?: Record<string, unknown>): QueuedPost;
33
+ dequeue(platform: PostPlatform): QueuedPost | null;
34
+ requeue(post: QueuedPost, error: string): void;
35
+ peek(platform: PostPlatform): QueuedPost[];
36
+ size(platform?: PostPlatform): number;
37
+ private pruneExpired;
38
+ private load;
39
+ private save;
40
+ }
41
+ //# sourceMappingURL=post-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-queue.d.ts","sourceRoot":"","sources":["../../src/resilience/post-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,UAAU,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,oBAAoB,EAAE,eAKlC,CAAC;AAEF,qBAAa,SAAS;IAIR,OAAO,CAAC,MAAM;IAH1B,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,QAAQ,CAAS;gBAEL,MAAM,EAAE,eAAe;IAK3C,OAAO,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,UAAU;IAuBpG,OAAO,CAAC,QAAQ,EAAE,YAAY,GAAG,UAAU,GAAG,IAAI;IAclD,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAY9C,IAAI,CAAC,QAAQ,EAAE,YAAY,GAAG,UAAU,EAAE;IAK1C,IAAI,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,MAAM;IAQrC,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,IAAI;CAWb"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * PostQueue — Durable queue for social media posts during outages.
3
+ *
4
+ * When X/Twitter or MoltBook circuit is open, posts are queued to
5
+ * a JSON file and drained when the service recovers.
6
+ *
7
+ * @see SPEC-nole-operational-resilience.md §7
8
+ */
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ export const DEFAULT_QUEUE_CONFIG = {
12
+ dataDir: '',
13
+ maxQueueSize: 100,
14
+ maxRetries: 5,
15
+ maxAgeMs: 24 * 60 * 60 * 1000, // 24h
16
+ };
17
+ export class PostQueue {
18
+ config;
19
+ queue = [];
20
+ filePath;
21
+ constructor(config) {
22
+ this.config = config;
23
+ this.filePath = path.join(config.dataDir, 'post-queue.json');
24
+ this.load();
25
+ }
26
+ enqueue(platform, content, metadata = {}) {
27
+ this.pruneExpired();
28
+ if (this.queue.length >= this.config.maxQueueSize) {
29
+ throw new Error(`Post queue full (${this.config.maxQueueSize} items) — cannot enqueue`);
30
+ }
31
+ const post = {
32
+ id: `post_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
33
+ platform,
34
+ content,
35
+ metadata,
36
+ queuedAt: Date.now(),
37
+ retries: 0,
38
+ lastRetryAt: null,
39
+ error: null,
40
+ };
41
+ this.queue.push(post);
42
+ this.save();
43
+ return post;
44
+ }
45
+ dequeue(platform) {
46
+ this.pruneExpired();
47
+ const idx = this.queue.findIndex(p => p.platform === platform && p.retries < this.config.maxRetries);
48
+ if (idx === -1)
49
+ return null;
50
+ const [post] = this.queue.splice(idx, 1);
51
+ this.save();
52
+ return post;
53
+ }
54
+ requeue(post, error) {
55
+ post.retries++;
56
+ post.lastRetryAt = Date.now();
57
+ post.error = error;
58
+ if (post.retries < this.config.maxRetries) {
59
+ this.queue.push(post);
60
+ }
61
+ this.save();
62
+ }
63
+ peek(platform) {
64
+ this.pruneExpired();
65
+ return this.queue.filter(p => p.platform === platform);
66
+ }
67
+ size(platform) {
68
+ this.pruneExpired();
69
+ if (platform) {
70
+ return this.queue.filter(p => p.platform === platform).length;
71
+ }
72
+ return this.queue.length;
73
+ }
74
+ pruneExpired() {
75
+ const cutoff = Date.now() - this.config.maxAgeMs;
76
+ const before = this.queue.length;
77
+ this.queue = this.queue.filter(p => p.queuedAt > cutoff && p.retries < this.config.maxRetries);
78
+ if (this.queue.length !== before) {
79
+ this.save();
80
+ }
81
+ }
82
+ load() {
83
+ try {
84
+ if (fs.existsSync(this.filePath)) {
85
+ const data = fs.readFileSync(this.filePath, 'utf-8');
86
+ this.queue = JSON.parse(data);
87
+ }
88
+ }
89
+ catch {
90
+ this.queue = [];
91
+ }
92
+ }
93
+ save() {
94
+ try {
95
+ const dir = path.dirname(this.filePath);
96
+ if (!fs.existsSync(dir)) {
97
+ fs.mkdirSync(dir, { recursive: true });
98
+ }
99
+ fs.writeFileSync(this.filePath, JSON.stringify(this.queue, null, 2), 'utf-8');
100
+ }
101
+ catch {
102
+ // Non-fatal — queue is best-effort durable
103
+ }
104
+ }
105
+ }
106
+ //# sourceMappingURL=post-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-queue.js","sourceRoot":"","sources":["../../src/resilience/post-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAsB7B,MAAM,CAAC,MAAM,oBAAoB,GAAoB;IACnD,OAAO,EAAE,EAAE;IACX,YAAY,EAAE,GAAG;IACjB,UAAU,EAAE,CAAC;IACb,QAAQ,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM;CACtC,CAAC;AAEF,MAAM,OAAO,SAAS;IAIA;IAHZ,KAAK,GAAiB,EAAE,CAAC;IACzB,QAAQ,CAAS;IAEzB,YAAoB,MAAuB;QAAvB,WAAM,GAAN,MAAM,CAAiB;QACzC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;QAC7D,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,OAAO,CAAC,QAAsB,EAAE,OAAe,EAAE,WAAoC,EAAE;QACrF,IAAI,CAAC,YAAY,EAAE,CAAC;QAEpB,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,CAAC,MAAM,CAAC,YAAY,0BAA0B,CAAC,CAAC;QAC1F,CAAC;QAED,MAAM,IAAI,GAAe;YACvB,EAAE,EAAE,QAAQ,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;YAClE,QAAQ;YACR,OAAO;YACP,QAAQ;YACR,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE;YACpB,OAAO,EAAE,CAAC;YACV,WAAW,EAAE,IAAI;YACjB,KAAK,EAAE,IAAI;SACZ,CAAC;QAEF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CAAC,QAAsB;QAC5B,IAAI,CAAC,YAAY,EAAE,CAAC;QAEpB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CACnC,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAC9D,CAAC;QAEF,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAE5B,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CAAC,IAAgB,EAAE,KAAa;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC9B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QAEnB,IAAI,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YAC1C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QAED,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,IAAI,CAAC,QAAsB;QACzB,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,CAAC,QAAuB;QAC1B,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,MAAM,CAAC;QAChE,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;IAEO,YAAY;QAClB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACjC,CAAC,CAAC,QAAQ,GAAG,MAAM,IAAI,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAC1D,CAAC;QACF,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YACjC,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAEO,IAAI;QACV,IAAI,CAAC;YACH,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBACrD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAEO,IAAI;QACV,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACxC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACzC,CAAC;YACD,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;QAChF,CAAC;QAAC,MAAM,CAAC;YACP,2CAA2C;QAC7C,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * QueueDrain — Periodic processor that drains the PostQueue when
3
+ * service circuits recover to closed or half-open.
4
+ *
5
+ * @see SPEC-nole-operational-resilience.md §7.2
6
+ */
7
+ import type { PostQueue, PostPlatform } from './post-queue.js';
8
+ import type { ServiceRegistry } from './service-registry.js';
9
+ export type PostSender = (platform: PostPlatform, content: string, metadata: Record<string, unknown>) => Promise<void>;
10
+ export interface QueueDrainConfig {
11
+ intervalMs: number;
12
+ batchSize: number;
13
+ interPostDelayMs: number;
14
+ logger?: {
15
+ debug: (msg: string, meta?: Record<string, unknown>) => void;
16
+ warn: (msg: string, meta?: Record<string, unknown>) => void;
17
+ error: (msg: string, meta?: Record<string, unknown>) => void;
18
+ };
19
+ }
20
+ export declare const DEFAULT_DRAIN_CONFIG: QueueDrainConfig;
21
+ export declare class QueueDrain {
22
+ private queue;
23
+ private registry;
24
+ private sender;
25
+ private config;
26
+ private timer;
27
+ private running;
28
+ private logger;
29
+ constructor(queue: PostQueue, registry: ServiceRegistry, sender: PostSender, config?: QueueDrainConfig);
30
+ start(): void;
31
+ stop(): void;
32
+ drain(): Promise<number>;
33
+ isRunning(): boolean;
34
+ }
35
+ //# sourceMappingURL=queue-drain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue-drain.d.ts","sourceRoot":"","sources":["../../src/resilience/queue-drain.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,UAAU,GAAG,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE;QACP,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC7D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC5D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAC9D,CAAC;CACH;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAIlC,CAAC;AAEF,qBAAa,UAAU;IAMnB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IARhB,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAA0C;gBAG9C,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,gBAAuC;IASzD,KAAK,IAAI,IAAI;IAMb,IAAI,IAAI,IAAI;IAQN,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAgD9B,SAAS,IAAI,OAAO;CAGrB"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * QueueDrain — Periodic processor that drains the PostQueue when
3
+ * service circuits recover to closed or half-open.
4
+ *
5
+ * @see SPEC-nole-operational-resilience.md §7.2
6
+ */
7
+ export const DEFAULT_DRAIN_CONFIG = {
8
+ intervalMs: 60_000,
9
+ batchSize: 5,
10
+ interPostDelayMs: 5_000,
11
+ };
12
+ export class QueueDrain {
13
+ queue;
14
+ registry;
15
+ sender;
16
+ config;
17
+ timer = null;
18
+ running = false;
19
+ logger;
20
+ constructor(queue, registry, sender, config = DEFAULT_DRAIN_CONFIG) {
21
+ this.queue = queue;
22
+ this.registry = registry;
23
+ this.sender = sender;
24
+ this.config = config;
25
+ this.logger = config.logger ?? {
26
+ debug: () => { },
27
+ warn: () => { },
28
+ error: () => { },
29
+ };
30
+ }
31
+ start() {
32
+ if (this.timer)
33
+ return;
34
+ this.timer = setInterval(() => void this.drain(), this.config.intervalMs);
35
+ this.logger.debug('Queue drain started', { intervalMs: this.config.intervalMs });
36
+ }
37
+ stop() {
38
+ if (this.timer) {
39
+ clearInterval(this.timer);
40
+ this.timer = null;
41
+ }
42
+ this.logger.debug('Queue drain stopped');
43
+ }
44
+ async drain() {
45
+ if (this.running)
46
+ return 0;
47
+ this.running = true;
48
+ let sent = 0;
49
+ try {
50
+ const platforms = ['x-twitter', 'moltbook'];
51
+ for (const platform of platforms) {
52
+ if (!this.registry.isAvailable(platform)) {
53
+ this.logger.debug(`${platform}: circuit still open, skipping drain`);
54
+ continue;
55
+ }
56
+ const queued = this.queue.size(platform);
57
+ if (queued === 0)
58
+ continue;
59
+ this.logger.debug(`${platform}: draining ${queued} queued posts`);
60
+ for (let i = 0; i < this.config.batchSize; i++) {
61
+ const post = this.queue.dequeue(platform);
62
+ if (!post)
63
+ break;
64
+ try {
65
+ await this.sender(platform, post.content, post.metadata);
66
+ sent++;
67
+ this.registry.recordSuccess(platform);
68
+ this.logger.debug(`${platform}: sent queued post ${post.id}`);
69
+ if (i < this.config.batchSize - 1) {
70
+ await new Promise(r => setTimeout(r, this.config.interPostDelayMs));
71
+ }
72
+ }
73
+ catch (err) {
74
+ const error = err instanceof Error ? err.message : String(err);
75
+ this.queue.requeue(post, error);
76
+ this.registry.recordFailure(platform);
77
+ this.logger.warn(`${platform}: failed to send ${post.id}, requeued`, { error });
78
+ break;
79
+ }
80
+ }
81
+ }
82
+ }
83
+ finally {
84
+ this.running = false;
85
+ }
86
+ return sent;
87
+ }
88
+ isRunning() {
89
+ return this.timer !== null;
90
+ }
91
+ }
92
+ //# sourceMappingURL=queue-drain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue-drain.js","sourceRoot":"","sources":["../../src/resilience/queue-drain.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH,MAAM,CAAC,MAAM,oBAAoB,GAAqB;IACpD,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,CAAC;IACZ,gBAAgB,EAAE,KAAK;CACxB,CAAC;AAEF,MAAM,OAAO,UAAU;IAMX;IACA;IACA;IACA;IARF,KAAK,GAA0C,IAAI,CAAC;IACpD,OAAO,GAAG,KAAK,CAAC;IAChB,MAAM,CAA0C;IAExD,YACU,KAAgB,EAChB,QAAyB,EACzB,MAAkB,EAClB,SAA2B,oBAAoB;QAH/C,UAAK,GAAL,KAAK,CAAW;QAChB,aAAQ,GAAR,QAAQ,CAAiB;QACzB,WAAM,GAAN,MAAM,CAAY;QAClB,WAAM,GAAN,MAAM,CAAyC;QAEvD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI;YAC7B,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;YACf,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;YACd,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;SAChB,CAAC;IACJ,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1E,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,IAAI,GAAG,CAAC,CAAC;QAEb,IAAI,CAAC;YACH,MAAM,SAAS,GAAmB,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;YAE5D,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACjC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACzC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,sCAAsC,CAAC,CAAC;oBACrE,SAAS;gBACX,CAAC;gBAED,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACzC,IAAI,MAAM,KAAK,CAAC;oBAAE,SAAS;gBAE3B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,cAAc,MAAM,eAAe,CAAC,CAAC;gBAElE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;oBAC1C,IAAI,CAAC,IAAI;wBAAE,MAAM;oBAEjB,IAAI,CAAC;wBACH,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;wBACzD,IAAI,EAAE,CAAC;wBACP,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;wBACtC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,sBAAsB,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;wBAE9D,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;4BAClC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC;wBACtE,CAAC;oBACH,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;wBAC/D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;wBAChC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;wBACtC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,oBAAoB,IAAI,CAAC,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;wBAChF,MAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * ResilientClient — Unified wrapper combining circuit breaker + retry.
3
+ *
4
+ * Replaces raw fetch() calls in Nole's service clients with
5
+ * protection against transient failures and cascading outages.
6
+ *
7
+ * @see SPEC-nole-operational-resilience.md §5
8
+ */
9
+ import { type CircuitBreakerConfig, type CircuitState } from './circuit-breaker.js';
10
+ import { type RetryConfig } from './retry.js';
11
+ export declare class CircuitOpenError extends Error {
12
+ readonly service: string;
13
+ readonly state: CircuitState;
14
+ readonly cooldownRemainingMs: number;
15
+ constructor(service: string, cooldownRemainingMs: number);
16
+ }
17
+ export interface ResilientClientConfig {
18
+ circuitBreaker: CircuitBreakerConfig;
19
+ retry?: RetryConfig;
20
+ timeoutMs: number;
21
+ logger?: {
22
+ debug: (msg: string, meta?: Record<string, unknown>) => void;
23
+ warn: (msg: string, meta?: Record<string, unknown>) => void;
24
+ error: (msg: string, meta?: Record<string, unknown>) => void;
25
+ };
26
+ fetchFn?: typeof globalThis.fetch;
27
+ }
28
+ export interface ResilientResponse {
29
+ status: number;
30
+ headers: Headers;
31
+ body: string;
32
+ attempts: number;
33
+ totalDurationMs: number;
34
+ }
35
+ export declare class ResilientClient {
36
+ private config;
37
+ private breaker;
38
+ private retryConfig;
39
+ private timeoutMs;
40
+ private logger;
41
+ private fetchFn;
42
+ constructor(config: ResilientClientConfig);
43
+ execute(url: string, init?: RequestInit): Promise<ResilientResponse>;
44
+ getCircuitStatus(): {
45
+ state: CircuitState;
46
+ failureCount: number;
47
+ consecutiveOpenings: number;
48
+ currentCooldownMs: number;
49
+ lastStateChange: number;
50
+ recentFailures: string[];
51
+ };
52
+ get serviceName(): string;
53
+ }
54
+ //# sourceMappingURL=resilient-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilient-client.d.ts","sourceRoot":"","sources":["../../src/resilience/resilient-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAkB,KAAK,oBAAoB,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpG,OAAO,EACL,KAAK,WAAW,EAMjB,MAAM,YAAY,CAAC;AAEpB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;gBAEzB,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM;CAOzD;AAED,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,oBAAoB,CAAC;IACrC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QACP,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC7D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC5D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAC9D,CAAC;IACF,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,eAAe;IAOd,OAAO,CAAC,MAAM;IAN1B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAA+C;IAC7D,OAAO,CAAC,OAAO,CAA0B;gBAErB,MAAM,EAAE,qBAAqB;IAY3C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAyF1E,gBAAgB;;;;;;;;IAIhB,IAAI,WAAW,IAAI,MAAM,CAExB;CACF"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * ResilientClient — Unified wrapper combining circuit breaker + retry.
3
+ *
4
+ * Replaces raw fetch() calls in Nole's service clients with
5
+ * protection against transient failures and cascading outages.
6
+ *
7
+ * @see SPEC-nole-operational-resilience.md §5
8
+ */
9
+ import { CircuitBreaker } from './circuit-breaker.js';
10
+ import { DEFAULT_RETRY_CONFIG, calculateDelay, isRetryable, extractRetryAfter, sleep, } from './retry.js';
11
+ export class CircuitOpenError extends Error {
12
+ service;
13
+ state;
14
+ cooldownRemainingMs;
15
+ constructor(service, cooldownRemainingMs) {
16
+ super(`Circuit breaker OPEN for ${service} — next probe in ${Math.ceil(cooldownRemainingMs / 1000)}s`);
17
+ this.name = 'CircuitOpenError';
18
+ this.service = service;
19
+ this.state = 'open';
20
+ this.cooldownRemainingMs = cooldownRemainingMs;
21
+ }
22
+ }
23
+ export class ResilientClient {
24
+ config;
25
+ breaker;
26
+ retryConfig;
27
+ timeoutMs;
28
+ logger;
29
+ fetchFn;
30
+ constructor(config) {
31
+ this.config = config;
32
+ this.breaker = new CircuitBreaker(config.circuitBreaker);
33
+ this.retryConfig = config.retry ?? DEFAULT_RETRY_CONFIG;
34
+ this.timeoutMs = config.timeoutMs;
35
+ this.fetchFn = config.fetchFn ?? globalThis.fetch;
36
+ this.logger = config.logger ?? {
37
+ debug: () => { },
38
+ warn: () => { },
39
+ error: () => { },
40
+ };
41
+ }
42
+ async execute(url, init) {
43
+ const startTime = Date.now();
44
+ const service = this.config.circuitBreaker.name;
45
+ if (!this.breaker.canExecute()) {
46
+ const status = this.breaker.getStatus();
47
+ const remaining = status.currentCooldownMs - (Date.now() - status.lastStateChange);
48
+ throw new CircuitOpenError(service, Math.max(0, remaining));
49
+ }
50
+ let lastError = null;
51
+ const maxAttempts = 1 + this.retryConfig.maxRetries;
52
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
53
+ try {
54
+ this.logger.debug(`${service}: attempt ${attempt + 1}/${maxAttempts}`, { url });
55
+ const controller = new AbortController();
56
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
57
+ const resp = await this.fetchFn(url, {
58
+ ...init,
59
+ signal: controller.signal,
60
+ });
61
+ clearTimeout(timeout);
62
+ if (this.breaker.isFailureStatus(resp.status)) {
63
+ const body = await resp.text().catch(() => '');
64
+ this.breaker.recordFailure(`HTTP ${resp.status}: ${body.slice(0, 200)}`);
65
+ if (isRetryable(resp.status, this.retryConfig) && attempt < maxAttempts - 1) {
66
+ const delay = resp.status === 429
67
+ ? (extractRetryAfter(resp.headers) ?? calculateDelay(attempt, this.retryConfig))
68
+ : calculateDelay(attempt, this.retryConfig);
69
+ this.logger.warn(`${service}: HTTP ${resp.status}, retrying in ${delay}ms`, {
70
+ attempt: attempt + 1, url,
71
+ });
72
+ await sleep(delay);
73
+ continue;
74
+ }
75
+ throw new Error(`${service}: HTTP ${resp.status} after ${attempt + 1} attempts`);
76
+ }
77
+ if (resp.status >= 200 && resp.status < 500) {
78
+ this.breaker.recordSuccess();
79
+ }
80
+ if (resp.status === 429 && attempt < maxAttempts - 1) {
81
+ const delay = extractRetryAfter(resp.headers) ?? calculateDelay(attempt, this.retryConfig);
82
+ this.logger.warn(`${service}: rate limited (429), waiting ${delay}ms`, { url });
83
+ await sleep(delay);
84
+ continue;
85
+ }
86
+ const body = await resp.text();
87
+ return {
88
+ status: resp.status,
89
+ headers: resp.headers,
90
+ body,
91
+ attempts: attempt + 1,
92
+ totalDurationMs: Date.now() - startTime,
93
+ };
94
+ }
95
+ catch (err) {
96
+ if (err instanceof CircuitOpenError)
97
+ throw err;
98
+ const error = err instanceof Error ? err : new Error(String(err));
99
+ lastError = error;
100
+ this.breaker.recordFailure(error.message.slice(0, 200));
101
+ if (isRetryable(error, this.retryConfig) && attempt < maxAttempts - 1) {
102
+ const delay = calculateDelay(attempt, this.retryConfig);
103
+ this.logger.warn(`${service}: network error, retrying in ${delay}ms`, {
104
+ attempt: attempt + 1, error: error.message.slice(0, 100),
105
+ });
106
+ await sleep(delay);
107
+ continue;
108
+ }
109
+ }
110
+ }
111
+ throw lastError ?? new Error(`${service}: all ${maxAttempts} attempts failed`);
112
+ }
113
+ getCircuitStatus() {
114
+ return this.breaker.getStatus();
115
+ }
116
+ get serviceName() {
117
+ return this.config.circuitBreaker.name;
118
+ }
119
+ }
120
+ //# sourceMappingURL=resilient-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilient-client.js","sourceRoot":"","sources":["../../src/resilience/resilient-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Retry logic — exponential backoff with jitter, Retry-After support.
3
+ *
4
+ * Retry is separate from the circuit breaker: the breaker decides whether
5
+ * to attempt at all, retry decides how many times to reattempt on failure.
6
+ *
7
+ * @see SPEC-nole-operational-resilience.md §4
8
+ */
9
+ export interface RetryConfig {
10
+ maxRetries: number;
11
+ baseDelayMs: number;
12
+ maxDelayMs: number;
13
+ jitterFactor: number;
14
+ retryableStatusCodes: number[];
15
+ retryOnNetworkError: boolean;
16
+ }
17
+ export declare const DEFAULT_RETRY_CONFIG: RetryConfig;
18
+ export declare function calculateDelay(attempt: number, config: RetryConfig): number;
19
+ export declare function isRetryable(statusOrError: number | Error, config: RetryConfig): boolean;
20
+ export declare function extractRetryAfter(headers: Headers | Record<string, string>): number | null;
21
+ export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
22
+ //# sourceMappingURL=retry.d.ts.map