@git-stunts/alfred 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -6,11 +6,11 @@
6
6
  // @ts-self-types="./index.d.ts"
7
7
 
8
8
  // Error types
9
- export {
10
- RetryExhaustedError,
11
- CircuitOpenError,
9
+ export {
10
+ RetryExhaustedError,
11
+ CircuitOpenError,
12
12
  TimeoutError,
13
- BulkheadRejectedError
13
+ BulkheadRejectedError,
14
14
  } from './errors.js';
15
15
 
16
16
  // Resilience policies
@@ -18,6 +18,7 @@ export { retry } from './policies/retry.js';
18
18
  export { circuitBreaker } from './policies/circuit-breaker.js';
19
19
  export { timeout } from './policies/timeout.js';
20
20
  export { bulkhead } from './policies/bulkhead.js';
21
+ export { hedge } from './policies/hedge.js';
21
22
 
22
23
  // Composition utilities
23
24
  export { compose, fallback, race } from './compose.js';
@@ -27,3 +28,6 @@ export { Policy, Policy as default } from './policy.js';
27
28
 
28
29
  // Clock utilities
29
30
  export { SystemClock, TestClock } from './utils/clock.js';
31
+
32
+ // Telemetry
33
+ export { InMemorySink, ConsoleSink, NoopSink, MultiSink, MetricsSink } from './telemetry.js';
@@ -10,6 +10,7 @@
10
10
  import { BulkheadRejectedError } from '../errors.js';
11
11
  import { SystemClock } from '../utils/clock.js';
12
12
  import { NoopSink } from '../telemetry.js';
13
+ import { resolve as resolveValue } from '../utils/resolvable.js';
13
14
 
14
15
  /**
15
16
  * @typedef {Object} BulkheadOptions
@@ -28,11 +29,11 @@ import { NoopSink } from '../telemetry.js';
28
29
 
29
30
  class BulkheadPolicy {
30
31
  constructor(options) {
31
- const {
32
- limit,
33
- queueLimit = 0,
32
+ const {
33
+ limit,
34
+ queueLimit = 0,
34
35
  telemetry = new NoopSink(),
35
- clock = new SystemClock()
36
+ clock = new SystemClock(),
36
37
  } = options;
37
38
 
38
39
  if (limit <= 0) {
@@ -49,24 +50,38 @@ class BulkheadPolicy {
49
50
  }
50
51
 
51
52
  processQueue() {
52
- if (this.active < this.limit && this.queue.length > 0) {
53
- const { fn, resolve, reject } = this.queue.shift();
53
+ const limit = resolveValue(this.limit);
54
+ if (this.active < limit && this.queue.length > 0) {
55
+ const { fn, resolve: promiseResolve, reject } = this.queue.shift();
54
56
  this.active++;
55
-
57
+
56
58
  this.emitEvent('bulkhead.execute', {
57
59
  active: this.active,
58
- pending: this.queue.length
60
+ pending: this.queue.length,
59
61
  });
60
62
 
61
63
  Promise.resolve()
62
64
  .then(() => fn())
63
- .then(resolve, reject)
65
+ .then(
66
+ (result) => {
67
+ this.emitEvent('bulkhead.complete', {
68
+ active: this.active,
69
+ pending: this.queue.length,
70
+ metrics: { successes: 1 },
71
+ });
72
+ promiseResolve(result);
73
+ },
74
+ (error) => {
75
+ this.emitEvent('bulkhead.complete', {
76
+ active: this.active,
77
+ pending: this.queue.length,
78
+ metrics: { failures: 1 },
79
+ });
80
+ reject(error);
81
+ }
82
+ )
64
83
  .finally(() => {
65
84
  this.active--;
66
- this.emitEvent('bulkhead.complete', {
67
- active: this.active,
68
- pending: this.queue.length
69
- });
70
85
  this.processQueue();
71
86
  });
72
87
  }
@@ -76,36 +91,48 @@ class BulkheadPolicy {
76
91
  this.telemetry.emit({
77
92
  type,
78
93
  timestamp: this.clock.now(),
79
- ...data
94
+ ...data,
80
95
  });
81
96
  }
82
97
 
83
98
  async execute(fn) {
84
- if (this.active < this.limit) {
99
+ const limit = resolveValue(this.limit);
100
+ const queueLimit = resolveValue(this.queueLimit);
101
+
102
+ if (this.active < limit) {
85
103
  this.active++;
86
104
  this.emitEvent('bulkhead.execute', {
87
105
  active: this.active,
88
- pending: this.queue.length
106
+ pending: this.queue.length,
89
107
  });
90
108
 
91
109
  try {
92
- return await fn();
93
- } finally {
94
- this.active--;
110
+ const result = await fn();
111
+ this.emitEvent('bulkhead.complete', {
112
+ active: this.active,
113
+ pending: this.queue.length,
114
+ metrics: { successes: 1 },
115
+ });
116
+ return result;
117
+ } catch (error) {
95
118
  this.emitEvent('bulkhead.complete', {
96
119
  active: this.active,
97
- pending: this.queue.length
120
+ pending: this.queue.length,
121
+ metrics: { failures: 1 },
98
122
  });
123
+ throw error;
124
+ } finally {
125
+ this.active--;
99
126
  this.processQueue();
100
127
  }
101
128
  }
102
129
 
103
- if (this.queue.length < this.queueLimit) {
130
+ if (this.queue.length < queueLimit) {
104
131
  this.emitEvent('bulkhead.queued', {
105
132
  active: this.active,
106
- pending: this.queue.length + 1
133
+ pending: this.queue.length + 1,
107
134
  });
108
-
135
+
109
136
  return new Promise((resolve, reject) => {
110
137
  this.queue.push({ fn, resolve, reject });
111
138
  });
@@ -113,16 +140,17 @@ class BulkheadPolicy {
113
140
 
114
141
  this.emitEvent('bulkhead.reject', {
115
142
  active: this.active,
116
- pending: this.queue.length
143
+ pending: this.queue.length,
144
+ metrics: { bulkheadRejections: 1 },
117
145
  });
118
- throw new BulkheadRejectedError(this.limit, this.queueLimit);
146
+ throw new BulkheadRejectedError(limit, queueLimit);
119
147
  }
120
148
 
121
149
  get stats() {
122
- return {
123
- active: this.active,
124
- pending: this.queue.length,
125
- available: Math.max(0, this.limit - this.active)
150
+ return {
151
+ active: this.active,
152
+ pending: this.queue.length,
153
+ available: Math.max(0, resolveValue(this.limit) - this.active),
126
154
  };
127
155
  }
128
156
  }
@@ -135,11 +163,11 @@ class BulkheadPolicy {
135
163
  */
136
164
  export function bulkhead(options) {
137
165
  const policy = new BulkheadPolicy(options);
138
-
166
+
139
167
  return {
140
168
  execute: (fn) => policy.execute(fn),
141
169
  get stats() {
142
170
  return policy.stats;
143
- }
171
+ },
144
172
  };
145
- }
173
+ }
@@ -1,6 +1,7 @@
1
1
  import { CircuitOpenError } from '../errors.js';
