deno.land / x / cockatiel@v3.1.2 / src / Policy.ts

نووسراو ببینە
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
import { ConstantBackoff, IBackoffFactory } from './backoff/Backoff';import { IBreaker } from './breaker/Breaker';import { BulkheadPolicy } from './BulkheadPolicy';import { CircuitBreakerPolicy } from './CircuitBreakerPolicy';import { Event } from './common/Event';import { ExecuteWrapper } from './common/Executor';import { FallbackPolicy } from './FallbackPolicy';import { NoopPolicy } from './NoopPolicy';import { IRetryBackoffContext, RetryPolicy } from './RetryPolicy';import { TimeoutPolicy, TimeoutStrategy } from './TimeoutPolicy';
type Constructor<T> = new (...args: any) => T;
const typeFilter = <T>(cls: Constructor<T>, predicate?: (error: T) => boolean) => predicate ? (v: unknown) => v instanceof cls && predicate(v) : (v: unknown) => v instanceof cls;
const always = () => true;const never = () => false;
export interface IBasePolicyOptions { errorFilter: (error: Error) => boolean; resultFilter: (result: unknown) => boolean;}
/** * The reason for a call failure. Either an error, or the a value that was * marked as a failure (when using result filtering). */export type FailureReason<ReturnType> = { error: Error } | { value: ReturnType };
/** * Event emitted on the `onFailure` calls. */export interface IFailureEvent { /** * Call duration, in milliseconds (with nanosecond precision, as the OS allows). */ duration: number;
/** * Whether the error was handled by the policy. */ handled: boolean;
/** * The reason for the error. */ reason: FailureReason<unknown>;}
/** * Event emitted on the `onSuccess` calls. */export interface ISuccessEvent { /** * Call duration, in milliseconds (with nanosecond precision, as the OS allows). */ duration: number;}
export interface IDefaultPolicyContext { /** * Abort signal for the operation. This is propagated through multiple * retry policies. */ signal: AbortSignal;}
/** * IPolicy is the type of all policies that Cockatiel provides. It describes * an execute() function which takes a generic argument. */export interface IPolicy< ContextType extends IDefaultPolicyContext = IDefaultPolicyContext, AltReturn = never,> { /** * Virtual property only used for TypeScript--will not actually be defined. * @deprecated This property does not exist */ readonly _altReturn: AltReturn;
/** * Fires on the policy when a request successfully completes and some * successful value will be returned. In a retry policy, this is fired once * even if the request took multiple retries to succeed. */ readonly onSuccess: Event<ISuccessEvent>;
/** * Fires on the policy when a request fails *due to a handled reason* fails * and will give rejection to the called. */ readonly onFailure: Event<IFailureEvent>;
/** * Runs the function through behavior specified by the policy. */ execute<T>( fn: (context: ContextType) => PromiseLike<T> | T, signal?: AbortSignal, ): Promise<T | AltReturn>;}
export interface IMergedPolicy<A extends IDefaultPolicyContext, B, W extends IPolicy<any, any>[]> extends IPolicy<A, B> { readonly wrapped: W;}
type MergePolicies<A, B> = A extends IPolicy<infer A1, any> ? B extends IPolicy<infer B1, any> ? IMergedPolicy< A1 & B1, A['_altReturn'] | B['_altReturn'], B extends IMergedPolicy<any, any, infer W> ? [A, ...W] : [A, B] > : never : never;
export class Policy { /** * Factory that builds a base set of filters that can be used in circuit * breakers, retries, etc. */ constructor(public readonly options: Readonly<IBasePolicyOptions>) {}
/** * Allows the policy to additionally handles errors of the given type. * * @param cls Class constructor to check that the error is an instance of. * @param predicate If provided, a function to be called with the error * which should return "true" if we want to handle this error. * @example * ```js * // retry both network errors and response errors with a 503 status code * new Policy() * .orType(NetworkError) * .orType(ResponseError, err => err.statusCode === 503) * .retry() * .attempts(3) * .execute(() => getJsonFrom('https://example.com')); * ``` */ public orType<T>(cls: Constructor<T>, predicate?: (error: T) => boolean) { const filter = typeFilter(cls, predicate); return new Policy({ ...this.options, errorFilter: e => this.options.errorFilter(e) || filter(e), }); }
/** * Allows the policy to additionally handles errors that pass the given * predicate function. * * @param predicate Takes any thrown error, and returns true if it should * be retried by this policy. * @example * ```js * // only retry if the error has a "shouldBeRetried" property set * new Policy() * .orWhen(err => err.shouldBeRetried === true) * .retry() * .attempts(3) * .execute(() => getJsonFrom('https://example.com')); * ``` */ public orWhen(predicate: (error: Error) => boolean) { return new Policy({ ...this.options, errorFilter: e => this.options.errorFilter(e) || predicate(e), }); }
/** * Adds handling for return values. The predicate will be called with * the return value of the executed function, * * @param predicate Takes the returned value, and returns true if it * should be retried by this policy. * @example * ```js * // retry when the response status code is a 5xx * new Policy() * .orResultWhen(res => res.statusCode >= 500) * .retry() * .attempts(3) * .execute(() => getJsonFrom('https://example.com')); * ``` */ public orWhenResult(predicate: (r: unknown) => boolean) { return new Policy({ ...this.options, resultFilter: r => this.options.resultFilter(r) || predicate(r), }); }
/** * Adds handling for return values. The predicate will be called with * the return value of the executed function, * * @param predicate Takes the returned value, and returns true if it * should be retried by this policy. * @example * ```js * // retry when the response status code is a 5xx * new Policy() * .orResultType(res => res.statusCode >= 500) * .retry() * .attempts(3) * .execute(() => getJsonFrom('https://example.com')); * ``` */ public orResultType<T>(cls: Constructor<T>, predicate?: (error: T) => boolean) { const filter = typeFilter(cls, predicate); return new Policy({ ...this.options, resultFilter: r => this.options.resultFilter(r) || filter(r), }); }}
export const noop = new NoopPolicy();
/** * A policy that handles all errors. */export const handleAll = new Policy({ errorFilter: always, resultFilter: never });
/** * See {@link Policy.orType} for usage. */export function handleType<T>(cls: Constructor<T>, predicate?: (error: T) => boolean) { return new Policy({ errorFilter: typeFilter(cls, predicate), resultFilter: never });}
/** * See {@link Policy.orWhen} for usage. */export function handleWhen(predicate: (error: Error) => boolean) { return new Policy({ errorFilter: predicate, resultFilter: never });}/** * See {@link Policy.orResultType} for usage. */export function handleResultType<T>(cls: Constructor<T>, predicate?: (error: T) => boolean) { return new Policy({ errorFilter: never, resultFilter: typeFilter(cls, predicate) });}
/** * See {@link Policy.orWhenResult} for usage. */export function handleWhenResult(predicate: (error: unknown) => boolean) { return new Policy({ errorFilter: never, resultFilter: predicate });}
/** * Creates a bulkhead--a policy that limits the number of concurrent calls. */export function bulkhead(limit: number, queue: number = 0) { return new BulkheadPolicy(limit, queue);}
/** * A decorator that can be used to wrap class methods and apply the given * policy to them. It also adds the last argument normally given in * {@link Policy.execute} as the last argument in the function call. * For example: * * ```ts * import { usePolicy, retry, handleAll } from 'cockatiel'; * * const retry = retry(handleAll, { maxAttempts: 3 }); * * class Database { * @usePolicy(retry) * public getUserInfo(userId, context, cancellationToken) { * console.log('Retry attempt number', context.attempt); * // implementation here * } * } * * const db = new Database(); * db.getUserInfo(3).then(info => console.log('User 3 info:', info)) * ``` * * Note that it will force the return type to be a Promise, since that's * what policies return. */export function usePolicy(policy: IPolicy<IDefaultPolicyContext, never>) { return (_target: unknown, _key: string, descriptor: PropertyDescriptor) => { const inner = descriptor.value; if (typeof inner !== 'function') { throw new Error(`Can only decorate functions with @cockatiel, got ${typeof inner}`); }
descriptor.value = function (this: unknown, ...args: any[]) { const signal = args[args.length - 1] instanceof AbortSignal ? args.pop() : undefined; return policy.execute(context => inner.apply(this, [...args, context]), signal); }; };}
/** * Creates a timeout policy. * @param duration - How long to wait before timing out execute()'d functions * @param strategy - Strategy for timeouts, "Cooperative" or "Aggressive". * A {@link CancellationToken} will be pass to any executed function, and in * cooperative timeouts we'll simply wait for that function to return or * throw. In aggressive timeouts, we'll immediately throw a * {@link TaskCancelledError} when the timeout is reached, in addition to * marking the passed token as failed. */export function timeout( duration: number, strategyOrOpts: TimeoutStrategy | { strategy: TimeoutStrategy; abortOnReturn: boolean },) { return new TimeoutPolicy( duration, typeof strategyOrOpts === 'string' ? { strategy: strategyOrOpts } : strategyOrOpts, );}
/** * Wraps the given set of policies into a single policy. For instance, this: * * ```js * retry.execute(() => * breaker.execute(() => * timeout.execute(({ cancellationToken }) => getData(cancellationToken)))) * ``` * * Is the equivalent to: * * ```js * Policy * .wrap(retry, breaker, timeout) * .execute(({ cancellationToken }) => getData(cancellationToken))); * ``` * * The `context` argument passed to the executed function is the merged object * of all previous policies. * */// The types here a certain unattrative. Ideally we could do// `wrap<A, B>(p: IPolicy<A, B>): IPolicy<A, B>`, but TS doesn't narrow the// types well in that scenario (unless p is explicitly typed as an IPolicy// and not some implementation) and returns `IPolicy<void, unknown>` and// the like. This is the best solution I've found for it.export function wrap<A extends IPolicy<IDefaultPolicyContext, unknown>>(p1: A): A;export function wrap< A extends IPolicy<IDefaultPolicyContext, unknown>, B extends IPolicy<IDefaultPolicyContext, unknown>,>(p1: A, p2: B): MergePolicies<A, B>;export function wrap< A extends IPolicy<IDefaultPolicyContext, unknown>, B extends IPolicy<IDefaultPolicyContext, unknown>, C extends IPolicy<IDefaultPolicyContext, unknown>,>(p1: A, p2: B, p3: C): MergePolicies<C, MergePolicies<A, B>>;export function wrap< A extends IPolicy<IDefaultPolicyContext, unknown>, B extends IPolicy<IDefaultPolicyContext, unknown>, C extends IPolicy<IDefaultPolicyContext, unknown>, D extends IPolicy<IDefaultPolicyContext, unknown>,>(p1: A, p2: B, p3: C, p4: D): MergePolicies<D, MergePolicies<C, MergePolicies<A, B>>>;export function wrap< A extends IPolicy<IDefaultPolicyContext, unknown>, B extends IPolicy<IDefaultPolicyContext, unknown>, C extends IPolicy<IDefaultPolicyContext, unknown>, D extends IPolicy<IDefaultPolicyContext, unknown>, E extends IPolicy<IDefaultPolicyContext, unknown>,>( p1: A, p2: B, p3: C, p4: D, p5: E,): MergePolicies<E, MergePolicies<D, MergePolicies<C, MergePolicies<A, B>>>>;export function wrap<C extends IDefaultPolicyContext, A>(...p: Array<IPolicy<C, A>>): IPolicy<C, A>;export function wrap<C extends IDefaultPolicyContext, A>( ...p: Array<IPolicy<C, A>>): IMergedPolicy<C, A, IPolicy<C, A>[]> { return { _altReturn: undefined as any, onFailure: p[0].onFailure, onSuccess: p[0].onSuccess, wrapped: p, execute<T>(fn: (context: C) => PromiseLike<T> | T, signal: AbortSignal): Promise<T | A> { const run = (context: C, i: number): PromiseLike<T | A> | T | A => i === p.length ? fn(context) : p[i].execute(next => run({ ...context, ...next }, i + 1), context.signal); return Promise.resolve(run({ signal } as C, 0)); }, };}
/** * Creates a retry policy. The options should contain the backoff strategy to * use. Included strategies are: * - {@link ConstantBackoff} * - {@link ExponentialBackoff} * - {@link IterableBackoff} * - {@link DelegateBackoff} (advanced) * * For example: * * ``` * import { handleAll, retry } from 'cockatiel'; * * const policy = retry(handleAll, { backoff: new ExponentialBackoff() }); * ``` * * You can optionally pass in the `attempts` to limit the maximum number of * retry attempts per call. */export function retry( policy: Policy, opts: { maxAttempts?: number; backoff?: IBackoffFactory<IRetryBackoffContext<unknown>>; },) { return new RetryPolicy( { backoff: opts.backoff || new ConstantBackoff(0), maxAttempts: opts.maxAttempts ?? Infinity }, new ExecuteWrapper(policy.options.errorFilter, policy.options.resultFilter), );}
/** * Returns a circuit breaker for the policy. **Important**: you should share * your circuit breaker between executions of whatever function you're * wrapping for it to function! * * ```ts * import { SamplingBreaker, Policy } from 'cockatiel'; * * // Break if more than 20% of requests fail in a 30 second time window: * const breaker = Policy * .handleAll() * .circuitBreaker(10_000, new SamplingBreaker(0.2, 30 * 1000)); * * export function handleRequest() { * return breaker.execute(() => getInfoFromDatabase()); * } * ``` * * @param halfOpenAfter Time after failures to try to open the circuit * breaker again. * @param breaker The circuit breaker to use. This package exports * ConsecutiveBreaker and SamplingBreakers for you to use. */export function circuitBreaker(policy: Policy, opts: { halfOpenAfter: number; breaker: IBreaker }) { return new CircuitBreakerPolicy( opts, new ExecuteWrapper(policy.options.errorFilter, policy.options.resultFilter), );}
/** * Falls back to the given value in the event of an error. * * ```ts * import { Policy } from 'cockatiel'; * * const fallback = Policy * .handleType(DatabaseError) * .fallback(() => getStaleData()); * * export function handleRequest() { * return fallback.execute(() => getInfoFromDatabase()); * } * ``` * * @param toValue Value to fall back to, or a function that creates the * value to return (any may return a promise) */export function fallback<R>(policy: Policy, valueOrFactory: (() => Promise<R> | R) | R) { return new FallbackPolicy( new ExecuteWrapper(policy.options.errorFilter, policy.options.resultFilter), // not technically type-safe, since if they actually want to _return_ // a function, that gets lost here. We'll just advice in the docs to // use a higher-order function if necessary. (typeof valueOrFactory === 'function' ? valueOrFactory : () => valueOrFactory) as () => R, );}
cockatiel

Version Info

Tagged at
5 months ago