-
Notifications
You must be signed in to change notification settings - Fork 2
/
rebucket.ts
74 lines (62 loc) · 2.12 KB
/
rebucket.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
import type {R2Bucket} from '@cloudflare/workers-types';
type ReBucketSupportedMethods = Pick<R2Bucket,'head'|'get'|'list'|'delete'|'put'>;
const DEFAULT_RETRY_COUNT = 3;
const DEFAULT_DELAY = 1000;
async function sleep(time = 0): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, time));
}
export async function withRetry<R>(
fn: () => Promise<R>,
options: {
retries: number;
delay: number;
error?: (error: unknown, state: {retry: number; fn: string;}) => unknown,
},
): Promise<R> {
let err;
for (let i = 0; i < options.retries; i++) {
try {
const result = await fn();
return result;
} catch (error) {
err = error;
if (options.error) {
options.error(err, {retry: i, fn: fn.name});
}
if (i > 0) {
await sleep(options.delay * i * i); // Exponential delay (0s, 1s, 4s, 9s, ...)
}
}
}
throw err;
}
export interface ReBucketOptions {
retries?: number;
delay?: number;
error?: (error: unknown, state: {retry: number; fn: string;}) => unknown;
}
export class ReBucket<T extends ReBucketSupportedMethods> {
constructor(
readonly bucket: T,
readonly options: ReBucketOptions,
) {}
readonly retries = 1 + ((typeof this.options.retries === 'number') ? this.options.retries : DEFAULT_RETRY_COUNT);
readonly delay = ((typeof this.options.delay === 'number') ? this.options.delay : DEFAULT_DELAY);
readonly error = this.options.error;
get(...parameters: Parameters<ReBucketSupportedMethods['get']>) {
return withRetry(() => this.bucket.get(...parameters), this);
}
delete(...parameters: Parameters<ReBucketSupportedMethods['delete']>) {
return withRetry(() => this.bucket.delete(...parameters), this);
}
list(...parameters: Parameters<ReBucketSupportedMethods['list']>) {
return withRetry(() => this.bucket.list(...parameters), this);
}
put(...parameters: Parameters<ReBucketSupportedMethods['put']>) {
// FIXME - think up some strategy for tee'ing the ReadableStream so it can be retried
return this.bucket.put(...parameters);
}
head(...parameters: Parameters<ReBucketSupportedMethods['head']>) {
return withRetry(() => this.bucket.head(...parameters), this);
}
}