2
2
  import { SystemClock } from '../utils/clock.js';
3
3
  import { NoopSink } from '../telemetry.js';
4
+ import { resolve } from '../utils/resolvable.js';
4
5
 
5
6
  /**
6
7
  * Circuit breaker states.
@@ -10,7 +11,7 @@ import { NoopSink } from '../telemetry.js';
10
11
  const State = {
11
12
  CLOSED: 'CLOSED',
12
13
  OPEN: 'OPEN',
13
- HALF_OPEN: 'HALF_OPEN'
14
+ HALF_OPEN: 'HALF_OPEN',
14
15
  };
15
16
 
16
17
  /**
@@ -43,7 +44,7 @@ class CircuitBreakerPolicy {
43
44
  onClose,
44
45
  onHalfOpen,
45
46
  clock = new SystemClock(),
46
- telemetry = new NoopSink()
47
+ telemetry = new NoopSink(),
47
48
  } = options;
48
49
 
49
50
  if (threshold === undefined || threshold === null) {
@@ -62,7 +63,7 @@ class CircuitBreakerPolicy {
62
63
  onClose,
63
64
  onHalfOpen,
64
65
  clock,
65
- telemetry
66
+ telemetry,
66
67
  };
67
68
 
68
69
  this._state = State.CLOSED;
@@ -79,7 +80,7 @@ class CircuitBreakerPolicy {
79
80
  this.options.telemetry.emit({
80
81
  type,
81
82
  timestamp: this.options.clock.now(),
82
- ...data
83
+ ...data,
83
84
  });
84
85
  }
85
86
 
@@ -87,7 +88,10 @@ class CircuitBreakerPolicy {
87
88
  this._state = State.OPEN;
88
89
  this.openedAt = new Date(this.options.clock.now());
89
90
  this.options.onOpen?.();
90
- this.emitEvent('circuit.open', { failureCount: this.failureCount });
91
+ this.emitEvent('circuit.open', {
92
+ failureCount: this.failureCount,
93
+ metrics: { circuitBreaks: 1 },
94
+ });
91
95
  }
92
96
 
93
97
  close() {
@@ -111,15 +115,18 @@ class CircuitBreakerPolicy {
111
115
  return false;
112
116
  }
113
117
  const elapsed = this.options.clock.now() - this.openedAt.getTime();
114
- return elapsed >= this.options.duration;
118
+ return elapsed >= resolve(this.options.duration);
115
119
  }
116
120
 
117
121
  recordSuccess() {
118
- this.emitEvent('circuit.success', { state: this._state });
122
+ this.emitEvent('circuit.success', {
123
+ state: this._state,
124
+ metrics: { successes: 1 },
125
+ });
119
126
 
120
127
  if (this._state === State.HALF_OPEN) {
121
128
  this.successCount++;
122
- if (this.successCount >= this.options.successThreshold) {
129
+ if (this.successCount >= resolve(this.options.successThreshold)) {
123
130
  this.close();
124
131
  }
125
132
  } else if (this._state === State.CLOSED) {
@@ -134,14 +141,15 @@ class CircuitBreakerPolicy {
134
141
 
135
142
  this.emitEvent('circuit.failure', {
136
143
  error,
137
- state: this._state
144
+ state: this._state,
145
+ metrics: { failures: 1 },
138
146
  });
139
147
 
140
148
  if (this._state === State.HALF_OPEN) {
141
149
  this.open();
142
150
  } else if (this._state === State.CLOSED) {
143
151
  this.failureCount++;
144
- if (this.failureCount >= this.options.threshold) {
152
+ if (this.failureCount >= resolve(this.options.threshold)) {
145
153
  this.open();
146
154
  }
147
155
  }
@@ -155,7 +163,8 @@ class CircuitBreakerPolicy {
155
163
  if (this._state === State.OPEN) {
156
164
  this.emitEvent('circuit.reject', {
157
165
  openedAt: this.openedAt,
158
- failureCount: this.failureCount
166
+ failureCount: this.failureCount,
167
+ metrics: { circuitRejections: 1 },
159
168
  });
160
169
  throw new CircuitOpenError(this.openedAt, this.failureCount);
161
170
  }
@@ -184,6 +193,6 @@ export function circuitBreaker(options) {
184
193
  execute: (fn) => policy.execute(fn),
185
194
  get state() {
186
195
  return policy.state;
187
- }
196
+ },
188
197
  };
189
- }
198
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @fileoverview Hedge policy for speculative execution.
3
+ *
4
+ * Starts concurrent "hedged" attempts if the primary attempt takes too long,
5
+ * helping to reduce tail latency in distributed systems.
6
+ *
7
+ * @module @git-stunts/alfred/policies/hedge
8
+ */
9
+
10
+ import { SystemClock } from '../utils/clock.js';
11
+ import { NoopSink } from '../telemetry.js';
12
+ import { resolve } from '../utils/resolvable.js';
13
+
14
+ /**
15
+ * @typedef {Object} HedgeOptions
16
+ * @property {number} delay - Milliseconds to wait before spawning a hedge.
17
+ * @property {number} [maxHedges=1] - Maximum number of hedged attempts to spawn.
18
+ * @property {import('../telemetry.js').TelemetrySink} [telemetry] - Telemetry sink.
19
+ * @property {{ now(): number, sleep(ms: number): Promise<void> }} [clock] - Clock for testing.
20
+ */
21
+
22
+ class HedgeExecutor {
23
+ constructor(fn, options) {
24
+ this.fn = fn;
25
+ this.options = {
26
+ telemetry: new NoopSink(),
27
+ clock: new SystemClock(),
28
+ maxHedges: 1,
29
+ ...options,
30
+ };
31
+ this.abortControllers = [];
32
+ this._finished = false;
33
+ }
34
+
35
+ async execute() {
36
+ const delay = resolve(this.options.delay);
37
+ const maxHedges = resolve(this.options.maxHedges);
38
+ const attempts = [];
39
+
40
+ // Start primary attempt
41
+ attempts.push(this.createAttempt(0));
42
+
43
+ // Schedule hedges
44
+ for (let i = 1; i <= maxHedges; i++) {
45
+ attempts.push(this.scheduleHedge(i, delay * i));
46
+ }
47
+
48
+ try {
49
+ return await Promise.any(attempts);
50
+ } finally {
51
+ this.cancelAll();
52
+ }
53
+ }
54
+
55
+ createAttempt(index) {
56
+ const controller = new AbortController();
57
+ this.abortControllers.push(controller);
58
+ const { clock, telemetry } = this.options;
59
+
60
+ const startTime = clock.now();
61
+ telemetry.emit({
62
+ type: 'hedge.attempt',
63
+ timestamp: startTime,
64
+ index,
65
+ metrics: index > 0 ? { hedges: 1 } : {},
66
+ });
67
+
68
+ return this.fn(controller.signal)
69
+ .then((result) => {
70
+ const endTime = clock.now();
71
+ telemetry.emit({
72
+ type: 'hedge.success',
73
+ timestamp: endTime,
74
+ index,
75
+ duration: endTime - startTime,
76
+ metrics: { successes: 1 },
77
+ });
78
+ return result;
79
+ })
80
+ .catch((error) => {
81
+ if (error.name !== 'AbortError') {
82
+ const endTime = clock.now();
83
+ telemetry.emit({
84
+ type: 'hedge.failure',
85
+ timestamp: endTime,
86
+ index,
87
+ error,
88
+ duration: endTime - startTime,
89
+ metrics: { failures: 1 },
90
+ });
91
+ }
92
+ throw error;
93
+ });
94
+ }
95
+
96
+ scheduleHedge(index, delayMs) {
97
+ return this.options.clock.sleep(delayMs).then(() => {
98
+ if (this._finished) {
99
+ return new Promise(() => {}); // Never resolve if we are done
100
+ }
101
+ return this.createAttempt(index);
102
+ });
103
+ }
104
+
105
+ cancelAll() {
106
+ this._finished = true;
107
+ for (const controller of this.abortControllers) {
108
+ controller.abort();
109
+ }
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Creates a Hedge policy.
115
+ *
116
+ * @param {HedgeOptions} options - Hedge configuration
117
+ * @returns {{ execute: <T>(fn: () => Promise<T>) => Promise<T> }}
118
+ */
119
+ export function hedge(options) {
120
+ return {
121
+ execute: (fn) => new HedgeExecutor(fn, options).execute(),
122
+ };
123
+ }
@@ -11,6 +11,7 @@ import { SystemClock } from '../utils/clock.js';
11
11
  import { createJitter } from '../utils/jitter.js';
12
12
  import { RetryExhaustedError } from '../errors.js';
13
13
  import { NoopSink } from '../telemetry.js';
14
+ import { resolve } from '../utils/resolvable.js';
14
15
 
15
16
  /**
16
17
  * @typedef {'constant' | 'linear' | 'exponential'} BackoffStrategy
@@ -38,7 +39,7 @@ const DEFAULT_OPTIONS = {
38
39
  delay: 1000,
39
40
  maxDelay: 30000,
40
41
  backoff: 'constant',
41
- jitter: 'none'
42
+ jitter: 'none',
42
43
  };
43
44
 
44
45
  function calculateBackoff(strategy, baseDelay, attempt) {
@@ -59,34 +60,38 @@ class RetryExecutor {
59
60
  this.options = { ...DEFAULT_OPTIONS, ...options };
60
61
  this.clock = options.clock || new SystemClock();
61
62
  this.telemetry = options.telemetry || new NoopSink();
62
- this.applyJitter = createJitter(this.options.jitter);
63
- this.prevDelay = this.options.delay;
63
+ // this.applyJitter is now created dynamically in calculateDelay
64
+ this.prevDelay = resolve(this.options.delay);
64
65
  }
65
66
 
66
67
  calculateDelay(attempt) {
67
- const { backoff, delay: baseDelay, maxDelay, jitter } = this.options;
68
+ const backoff = resolve(this.options.backoff);
69
+ const baseDelay = resolve(this.options.delay);
70
+ const maxDelay = resolve(this.options.maxDelay);
71
+ const jitter = resolve(this.options.jitter);
72
+
68
73
  const rawDelay = calculateBackoff(backoff, baseDelay, attempt);
74
+ const applyJitter = createJitter(jitter);
69
75
 
70
76
  if (jitter === 'decorrelated') {
71
- const actual = this.applyJitter(baseDelay, this.prevDelay, maxDelay);
77
+ const actual = applyJitter(baseDelay, this.prevDelay, maxDelay);
72
78
  this.prevDelay = actual;
73
79
  return actual;
74
80
  }
75
-
76
- return Math.min(this.applyJitter(rawDelay), maxDelay);
81
+
82
+ return Math.min(applyJitter(rawDelay), maxDelay);
77
83
  }
78
84
 
79
85
  async execute() {
80
- const totalAttempts = this.options.retries + 1;
81
-
82
- for (let attempt = 1; attempt <= totalAttempts; attempt++) {
83
- const shouldStop = await this.tryAttempt(attempt, totalAttempts);
86
+ // Loop condition: attempt <= (current_retries + 1)
87
+ // We start at 1.
88
+ for (let attempt = 1; attempt <= resolve(this.options.retries) + 1; attempt++) {
89
+ const shouldStop = await this.tryAttempt(attempt);
84
90
  if (shouldStop) {
85
91
  return shouldStop.result;
86
92
  }
87
93
  }
88
-
89
- // Should be unreachable if logic is correct, but satisfied strict returns
94
+
90
95
  throw new Error('Unexpected retry loop termination');
91
96
  }
92
97
 
@@ -102,22 +107,24 @@ class RetryExecutor {
102
107
  // But we need to calculate delay first
103
108
  const delay = this.calculateDelay(attempt);
104
109
  this.emitScheduled(attempt, delay, error);
105
-
110
+
106
111
  if (this.options.onRetry) {
107
112
  this.options.onRetry(error, attempt, delay);
108
113
  }
109
-
114
+
110
115
  await this.clock.sleep(delay);
111
116
  return null; // Continue loop
112
117
  }
113
118
  }
114
119
 
115
120
  emitSuccess(attempt, startTime) {
121
+ const endTime = this.clock.now();
116
122
  this.telemetry.emit({
117
123
  type: 'retry.success',
118
- timestamp: this.clock.now(),
124
+ timestamp: endTime,
119
125
  attempt,
120
- duration: this.clock.now() - startTime
126
+ duration: endTime - startTime,
127
+ metrics: { successes: 1 },
121
128
  });
122
129
  }
123
130
 
@@ -127,30 +134,33 @@ class RetryExecutor {
127
134
  timestamp: this.clock.now(),
128
135
  attempt,
129
136
  delay,
130
- error
137
+ error,
138
+ metrics: { retries: 1 },
131
139
  });
132
140
  }
133
141
 
134
142
  handleFailure(error, attempt, startTime) {
143
+ const endTime = this.clock.now();
135
144
  this.telemetry.emit({
136
145
  type: 'retry.failure',
137
- timestamp: this.clock.now(),
146
+ timestamp: endTime,
138
147
  attempt,
139
148
  error,
140
- duration: this.clock.now() - startTime
149
+ duration: endTime - startTime,
150
+ metrics: { failures: 1 },
141
151
  });
142
152
 
143
153
  if (this.options.shouldRetry && !this.options.shouldRetry(error)) {
144
154
  throw error;
145
155
  }
146
156
 
147
- const totalAttempts = this.options.retries + 1;
157
+ const totalAttempts = resolve(this.options.retries) + 1;
148
158
  if (attempt >= totalAttempts) {
149
159
  this.telemetry.emit({
150
160
  type: 'retry.exhausted',
151
- timestamp: this.clock.now(),
161
+ timestamp: endTime,
152
162
  attempts: attempt,
153
- error
163
+ error,
154
164
  });
155
165
  throw new RetryExhaustedError(attempt, error);
156
166
  }
@@ -167,4 +177,4 @@ class RetryExecutor {
167
177
  */
168
178
  export async function retry(fn, options = {}) {
169
179
  return new RetryExecutor(fn, options).execute();
170
- }
180
+ }
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { TimeoutError } from '../errors.js';
11
11
  import { NoopSink } from '../telemetry.js';
12
+ import { resolve } from '../utils/resolvable.js';
12
13
 
13
14
  /**
14
15
  * @typedef {Object} TimeoutOptions
@@ -45,6 +46,7 @@ import { NoopSink } from '../telemetry.js';
45
46
  */
46
47
  export async function timeout(ms, fn, options = {}) {
47
48
  const { onTimeout, telemetry = new NoopSink() } = options;
49
+ const timeoutMs = resolve(ms);
48
50
  const controller = new AbortController();
49
51
  const startTime = Date.now();
50
52
 
@@ -58,16 +60,17 @@ export async function timeout(ms, fn, options = {}) {
58
60
  if (onTimeout) {
59
61
  onTimeout(elapsed);
60
62
  }
61
-
63
+
62
64
  telemetry.emit({
63
65
  type: 'timeout',
64
66
  timestamp: Date.now(),
65
- timeout: ms,
66
- elapsed
67
+ timeout: timeoutMs,
68
+ elapsed,
69
+ metrics: { timeouts: 1, failures: 1 },
67
70
  });
68
71
 
69
- reject(new TimeoutError(ms, elapsed));
70
- }, ms);
72
+ reject(new TimeoutError(timeoutMs, elapsed));
73
+ }, timeoutMs);
71
74
  });
