Setting the file. One moment. Read Back Reads · Wix Replatform · wix/skills · Skills Docslib/read-back-reads.js
lib/read-back-reads.js
JavaScript·94 lines·3 KB
11 now = () => performance.now(),
12 sleep = ms => new Promise(r => setTimeout(r, ms)),
13 budgetMs = 600000,
14 settlementMs = 60000,
15 pacingMs = 200
16 } = {}) {
17 Object.assign(this, {
18 client,
19 now,
20 sleep,
21 budgetMs,
22 settlementMs,
23 pacingMs
24 });
25 this.stats = {
26 attempts: 0,
27 timeouts: 0,
28 readMs: 0,
29 pacingMs: 0,
30 retryMs: 0,
31 settlementMs: 0
32 };
33 this.nextAt = 0;
34 this.queue = Promise.resolve();
35 this.wix = {
36 send: request => this.send(request)
37 };
38 }
39 remaining() {
40 return Math.max(0, this.budgetMs - ['readMs', 'pacingMs', 'retryMs', 'settlementMs'].reduce((n, k) => n + this.stats[k], 0));
41 }
42 async timed(kind, fn) {
43 const start = this.now();
44 try {
45 return await fn();
46 } finally {
47 this.stats[kind] += Math.max(0, this.now() - start);
48 }
49 }
50 async wait(ms, kind) {
51 if (ms > this.remaining()) throw new ReadBackBudgetError();
52 await this.timed(kind, () => this.sleep(ms));
53 if (this.remaining() <= 0) throw new ReadBackBudgetError();
54 }
55 async settle() {
56 if (this.settlementMs - this.stats.settlementMs < 60000) throw new ReadBackBudgetError('media-settlement-exhausted');
57 await this.wait(60000, 'settlementMs');
58 }
59 send(request) {
60 const task = this.queue.then(() => this.attempt(request));
61 this.queue = task.catch(() => {});
62 return task;
63 }
64 async attempt(request) {
65 for (let i = 0; i < 4; i++) {
66 if (this.remaining() <= 0) throw new ReadBackBudgetError();
67 const delay = Math.max(0, this.nextAt - this.now());
68 if (delay) await this.wait(delay, 'pacingMs');
69 this.stats.attempts++;
70 try {
71 const result = await this.timed('readMs', () => this.client.send(require('../resources/rp-target-wix/lib/wix-writers').buildBoundedReadRequest(request, Math.min(10000, this.remaining()))));
72 this.nextAt = this.now() + this.pacingMs;
73 return result;
74 } catch (e) {
75 this.nextAt = this.now() + this.pacingMs;
76 if (e.readTimedOut) this.stats.timeouts++;
77 if (e.status === 401 || e.status === 403) {
78 e.code = e.status === 401 ? 'auth-failed' : 'permission-denied';
79 throw e;
80 }
81 const retry = e.readTimedOut || !e.status || [429, 500, 502, 503, 504].includes(e.status) || e.status >= 200 && e.status < 300 && e.outcomeKnown === false || e.status === 404 && e.outcomeKnown === true;
82 if (!retry || i === 3) {
83 e.code ||= retry ? 'read-unavailable' : 'read-rejected';
84 throw e;
85 }
86 await this.wait(e.status === 429 && e.retryAfterMs != null ? e.retryAfterMs : 1000 * 2 ** i, 'retryMs');
87 }
88 }
89 }
90}
91module.exports = {
92 ReadBackReads,
93 ReadBackBudgetError
94};