@lingo.dev/_sdk 0.16.3 → 0.16.5
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/build/index.cjs +100 -18
- package/build/index.d.cts +27 -3
- package/build/index.d.ts +27 -3
- package/build/index.mjs +93 -11
- package/package.json +1 -1
package/build/index.cjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;// src/index.ts
|
|
2
2
|
var _zod = require('zod'); var _zod2 = _interopRequireDefault(_zod);
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
3
6
|
var __spec = require('@lingo.dev/_spec');
|
|
4
7
|
var _cuid2 = require('@paralleldrive/cuid2');
|
|
5
8
|
|
|
@@ -111,14 +114,21 @@ var engineParamsSchema = _zod2.default.object({
|
|
|
111
114
|
apiUrl: _zod2.default.string().url().default("https://api.lingo.dev"),
|
|
112
115
|
batchSize: _zod2.default.number().int().gt(0).lte(250).default(25),
|
|
113
116
|
idealBatchItemSize: _zod2.default.number().int().gt(0).lte(2500).default(250),
|
|
114
|
-
engineId: _zod2.default.string().optional()
|
|
117
|
+
engineId: _zod2.default.string().optional(),
|
|
118
|
+
// Number of times a localization request is retried after a transient
|
|
119
|
+
// failure (5xx response or network error). `0` disables retries.
|
|
120
|
+
maxRetries: _zod2.default.number().int().gte(0).default(3),
|
|
121
|
+
// Base delay (ms) for the exponential backoff between retries. The actual
|
|
122
|
+
// wait grows as `retryDelayMs * 2 ** attempt` plus a small random jitter.
|
|
123
|
+
retryDelayMs: _zod2.default.number().int().gte(0).default(500)
|
|
115
124
|
}).passthrough();
|
|
125
|
+
var normalizedLocaleCodeSchema = __spec.localeCodeSchema.transform(__spec.normalizeLocale);
|
|
116
126
|
var payloadSchema = _zod2.default.record(_zod2.default.string(), _zod2.default.any());
|
|
117
|
-
var referenceSchema = _zod2.default.record(
|
|
127
|
+
var referenceSchema = _zod2.default.record(normalizedLocaleCodeSchema, payloadSchema);
|
|
118
128
|
var hintsSchema = _zod2.default.record(_zod2.default.string(), _zod2.default.array(_zod2.default.string()));
|
|
119
129
|
var localizationParamsSchema = _zod2.default.object({
|
|
120
|
-
sourceLocale: _zod2.default.union([
|
|
121
|
-
targetLocale:
|
|
130
|
+
sourceLocale: _zod2.default.union([normalizedLocaleCodeSchema, _zod2.default.null()]),
|
|
131
|
+
targetLocale: normalizedLocaleCodeSchema,
|
|
122
132
|
fast: _zod2.default.boolean().optional(),
|
|
123
133
|
reference: referenceSchema.optional(),
|
|
124
134
|
hints: hintsSchema.optional(),
|
|
@@ -162,6 +172,74 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
162
172
|
}
|
|
163
173
|
throw new Error(context ? `${context}: ${msg}` : msg);
|
|
164
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Sleep for `ms` milliseconds, rejecting early if the signal is aborted.
|
|
177
|
+
*/
|
|
178
|
+
static sleep(ms, signal) {
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
if (_optionalChain([signal, 'optionalAccess', _4 => _4.aborted])) {
|
|
181
|
+
reject(new Error("Operation was aborted"));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const onAbort = () => {
|
|
185
|
+
clearTimeout(timer);
|
|
186
|
+
reject(new Error("Operation was aborted"));
|
|
187
|
+
};
|
|
188
|
+
const timer = setTimeout(() => {
|
|
189
|
+
_optionalChain([signal, 'optionalAccess', _5 => _5.removeEventListener, 'call', _6 => _6("abort", onAbort)]);
|
|
190
|
+
resolve();
|
|
191
|
+
}, ms);
|
|
192
|
+
_optionalChain([signal, 'optionalAccess', _7 => _7.addEventListener, 'call', _8 => _8("abort", onAbort, { once: true })]);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Exponential backoff with full jitter: a random delay in
|
|
197
|
+
* `[0, retryDelayMs * 2 ** attempt]`. Jitter spreads out retries from many
|
|
198
|
+
* clients so a recovering server is not hit by a synchronized wave.
|
|
199
|
+
*/
|
|
200
|
+
backoffDelay(attempt) {
|
|
201
|
+
const ceiling = this.config.retryDelayMs * 2 ** attempt;
|
|
202
|
+
return Math.round(Math.random() * ceiling);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Perform a fetch, retrying on transient failures (5xx responses and
|
|
206
|
+
* network errors) with exponential backoff. The retry decision is made on
|
|
207
|
+
* the HTTP status code (>= 500), so non-retryable responses (e.g. 4xx) are
|
|
208
|
+
* returned immediately for the caller to handle. Aborted requests are never
|
|
209
|
+
* retried.
|
|
210
|
+
* @param url - The request URL
|
|
211
|
+
* @param init - Fetch init options (should include the AbortSignal)
|
|
212
|
+
* @param signal - Optional AbortSignal used to short-circuit retries
|
|
213
|
+
* @returns The fetch Response (which may still be a non-retryable error)
|
|
214
|
+
*/
|
|
215
|
+
async fetchWithRetry(url, init, signal) {
|
|
216
|
+
const { maxRetries } = this.config;
|
|
217
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
218
|
+
if (_optionalChain([signal, 'optionalAccess', _9 => _9.aborted])) {
|
|
219
|
+
throw new Error("Operation was aborted");
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const res = await fetch(url, init);
|
|
223
|
+
const isServerError = res.status >= 500 && res.status < 600;
|
|
224
|
+
if (isServerError && attempt < maxRetries) {
|
|
225
|
+
await _optionalChain([res, 'access', _10 => _10.body, 'optionalAccess', _11 => _11.cancel, 'call', _12 => _12()]);
|
|
226
|
+
await _LingoDotDevEngine.sleep(this.backoffDelay(attempt), signal);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
return res;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (_optionalChain([signal, 'optionalAccess', _13 => _13.aborted])) {
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
if (attempt < maxRetries) {
|
|
235
|
+
await _LingoDotDevEngine.sleep(this.backoffDelay(attempt), signal);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
throw new Error("Localization request failed after exhausting retries");
|
|
242
|
+
}
|
|
165
243
|
/**
|
|
166
244
|
* Create a new LingoDotDevEngine instance
|
|
167
245
|
* @param config - Configuration options for the Engine
|
|
@@ -191,7 +269,7 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
191
269
|
const processedPayloadChunk = await this.localizeChunk(
|
|
192
270
|
finalParams.sourceLocale,
|
|
193
271
|
finalParams.targetLocale,
|
|
194
|
-
{ data: chunk, reference:
|
|
272
|
+
{ data: chunk, reference: finalParams.reference, hints: params.hints },
|
|
195
273
|
params.fast || false,
|
|
196
274
|
params.filePath,
|
|
197
275
|
params.triggerType,
|
|
@@ -229,12 +307,16 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
229
307
|
metadata: filePath ? { filePath } : void 0,
|
|
230
308
|
...this.config.engineId && { engineId: this.config.engineId }
|
|
231
309
|
};
|
|
232
|
-
const res = await
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
310
|
+
const res = await this.fetchWithRetry(
|
|
311
|
+
url,
|
|
312
|
+
{
|
|
313
|
+
method: "POST",
|
|
314
|
+
headers: this.headers,
|
|
315
|
+
body: JSON.stringify(body, null, 2),
|
|
316
|
+
signal
|
|
317
|
+
},
|
|
236
318
|
signal
|
|
237
|
-
|
|
319
|
+
);
|
|
238
320
|
await _LingoDotDevEngine.throwOnHttpError(res);
|
|
239
321
|
const jsonResponse = await res.json();
|
|
240
322
|
if (!jsonResponse.data && jsonResponse.error) {
|
|
@@ -575,7 +657,7 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
575
657
|
break;
|
|
576
658
|
}
|
|
577
659
|
const siblings = Array.from(parent.childNodes).filter(
|
|
578
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access',
|
|
660
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _14 => _14.textContent, 'optionalAccess', _15 => _15.trim, 'call', _16 => _16()])
|
|
579
661
|
);
|
|
580
662
|
const index = siblings.indexOf(current);
|
|
581
663
|
if (index !== -1) {
|
|
@@ -595,7 +677,7 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
595
677
|
parent = parent.parentElement;
|
|
596
678
|
}
|
|
597
679
|
if (node.nodeType === 3) {
|
|
598
|
-
const text = _optionalChain([node, 'access',
|
|
680
|
+
const text = _optionalChain([node, 'access', _17 => _17.textContent, 'optionalAccess', _18 => _18.trim, 'call', _19 => _19()]) || "";
|
|
599
681
|
if (text) {
|
|
600
682
|
extractedContent[getPath(node)] = text;
|
|
601
683
|
}
|
|
@@ -610,15 +692,15 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
610
692
|
}
|
|
611
693
|
});
|
|
612
694
|
Array.from(element.childNodes).filter(
|
|
613
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access',
|
|
695
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _20 => _20.textContent, 'optionalAccess', _21 => _21.trim, 'call', _22 => _22()])
|
|
614
696
|
).forEach(processNode);
|
|
615
697
|
}
|
|
616
698
|
};
|
|
617
699
|
Array.from(document.head.childNodes).filter(
|
|
618
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access',
|
|
700
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _23 => _23.textContent, 'optionalAccess', _24 => _24.trim, 'call', _25 => _25()])
|
|
619
701
|
).forEach(processNode);
|
|
620
702
|
Array.from(document.body.childNodes).filter(
|
|
621
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access',
|
|
703
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _26 => _26.textContent, 'optionalAccess', _27 => _27.trim, 'call', _28 => _28()])
|
|
622
704
|
).forEach(processNode);
|
|
623
705
|
const localizedContent = await this._localizeRaw(
|
|
624
706
|
extractedContent,
|
|
@@ -634,10 +716,10 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
634
716
|
let current = parent;
|
|
635
717
|
for (const index of indices) {
|
|
636
718
|
const siblings = Array.from(parent.childNodes).filter(
|
|
637
|
-
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access',
|
|
719
|
+
(n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _29 => _29.textContent, 'optionalAccess', _30 => _30.trim, 'call', _31 => _31()])
|
|
638
720
|
);
|
|
639
721
|
current = siblings[parseInt(index)] || null;
|
|
640
|
-
if (_optionalChain([current, 'optionalAccess',
|
|
722
|
+
if (_optionalChain([current, 'optionalAccess', _32 => _32.nodeType]) === 1) {
|
|
641
723
|
parent = current;
|
|
642
724
|
}
|
|
643
725
|
}
|
|
@@ -726,7 +808,7 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
|
|
|
726
808
|
});
|
|
727
809
|
if (res.ok) {
|
|
728
810
|
const payload = await res.json();
|
|
729
|
-
if (!_optionalChain([payload, 'optionalAccess',
|
|
811
|
+
if (!_optionalChain([payload, 'optionalAccess', _33 => _33.email])) {
|
|
730
812
|
return null;
|
|
731
813
|
}
|
|
732
814
|
return {
|
package/build/index.d.cts
CHANGED
|
@@ -7,13 +7,15 @@ declare const engineParamsSchema: Z.ZodObject<{
|
|
|
7
7
|
batchSize: Z.ZodDefault<Z.ZodNumber>;
|
|
8
8
|
idealBatchItemSize: Z.ZodDefault<Z.ZodNumber>;
|
|
9
9
|
engineId: Z.ZodOptional<Z.ZodString>;
|
|
10
|
+
maxRetries: Z.ZodDefault<Z.ZodNumber>;
|
|
11
|
+
retryDelayMs: Z.ZodDefault<Z.ZodNumber>;
|
|
10
12
|
}, Z.core.$loose>;
|
|
11
13
|
declare const payloadSchema: Z.ZodRecord<Z.ZodString, Z.ZodAny>;
|
|
12
14
|
declare const localizationParamsSchema: Z.ZodObject<{
|
|
13
|
-
sourceLocale: Z.ZodUnion<readonly [Z.ZodString, Z.ZodNull]>;
|
|
14
|
-
targetLocale: Z.ZodString
|
|
15
|
+
sourceLocale: Z.ZodUnion<readonly [Z.ZodPipe<Z.ZodString, Z.ZodTransform<string, string>>, Z.ZodNull]>;
|
|
16
|
+
targetLocale: Z.ZodPipe<Z.ZodString, Z.ZodTransform<string, string>>;
|
|
15
17
|
fast: Z.ZodOptional<Z.ZodBoolean>;
|
|
16
|
-
reference: Z.ZodOptional<Z.ZodRecord<Z.ZodString, Z.ZodRecord<Z.ZodString, Z.ZodAny>>>;
|
|
18
|
+
reference: Z.ZodOptional<Z.ZodRecord<Z.ZodPipe<Z.ZodString, Z.ZodTransform<string, string>>, Z.ZodRecord<Z.ZodString, Z.ZodAny>>>;
|
|
17
19
|
hints: Z.ZodOptional<Z.ZodRecord<Z.ZodString, Z.ZodArray<Z.ZodString>>>;
|
|
18
20
|
filePath: Z.ZodOptional<Z.ZodString>;
|
|
19
21
|
triggerType: Z.ZodOptional<Z.ZodEnum<{
|
|
@@ -32,6 +34,28 @@ declare class LingoDotDevEngine {
|
|
|
32
34
|
private get headers();
|
|
33
35
|
private static extractErrorMessage;
|
|
34
36
|
private static throwOnHttpError;
|
|
37
|
+
/**
|
|
38
|
+
* Sleep for `ms` milliseconds, rejecting early if the signal is aborted.
|
|
39
|
+
*/
|
|
40
|
+
private static sleep;
|
|
41
|
+
/**
|
|
42
|
+
* Exponential backoff with full jitter: a random delay in
|
|
43
|
+
* `[0, retryDelayMs * 2 ** attempt]`. Jitter spreads out retries from many
|
|
44
|
+
* clients so a recovering server is not hit by a synchronized wave.
|
|
45
|
+
*/
|
|
46
|
+
private backoffDelay;
|
|
47
|
+
/**
|
|
48
|
+
* Perform a fetch, retrying on transient failures (5xx responses and
|
|
49
|
+
* network errors) with exponential backoff. The retry decision is made on
|
|
50
|
+
* the HTTP status code (>= 500), so non-retryable responses (e.g. 4xx) are
|
|
51
|
+
* returned immediately for the caller to handle. Aborted requests are never
|
|
52
|
+
* retried.
|
|
53
|
+
* @param url - The request URL
|
|
54
|
+
* @param init - Fetch init options (should include the AbortSignal)
|
|
55
|
+
* @param signal - Optional AbortSignal used to short-circuit retries
|
|
56
|
+
* @returns The fetch Response (which may still be a non-retryable error)
|
|
57
|
+
*/
|
|
58
|
+
private fetchWithRetry;
|
|
35
59
|
/**
|
|
36
60
|
* Create a new LingoDotDevEngine instance
|
|
37
61
|
* @param config - Configuration options for the Engine
|
package/build/index.d.ts
CHANGED
|
@@ -7,13 +7,15 @@ declare const engineParamsSchema: Z.ZodObject<{
|
|
|
7
7
|
batchSize: Z.ZodDefault<Z.ZodNumber>;
|
|
8
8
|
idealBatchItemSize: Z.ZodDefault<Z.ZodNumber>;
|
|
9
9
|
engineId: Z.ZodOptional<Z.ZodString>;
|
|
10
|
+
maxRetries: Z.ZodDefault<Z.ZodNumber>;
|
|
11
|
+
retryDelayMs: Z.ZodDefault<Z.ZodNumber>;
|
|
10
12
|
}, Z.core.$loose>;
|
|
11
13
|
declare const payloadSchema: Z.ZodRecord<Z.ZodString, Z.ZodAny>;
|
|
12
14
|
declare const localizationParamsSchema: Z.ZodObject<{
|
|
13
|
-
sourceLocale: Z.ZodUnion<readonly [Z.ZodString, Z.ZodNull]>;
|
|
14
|
-
targetLocale: Z.ZodString
|
|
15
|
+
sourceLocale: Z.ZodUnion<readonly [Z.ZodPipe<Z.ZodString, Z.ZodTransform<string, string>>, Z.ZodNull]>;
|
|
16
|
+
targetLocale: Z.ZodPipe<Z.ZodString, Z.ZodTransform<string, string>>;
|
|
15
17
|
fast: Z.ZodOptional<Z.ZodBoolean>;
|
|
16
|
-
reference: Z.ZodOptional<Z.ZodRecord<Z.ZodString, Z.ZodRecord<Z.ZodString, Z.ZodAny>>>;
|
|
18
|
+
reference: Z.ZodOptional<Z.ZodRecord<Z.ZodPipe<Z.ZodString, Z.ZodTransform<string, string>>, Z.ZodRecord<Z.ZodString, Z.ZodAny>>>;
|
|
17
19
|
hints: Z.ZodOptional<Z.ZodRecord<Z.ZodString, Z.ZodArray<Z.ZodString>>>;
|
|
18
20
|
filePath: Z.ZodOptional<Z.ZodString>;
|
|
19
21
|
triggerType: Z.ZodOptional<Z.ZodEnum<{
|
|
@@ -32,6 +34,28 @@ declare class LingoDotDevEngine {
|
|
|
32
34
|
private get headers();
|
|
33
35
|
private static extractErrorMessage;
|
|
34
36
|
private static throwOnHttpError;
|
|
37
|
+
/**
|
|
38
|
+
* Sleep for `ms` milliseconds, rejecting early if the signal is aborted.
|
|
39
|
+
*/
|
|
40
|
+
private static sleep;
|
|
41
|
+
/**
|
|
42
|
+
* Exponential backoff with full jitter: a random delay in
|
|
43
|
+
* `[0, retryDelayMs * 2 ** attempt]`. Jitter spreads out retries from many
|
|
44
|
+
* clients so a recovering server is not hit by a synchronized wave.
|
|
45
|
+
*/
|
|
46
|
+
private backoffDelay;
|
|
47
|
+
/**
|
|
48
|
+
* Perform a fetch, retrying on transient failures (5xx responses and
|
|
49
|
+
* network errors) with exponential backoff. The retry decision is made on
|
|
50
|
+
* the HTTP status code (>= 500), so non-retryable responses (e.g. 4xx) are
|
|
51
|
+
* returned immediately for the caller to handle. Aborted requests are never
|
|
52
|
+
* retried.
|
|
53
|
+
* @param url - The request URL
|
|
54
|
+
* @param init - Fetch init options (should include the AbortSignal)
|
|
55
|
+
* @param signal - Optional AbortSignal used to short-circuit retries
|
|
56
|
+
* @returns The fetch Response (which may still be a non-retryable error)
|
|
57
|
+
*/
|
|
58
|
+
private fetchWithRetry;
|
|
35
59
|
/**
|
|
36
60
|
* Create a new LingoDotDevEngine instance
|
|
37
61
|
* @param config - Configuration options for the Engine
|
package/build/index.mjs
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import Z from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
localeCodeSchema,
|
|
5
|
+
normalizeLocale
|
|
6
|
+
} from "@lingo.dev/_spec";
|
|
4
7
|
import { createId } from "@paralleldrive/cuid2";
|
|
5
8
|
|
|
6
9
|
// src/utils/observability.ts
|
|
@@ -111,14 +114,21 @@ var engineParamsSchema = Z.object({
|
|
|
111
114
|
apiUrl: Z.string().url().default("https://api.lingo.dev"),
|
|
112
115
|
batchSize: Z.number().int().gt(0).lte(250).default(25),
|
|
113
116
|
idealBatchItemSize: Z.number().int().gt(0).lte(2500).default(250),
|
|
114
|
-
engineId: Z.string().optional()
|
|
117
|
+
engineId: Z.string().optional(),
|
|
118
|
+
// Number of times a localization request is retried after a transient
|
|
119
|
+
// failure (5xx response or network error). `0` disables retries.
|
|
120
|
+
maxRetries: Z.number().int().gte(0).default(3),
|
|
121
|
+
// Base delay (ms) for the exponential backoff between retries. The actual
|
|
122
|
+
// wait grows as `retryDelayMs * 2 ** attempt` plus a small random jitter.
|
|
123
|
+
retryDelayMs: Z.number().int().gte(0).default(500)
|
|
115
124
|
}).passthrough();
|
|
125
|
+
var normalizedLocaleCodeSchema = localeCodeSchema.transform(normalizeLocale);
|
|
116
126
|
var payloadSchema = Z.record(Z.string(), Z.any());
|
|
117
|
-
var referenceSchema = Z.record(
|
|
127
|
+
var referenceSchema = Z.record(normalizedLocaleCodeSchema, payloadSchema);
|
|
118
128
|
var hintsSchema = Z.record(Z.string(), Z.array(Z.string()));
|
|
119
129
|
var localizationParamsSchema = Z.object({
|
|
120
|
-
sourceLocale: Z.union([
|
|
121
|
-
targetLocale:
|
|
130
|
+
sourceLocale: Z.union([normalizedLocaleCodeSchema, Z.null()]),
|
|
131
|
+
targetLocale: normalizedLocaleCodeSchema,
|
|
122
132
|
fast: Z.boolean().optional(),
|
|
123
133
|
reference: referenceSchema.optional(),
|
|
124
134
|
hints: hintsSchema.optional(),
|
|
@@ -162,6 +172,74 @@ var LingoDotDevEngine = class _LingoDotDevEngine {
|
|
|
162
172
|
}
|
|
163
173
|
throw new Error(context ? `${context}: ${msg}` : msg);
|
|
164
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Sleep for `ms` milliseconds, rejecting early if the signal is aborted.
|
|
177
|
+
*/
|
|
178
|
+
static sleep(ms, signal) {
|
|
179
|
+
return new Promise((resolve, reject) => {
|
|
180
|
+
if (signal?.aborted) {
|
|
181
|
+
reject(new Error("Operation was aborted"));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const onAbort = () => {
|
|
185
|
+
clearTimeout(timer);
|
|
186
|
+
reject(new Error("Operation was aborted"));
|
|
187
|
+
};
|
|
188
|
+
const timer = setTimeout(() => {
|
|
189
|
+
signal?.removeEventListener("abort", onAbort);
|
|
190
|
+
resolve();
|
|
191
|
+
}, ms);
|
|
192
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Exponential backoff with full jitter: a random delay in
|
|
197
|
+
* `[0, retryDelayMs * 2 ** attempt]`. Jitter spreads out retries from many
|
|
198
|
+
* clients so a recovering server is not hit by a synchronized wave.
|
|
199
|
+
*/
|
|
200
|
+
backoffDelay(attempt) {
|
|
201
|
+
const ceiling = this.config.retryDelayMs * 2 ** attempt;
|
|
202
|
+
return Math.round(Math.random() * ceiling);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Perform a fetch, retrying on transient failures (5xx responses and
|
|
206
|
+
* network errors) with exponential backoff. The retry decision is made on
|
|
207
|
+
* the HTTP status code (>= 500), so non-retryable responses (e.g. 4xx) are
|
|
208
|
+
* returned immediately for the caller to handle. Aborted requests are never
|
|
209
|
+
* retried.
|
|
210
|
+
* @param url - The request URL
|
|
211
|
+
* @param init - Fetch init options (should include the AbortSignal)
|
|
212
|
+
* @param signal - Optional AbortSignal used to short-circuit retries
|
|
213
|
+
* @returns The fetch Response (which may still be a non-retryable error)
|
|
214
|
+
*/
|
|
215
|
+
async fetchWithRetry(url, init, signal) {
|
|
216
|
+
const { maxRetries } = this.config;
|
|
217
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
218
|
+
if (signal?.aborted) {
|
|
219
|
+
throw new Error("Operation was aborted");
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const res = await fetch(url, init);
|
|
223
|
+
const isServerError = res.status >= 500 && res.status < 600;
|
|
224
|
+
if (isServerError && attempt < maxRetries) {
|
|
225
|
+
await res.body?.cancel();
|
|
226
|
+
await _LingoDotDevEngine.sleep(this.backoffDelay(attempt), signal);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
return res;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (signal?.aborted) {
|
|
232
|
+
throw error;
|
|
233
|
+
}
|
|
234
|
+
if (attempt < maxRetries) {
|
|
235
|
+
await _LingoDotDevEngine.sleep(this.backoffDelay(attempt), signal);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
throw new Error("Localization request failed after exhausting retries");
|
|
242
|
+
}
|
|
165
243
|
/**
|
|
166
244
|
* Create a new LingoDotDevEngine instance
|
|
167
245
|
* @param config - Configuration options for the Engine
|
|
@@ -191,7 +269,7 @@ var LingoDotDevEngine = class _LingoDotDevEngine {
|
|
|
191
269
|
const processedPayloadChunk = await this.localizeChunk(
|
|
192
270
|
finalParams.sourceLocale,
|
|
193
271
|
finalParams.targetLocale,
|
|
194
|
-
{ data: chunk, reference:
|
|
272
|
+
{ data: chunk, reference: finalParams.reference, hints: params.hints },
|
|
195
273
|
params.fast || false,
|
|
196
274
|
params.filePath,
|
|
197
275
|
params.triggerType,
|
|
@@ -229,12 +307,16 @@ var LingoDotDevEngine = class _LingoDotDevEngine {
|
|
|
229
307
|
metadata: filePath ? { filePath } : void 0,
|
|
230
308
|
...this.config.engineId && { engineId: this.config.engineId }
|
|
231
309
|
};
|
|
232
|
-
const res = await
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
310
|
+
const res = await this.fetchWithRetry(
|
|
311
|
+
url,
|
|
312
|
+
{
|
|
313
|
+
method: "POST",
|
|
314
|
+
headers: this.headers,
|
|
315
|
+
body: JSON.stringify(body, null, 2),
|
|
316
|
+
signal
|
|
317
|
+
},
|
|
236
318
|
signal
|
|
237
|
-
|
|
319
|
+
);
|
|
238
320
|
await _LingoDotDevEngine.throwOnHttpError(res);
|
|
239
321
|
const jsonResponse = await res.json();
|
|
240
322
|
if (!jsonResponse.data && jsonResponse.error) {
|