@prisma/composer-prisma-cloud 0.1.0-dev.13 → 0.1.0-dev.15

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.
@@ -431,15 +431,20 @@ function standardValidateSync(schema, value) {
431
431
  if (result.issues !== void 0) throw new Error(`config param validation failed: ${result.issues.map((issue) => issue.message).join("; ")}`);
432
432
  return result.value;
433
433
  }
434
- async function standardValidate(schema, value) {
435
- const result = await schema["~standard"].validate(value);
436
- if (result.issues !== void 0) throw new Error(`Schema validation failed: ${result.issues.map((issue) => issue.message).join("; ")}`);
437
- return result.value;
434
+ /** Bounded jittered backoff for retrying a dropped call. `maxRetries` is retries after the first attempt. */
435
+ const RETRY = {
436
+ initialDelayMs: 250,
437
+ multiplier: 2,
438
+ maxDelayMs: 5e3,
439
+ maxRetries: 5
440
+ };
441
+ const IDEMPOTENCY_KEY_HEADER$1 = "Idempotency-Key";
442
+ function sleep(ms) {
443
+ return new Promise((resolve) => setTimeout(resolve, ms));
438
444
  }
439
- /** `<base>/rpc/<method>`, preserving a base URL's own path (e.g. a mount point). */
440
- function methodUrl(base, method) {
441
- const normalizedBase = base.endsWith("/") ? base : `${base}/`;
442
- return new URL(`rpc/${method}`, normalizedBase).toString();
445
+ /** Whether a non-OK response is safe to retry: 429 or any 5xx, never another 4xx. */
446
+ function isRetryableStatus(status) {
447
+ return status === 429 || status >= 500;
443
448
  }
444
449
  /** The server's `{ error }` body, if the response has one — undefined otherwise. */
445
450
  async function errorDetail(res) {
@@ -450,22 +455,56 @@ async function errorDetail(res) {
450
455
  return;
451
456
  }
452
457
  }
458
+ /** `<base>/rpc/<method>`, preserving a base URL's own path (e.g. a mount point). */
459
+ function methodUrl(base, method) {
460
+ const normalizedBase = base.endsWith("/") ? base : `${base}/`;
461
+ return new URL(`rpc/${method}`, normalizedBase).toString();
462
+ }
463
+ /**
464
+ * Sends one call over `send`, retrying a thrown error, 429, or 5xx with
465
+ * full-jitter backoff. `buildRequest` runs per attempt but carries the same
466
+ * idempotency key each time — only the transport call repeats, not the key.
467
+ */
468
+ async function callWithRetry(send, buildRequest, method) {
469
+ let delay = RETRY.initialDelayMs;
470
+ let retries = 0;
471
+ for (;;) {
472
+ let res;
473
+ try {
474
+ res = await send(buildRequest());
475
+ } catch (err) {
476
+ if (retries >= RETRY.maxRetries) throw err;
477
+ retries += 1;
478
+ await sleep(Math.random() * delay);
479
+ delay = Math.min(delay * RETRY.multiplier, RETRY.maxDelayMs);
480
+ continue;
481
+ }
482
+ if (res.ok) return res.json();
483
+ if (!isRetryableStatus(res.status) || retries >= RETRY.maxRetries) {
484
+ const detail = await errorDetail(res);
485
+ throw new Error(`RPC call "${method}" failed: ${res.status} ${res.statusText}` + (detail !== void 0 ? ` — ${detail}` : ""));
486
+ }
487
+ retries += 1;
488
+ await sleep(Math.random() * delay);
489
+ delay = Math.min(delay * RETRY.multiplier, RETRY.maxDelayMs);
490
+ }
491
+ }
453
492
  function makeClient(contract, url, opts) {
454
493
  const send = opts?.fetch ?? fetch;
455
- const headers = { "content-type": "application/json" };
456
- if (opts?.serviceKey !== void 0) headers["Authorization"] = `Bearer ${opts.serviceKey}`;
494
+ const baseHeaders = { "content-type": "application/json" };
495
+ if (opts?.serviceKey !== void 0) baseHeaders["Authorization"] = `Bearer ${opts.serviceKey}`;
457
496
  const client = {};
458
- for (const [method, schemas] of Object.entries(blindCast(contract.__cmp))) client[method] = async (input) => {
459
- const res = await send(new Request(methodUrl(url, method), {
497
+ for (const method of Object.keys(contract.__cmp)) client[method] = async (input) => {
498
+ const idempotencyKey = crypto.randomUUID();
499
+ const body = JSON.stringify(input);
500
+ return callWithRetry(send, () => new Request(methodUrl(url, method), {
460
501
  method: "POST",
461
- headers,
462
- body: JSON.stringify(input)
463
- }));
464
- if (!res.ok) {
465
- const detail = await errorDetail(res);
466
- throw new Error(`RPC call "${method}" failed: ${res.status} ${res.statusText}` + (detail !== void 0 ? ` — ${detail}` : ""));
467
- }
468
- return standardValidate(schemas.output, await res.json());
502
+ headers: {
503
+ ...baseHeaders,
504
+ [IDEMPOTENCY_KEY_HEADER$1]: idempotencyKey
505
+ },
506
+ body
507
+ }), method);
469
508
  };
470
509
  return blindCast(client);
471
510
  }