72
75
 
73
76
  try {
package/src/policy.js CHANGED
@@ -34,6 +34,7 @@ import { retry } from './policies/retry.js';
34
34
  import { circuitBreaker } from './policies/circuit-breaker.js';
35
35
  import { timeout } from './policies/timeout.js';
36
36
  import { bulkhead } from './policies/bulkhead.js';
37
+ import { hedge } from './policies/hedge.js';
37
38
  import { compose, fallback, race } from './compose.js';
38
39
 
39
40
  /**
@@ -134,6 +135,17 @@ export class Policy {
134
135
  return new Policy((fn) => limiter.execute(fn));
135
136
  }
136
137
 
138
+ /**
139
+ * Creates a Policy that speculatively executes hedged attempts.
140
+ *
141
+ * @param {import('./policies/hedge.js').HedgeOptions} options
142
+ * @returns {Policy}
143
+ */
144
+ static hedge(options) {
145
+ const hedger = hedge(options);
146
+ return new Policy((fn) => hedger.execute(fn));
147
+ }
148
+
137
149
  /**
138
150
  * Creates a no-op Policy that passes through to the function directly.
139
151
  *
@@ -181,10 +193,7 @@ export class Policy {
181
193
  return new Policy((fn) => {
182
194
  // Compose: outer wraps inner
183
195
  // When outer calls its "fn", that fn is actually inner's execution
184
- return compose(
185
- { execute: outer },
186
- { execute: inner }
187
- ).execute(fn);
196
+ return compose({ execute: outer }, { execute: inner }).execute(fn);
188
197
  });
189
198
  }
190
199
 
@@ -212,10 +221,7 @@ export class Policy {
212
221
  const secondary = otherPolicy._executor;
213
222
 
214
223
  return new Policy((fn) => {
215
- return fallback(
216
- { execute: primary },
217
- { execute: secondary }
218
- ).execute(fn);
224
+ return fallback({ execute: primary }, { execute: secondary }).execute(fn);
219
225
  });
220
226
  }
221
227
 
@@ -243,10 +249,7 @@ export class Policy {
243
249
  const second = otherPolicy._executor;
244
250
 
245
251
  return new Policy((fn) => {
246
- return race(
247
- { execute: first },
248
- { execute: second }
249
- ).execute(fn);
252
+ return race({ execute: first }, { execute: second }).execute(fn);
250
253
  });
251
254
  }
252
255