@lingo.dev/_sdk 0.16.4 → 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 CHANGED
@@ -114,7 +114,13 @@ var engineParamsSchema = _zod2.default.object({
114
114
  apiUrl: _zod2.default.string().url().default("https://api.lingo.dev"),
115
115
  batchSize: _zod2.default.number().int().gt(0).lte(250).default(25),
116
116
  idealBatchItemSize: _zod2.default.number().int().gt(0).lte(2500).default(250),
117
- 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)
118
124
  }).passthrough();
119
125
  var normalizedLocaleCodeSchema = __spec.localeCodeSchema.transform(__spec.normalizeLocale);
120
126
  var payloadSchema = _zod2.default.record(_zod2.default.string(), _zod2.default.any());
@@ -166,6 +172,74 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
166
172
  }
167
173
  throw new Error(context ? `${context}: ${msg}` : msg);
168
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
+ }
169
243
  /**
170
244
  * Create a new LingoDotDevEngine instance
171
245
  * @param config - Configuration options for the Engine
@@ -233,12 +307,16 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
233
307
  metadata: filePath ? { filePath } : void 0,
234
308
  ...this.config.engineId && { engineId: this.config.engineId }
235
309
  };
236
- const res = await fetch(url, {
237
- method: "POST",
238
- headers: this.headers,
239
- body: JSON.stringify(body, null, 2),
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
+ },
240
318
  signal
241
- });
319
+ );
242
320
  await _LingoDotDevEngine.throwOnHttpError(res);
243
321
  const jsonResponse = await res.json();
244
322
  if (!jsonResponse.data && jsonResponse.error) {
@@ -579,7 +657,7 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
579
657
  break;
580
658
  }
581
659
  const siblings = Array.from(parent.childNodes).filter(
582
- (n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _4 => _4.textContent, 'optionalAccess', _5 => _5.trim, 'call', _6 => _6()])
660
+ (n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _14 => _14.textContent, 'optionalAccess', _15 => _15.trim, 'call', _16 => _16()])
583
661
  );
584
662
  const index = siblings.indexOf(current);
585
663
  if (index !== -1) {
@@ -599,7 +677,7 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
599
677
  parent = parent.parentElement;
600
678
  }
601
679
  if (node.nodeType === 3) {
602
- const text = _optionalChain([node, 'access', _7 => _7.textContent, 'optionalAccess', _8 => _8.trim, 'call', _9 => _9()]) || "";
680
+ const text = _optionalChain([node, 'access', _17 => _17.textContent, 'optionalAccess', _18 => _18.trim, 'call', _19 => _19()]) || "";
603
681
  if (text) {
604
682
  extractedContent[getPath(node)] = text;
605
683
  }
@@ -614,15 +692,15 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
614
692
  }
615
693
  });
616
694
  Array.from(element.childNodes).filter(
617
- (n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _10 => _10.textContent, 'optionalAccess', _11 => _11.trim, 'call', _12 => _12()])
695
+ (n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _20 => _20.textContent, 'optionalAccess', _21 => _21.trim, 'call', _22 => _22()])
618
696
  ).forEach(processNode);
619
697
  }
620
698
  };
621
699
  Array.from(document.head.childNodes).filter(
622
- (n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _13 => _13.textContent, 'optionalAccess', _14 => _14.trim, 'call', _15 => _15()])
700
+ (n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _23 => _23.textContent, 'optionalAccess', _24 => _24.trim, 'call', _25 => _25()])
623
701
  ).forEach(processNode);
624
702
  Array.from(document.body.childNodes).filter(
625
- (n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _16 => _16.textContent, 'optionalAccess', _17 => _17.trim, 'call', _18 => _18()])
703
+ (n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _26 => _26.textContent, 'optionalAccess', _27 => _27.trim, 'call', _28 => _28()])
626
704
  ).forEach(processNode);
627
705
  const localizedContent = await this._localizeRaw(
628
706
  extractedContent,
@@ -638,10 +716,10 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
638
716
  let current = parent;
639
717
  for (const index of indices) {
640
718
  const siblings = Array.from(parent.childNodes).filter(
641
- (n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _19 => _19.textContent, 'optionalAccess', _20 => _20.trim, 'call', _21 => _21()])
719
+ (n) => n.nodeType === 1 || n.nodeType === 3 && _optionalChain([n, 'access', _29 => _29.textContent, 'optionalAccess', _30 => _30.trim, 'call', _31 => _31()])
642
720
  );
643
721
  current = siblings[parseInt(index)] || null;
644
- if (_optionalChain([current, 'optionalAccess', _22 => _22.nodeType]) === 1) {
722
+ if (_optionalChain([current, 'optionalAccess', _32 => _32.nodeType]) === 1) {
645
723
  parent = current;
646
724
  }
647
725
  }
@@ -730,7 +808,7 @@ var LingoDotDevEngine = (_class = class _LingoDotDevEngine {
730
808
  });
731
809
  if (res.ok) {
732
810
  const payload = await res.json();
733
- if (!_optionalChain([payload, 'optionalAccess', _23 => _23.email])) {
811
+ if (!_optionalChain([payload, 'optionalAccess', _33 => _33.email])) {
734
812
  return null;
735
813
  }
736
814
  return {
package/build/index.d.cts CHANGED
@@ -7,6 +7,8 @@ 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<{
@@ -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,6 +7,8 @@ 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<{
@@ -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
@@ -114,7 +114,13 @@ var engineParamsSchema = Z.object({
114
114
  apiUrl: Z.string().url().default("https://api.lingo.dev"),
115
115
  batchSize: Z.number().int().gt(0).lte(250).default(25),
116
116
  idealBatchItemSize: Z.number().int().gt(0).lte(2500).default(250),
117
- 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)
118
124
  }).passthrough();
119
125
  var normalizedLocaleCodeSchema = localeCodeSchema.transform(normalizeLocale);
120
126
  var payloadSchema = Z.record(Z.string(), Z.any());
@@ -166,6 +172,74 @@ var LingoDotDevEngine = class _LingoDotDevEngine {
166
172
  }
167
173
  throw new Error(context ? `${context}: ${msg}` : msg);
168
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
+ }
169
243
  /**
170
244
  * Create a new LingoDotDevEngine instance
171
245
  * @param config - Configuration options for the Engine
@@ -233,12 +307,16 @@ var LingoDotDevEngine = class _LingoDotDevEngine {
233
307
  metadata: filePath ? { filePath } : void 0,
234
308
  ...this.config.engineId && { engineId: this.config.engineId }
235
309
  };
236
- const res = await fetch(url, {
237
- method: "POST",
238
- headers: this.headers,
239
- body: JSON.stringify(body, null, 2),
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
+ },
240
318
  signal
241
- });
319
+ );
242
320
  await _LingoDotDevEngine.throwOnHttpError(res);
243
321
  const jsonResponse = await res.json();
244
322
  if (!jsonResponse.data && jsonResponse.error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingo.dev/_sdk",
3
- "version": "0.16.4",
3
+ "version": "0.16.5",
4
4
  "description": "Lingo.dev JS SDK",
5
5
  "private": false,
6
6
  "repository": {