@squadbase/vite-server 0.1.10-dev.cec85c2 → 0.1.10-dev.d23e546

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.
@@ -109,7 +109,7 @@ function createClient(params) {
109
109
  queryOptions.partitionKey = options.partitionKey;
110
110
  }
111
111
  const iterator = cont.items.query(sql, queryOptions);
112
- const { resources } = await iterator.fetchNext();
112
+ const { resources } = await iterator.fetchAll();
113
113
  return resources ?? [];
114
114
  } catch (err) {
115
115
  const msg = err instanceof Error ? err.message : String(err);
@@ -445,7 +445,7 @@ Use \`TOP n\` (not \`LIMIT n\`) to bound the result. Cross-partition queries are
445
445
  }
446
446
  }
447
447
  const iterator = cont.items.query(sql, queryOptions);
448
- const { resources, requestCharge } = await iterator.fetchNext();
448
+ const { resources, requestCharge } = await iterator.fetchAll();
449
449
  const documents = resources ?? [];
450
450
  const truncated = documents.length > MAX_DOCUMENTS;
451
451
  return {
@@ -522,7 +522,7 @@ export default async function handler(_c: Context) {
522
522
  - Queries always target a single container and reference items via the alias \`c\` (e.g. \`SELECT c.id, c.name FROM c WHERE c.status = 'active'\`).
523
523
  - Row limiting: use \`TOP n\` (e.g. \`SELECT TOP 100 * FROM c\`). \`LIMIT\` is **not** supported. \`OFFSET m LIMIT n\` is supported on newer accounts but \`TOP\` is the safe default.
524
524
  - System fields are prefixed with an underscore (\`c.id\`, \`c._ts\` for the modification timestamp, \`c._etag\`).
525
- - Aggregates: \`COUNT(1)\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\`, with \`GROUP BY\` (e.g. \`SELECT c.country, COUNT(1) AS n FROM c GROUP BY c.country\`).
525
+ - Aggregates: \`COUNT(1)\`, \`SUM\`, \`AVG\`, \`MIN\`, \`MAX\`, with \`GROUP BY\` (e.g. \`SELECT c.country, COUNT(1) AS n FROM c GROUP BY c.country\`). Cross-partition \`GROUP BY\` is fully supported \u2014 the connector drains all result pages internally, so trust the returned rows even for queries that span many partitions. Do **not** work around perceived missing rows by re-fetching all items and aggregating in TypeScript.
526
526
  - Joins: only **intra-document** joins via \`JOIN\` over arrays inside the same item; there is no cross-container/cross-document join.
527
527
  - Always bound results with \`TOP n\` and prefer scoped queries with a \`partitionKey\` value when possible \u2014 cross-partition queries cost more RUs.`,
528
528
  ja: `### \u30C4\u30FC\u30EB
@@ -568,7 +568,7 @@ export default async function handler(_c: Context) {
568
568
  - \u30AF\u30A8\u30EA\u306F\u5E38\u306B\u5358\u4E00\u30B3\u30F3\u30C6\u30CA\u3092\u5BFE\u8C61\u3068\u3057\u3001\u30A2\u30A4\u30C6\u30E0\u306F\u5225\u540D \`c\` \u3067\u53C2\u7167\u3057\u307E\u3059\uFF08\u4F8B: \`SELECT c.id, c.name FROM c WHERE c.status = 'active'\`\uFF09\u3002
569
569
  - \u884C\u6570\u5236\u9650: \`TOP n\` \u3092\u4F7F\u7528\u3057\u307E\u3059\uFF08\u4F8B: \`SELECT TOP 100 * FROM c\`\uFF09\u3002\`LIMIT\` \u306F **\u4F7F\u7528\u3067\u304D\u307E\u305B\u3093**\u3002\u65B0\u3057\u3044\u30A2\u30AB\u30A6\u30F3\u30C8\u3067\u306F \`OFFSET m LIMIT n\` \u304C\u4F7F\u3048\u307E\u3059\u304C\u3001\u5B89\u5168\u5074\u306E\u65E2\u5B9A\u306F \`TOP\` \u3067\u3059\u3002
570
570
  - \u30B7\u30B9\u30C6\u30E0\u30D5\u30A3\u30FC\u30EB\u30C9\u306F\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u59CB\u307E\u308A\uFF08\`c.id\`\u3001\u5909\u66F4\u6642\u523B\u306E \`c._ts\`\u3001\`c._etag\`\uFF09\u3002
571
- - \u96C6\u7D04: \`COUNT(1)\`\u3001\`SUM\`\u3001\`AVG\`\u3001\`MIN\`\u3001\`MAX\` \u3092 \`GROUP BY\` \u3068\u7D44\u307F\u5408\u308F\u305B\u307E\u3059\uFF08\u4F8B: \`SELECT c.country, COUNT(1) AS n FROM c GROUP BY c.country\`\uFF09\u3002
571
+ - \u96C6\u7D04: \`COUNT(1)\`\u3001\`SUM\`\u3001\`AVG\`\u3001\`MIN\`\u3001\`MAX\` \u3092 \`GROUP BY\` \u3068\u7D44\u307F\u5408\u308F\u305B\u307E\u3059\uFF08\u4F8B: \`SELECT c.country, COUNT(1) AS n FROM c GROUP BY c.country\`\uFF09\u3002\u30AF\u30ED\u30B9\u30D1\u30FC\u30C6\u30A3\u30B7\u30E7\u30F3\u306E \`GROUP BY\` \u3082\u5B8C\u5168\u306B\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u304A\u308A\u3001\u30B3\u30CD\u30AF\u30BF\u304C\u5185\u90E8\u3067\u5168\u7D50\u679C\u30DA\u30FC\u30B8\u3092\u30C9\u30EC\u30A4\u30F3\u3059\u308B\u305F\u3081\u3001\u591A\u30D1\u30FC\u30C6\u30A3\u30B7\u30E7\u30F3\u306B\u307E\u305F\u304C\u308B\u30AF\u30A8\u30EA\u3067\u3082\u8FD4\u3063\u3066\u304D\u305F\u884C\u6570\u3092\u305D\u306E\u307E\u307E\u4FE1\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002**\u300C\u7D50\u679C\u304C\u6B20\u3051\u3066\u3044\u308B\u3088\u3046\u306B\u898B\u3048\u308B\u300D\u304B\u3089\u3068\u3044\u3063\u3066\u5168\u4EF6\u53D6\u5F97\u2192TS \u5074\u3067\u96C6\u8A08\u3059\u308B\u8FC2\u56DE\u306F\u3057\u306A\u3044\u3053\u3068\u3002**
572
572
  - \u7D50\u5408: \u540C\u4E00\u30A2\u30A4\u30C6\u30E0\u5185\u306E\u914D\u5217\u306B\u5BFE\u3059\u308B **\u30A4\u30F3\u30C8\u30E9\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8** \`JOIN\` \u306E\u307F\u3067\u3001\u30B3\u30F3\u30C6\u30CA\u9593\uFF0F\u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u9593\u306E\u7D50\u5408\u306F\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u307E\u305B\u3093\u3002
573
573
  - \u7D50\u679C\u4EF6\u6570\u306F\u5FC5\u305A \`TOP n\` \u3067\u5236\u9650\u3057\u3001\u53EF\u80FD\u3067\u3042\u308C\u3070 \`partitionKey\` \u3067\u5358\u4E00\u30D1\u30FC\u30C6\u30A3\u30B7\u30E7\u30F3\u306B\u30B9\u30B3\u30FC\u30D7\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u30AF\u30ED\u30B9\u30D1\u30FC\u30C6\u30A3\u30B7\u30E7\u30F3\u30AF\u30A8\u30EA\u306F RU \u3092\u591A\u304F\u6D88\u8CBB\u3057\u307E\u3059\uFF09\u3002`
574
574
  },
@@ -42,6 +42,39 @@ var ParameterDefinition = class {
42
42
  }
43
43
  };
44
44
 
45
+ // ../connectors/src/connectors/google-ads/constants.ts
46
+ var VERSION_FALLBACK_ORDER = ["v22", "v23", "v24"];
47
+ var cachedWorkingVersion = VERSION_FALLBACK_ORDER[0];
48
+ var looksSunset = (contentType) => {
49
+ return contentType != null && contentType.toLowerCase().includes("text/html");
50
+ };
51
+ async function fetchGoogleAdsWithVersionFallback(attempt) {
52
+ const order = [
53
+ cachedWorkingVersion,
54
+ ...VERSION_FALLBACK_ORDER.filter((v) => v !== cachedWorkingVersion)
55
+ ];
56
+ let lastResponse = null;
57
+ for (let i = 0; i < order.length; i++) {
58
+ const version = order[i];
59
+ const baseUrl = `https://googleads.googleapis.com/${version}/`;
60
+ const response = await attempt(baseUrl);
61
+ const contentType = response.headers.get("content-type");
62
+ if (looksSunset(contentType) && i < order.length - 1) {
63
+ console.warn(
64
+ `[google-ads] version ${version} returned a sunset/HTML response (status ${response.status}); retrying with newer version`
65
+ );
66
+ lastResponse = response;
67
+ continue;
68
+ }
69
+ cachedWorkingVersion = version;
70
+ return response;
71
+ }
72
+ return lastResponse ?? new Response(null, {
73
+ status: 502,
74
+ statusText: "All Google Ads API versions returned sunset responses"
75
+ });
76
+ }
77
+
45
78
  // ../connectors/src/connectors/google-ads/parameters.ts
46
79
  var parameters = {
47
80
  customerId: new ParameterDefinition({
@@ -56,7 +89,6 @@ var parameters = {
56
89
  };
57
90
 
58
91
  // ../connectors/src/connectors/google-ads/sdk/index.ts
59
- var BASE_URL = "https://googleads.googleapis.com/v18/";
60
92
  function createClient(params, fetchFn = fetch) {
61
93
  const rawCustomerId = params[parameters.customerId.slug];
62
94
  const defaultCustomerId = rawCustomerId?.replace(/-/g, "") ?? "";
@@ -71,24 +103,26 @@ function createClient(params, fetchFn = fetch) {
71
103
  }
72
104
  function request(path2, init) {
73
105
  const resolvedPath = defaultCustomerId ? path2.replace(/\{customerId\}/g, defaultCustomerId) : path2;
74
- const url = `${BASE_URL}${resolvedPath}`;
75
106
  const headers = new Headers(init?.headers);
76
107
  if (defaultCustomerId) {
77
108
  headers.set("login-customer-id", defaultCustomerId);
78
109
  }
79
- return fetchFn(url, { ...init, headers });
110
+ return fetchGoogleAdsWithVersionFallback(
111
+ (baseUrl) => fetchFn(`${baseUrl}${resolvedPath}`, { ...init, headers })
112
+ );
80
113
  }
81
114
  async function search(query, customerId) {
82
115
  const cid = resolveCustomerId(customerId);
83
- const url = `${BASE_URL}customers/${cid}/googleAds:searchStream`;
84
116
  const headers = new Headers();
85
117
  headers.set("Content-Type", "application/json");
86
118
  headers.set("login-customer-id", cid);
87
- const response = await fetchFn(url, {
88
- method: "POST",
89
- headers,
90
- body: JSON.stringify({ query })
91
- });
119
+ const response = await fetchGoogleAdsWithVersionFallback(
120
+ (baseUrl) => fetchFn(`${baseUrl}customers/${cid}/googleAds:searchStream`, {
121
+ method: "POST",
122
+ headers,
123
+ body: JSON.stringify({ query })
124
+ })
125
+ );
92
126
  if (!response.ok) {
93
127
  const body = await response.text();
94
128
  throw new Error(
@@ -99,8 +133,9 @@ function createClient(params, fetchFn = fetch) {
99
133
  return data.flatMap((chunk) => chunk.results ?? []);
100
134
  }
101
135
  async function listAccessibleCustomers() {
102
- const url = `${BASE_URL}customers:listAccessibleCustomers`;
103
- const response = await fetchFn(url, { method: "GET" });
136
+ const response = await fetchGoogleAdsWithVersionFallback(
137
+ (baseUrl) => fetchFn(`${baseUrl}customers:listAccessibleCustomers`, { method: "GET" })
138
+ );
104
139
  if (!response.ok) {
105
140
  const body = await response.text();
106
141
  throw new Error(
@@ -270,7 +305,65 @@ var AUTH_TYPES = {
270
305
 
271
306
  // ../connectors/src/connectors/google-ads/tools/list-customers.ts
272
307
  import { z } from "zod";
273
- var BASE_URL2 = "https://googleads.googleapis.com/v18/";
308
+
309
+ // ../connectors/src/connectors/google-ads/error.ts
310
+ var parseJsonOrNull = (text) => {
311
+ if (text.length === 0) return null;
312
+ try {
313
+ return JSON.parse(text);
314
+ } catch {
315
+ return null;
316
+ }
317
+ };
318
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
319
+ var collectGoogleAdsErrorCodes = (details) => {
320
+ if (!Array.isArray(details)) return [];
321
+ const codes = [];
322
+ for (const detail of details) {
323
+ if (!isRecord(detail)) continue;
324
+ const errors = detail.errors;
325
+ if (!Array.isArray(errors)) continue;
326
+ for (const e of errors) {
327
+ if (!isRecord(e)) continue;
328
+ const errorCode = e.errorCode;
329
+ if (!isRecord(errorCode)) continue;
330
+ for (const [k, v] of Object.entries(errorCode)) {
331
+ if (typeof v === "string") codes.push(`${k}=${v}`);
332
+ }
333
+ }
334
+ }
335
+ return codes;
336
+ };
337
+ var extractGoogleAdsErrorMessage = (status, statusText, parsedBody, rawText) => {
338
+ const fallback = `HTTP ${status} ${statusText}`;
339
+ if (isRecord(parsedBody)) {
340
+ const errorField = parsedBody.error;
341
+ if (typeof errorField === "string") return errorField;
342
+ if (isRecord(errorField)) {
343
+ const message = typeof errorField.message === "string" ? errorField.message : null;
344
+ const errorStatus = typeof errorField.status === "string" ? errorField.status : null;
345
+ const codes = collectGoogleAdsErrorCodes(errorField.details);
346
+ const parts = [
347
+ fallback,
348
+ errorStatus,
349
+ message,
350
+ codes.length > 0 ? `[${codes.join(", ")}]` : null
351
+ ].filter((p) => p != null && p.length > 0);
352
+ if (parts.length > 1) return parts.join(": ");
353
+ }
354
+ if (typeof parsedBody.message === "string") {
355
+ return `${fallback}: ${parsedBody.message}`;
356
+ }
357
+ }
358
+ if (rawText.length > 0) {
359
+ const trimmed = rawText.trim();
360
+ const preview = trimmed.length > 500 ? `${trimmed.slice(0, 500)}\u2026` : trimmed;
361
+ return `${fallback}: ${preview}`;
362
+ }
363
+ return fallback;
364
+ };
365
+
366
+ // ../connectors/src/connectors/google-ads/tools/list-customers.ts
274
367
  var REQUEST_TIMEOUT_MS = 6e4;
275
368
  var cachedToken = null;
276
369
  async function getProxyToken(config) {
@@ -346,48 +439,60 @@ var listCustomersTool = new ConnectorTool({
346
439
  const controller = new AbortController();
347
440
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
348
441
  try {
349
- const response = await fetch(proxyUrl, {
350
- method: "POST",
351
- headers: {
352
- "Content-Type": "application/json",
353
- Authorization: `Bearer ${token}`
354
- },
355
- body: JSON.stringify({
356
- url: `${BASE_URL2}customers:listAccessibleCustomers`,
357
- method: "GET"
358
- }),
359
- signal: controller.signal
360
- });
361
- const data = await response.json();
442
+ const response = await fetchGoogleAdsWithVersionFallback(
443
+ (baseUrl) => fetch(proxyUrl, {
444
+ method: "POST",
445
+ headers: {
446
+ "Content-Type": "application/json",
447
+ Authorization: `Bearer ${token}`
448
+ },
449
+ body: JSON.stringify({
450
+ url: `${baseUrl}customers:listAccessibleCustomers`,
451
+ method: "GET"
452
+ }),
453
+ signal: controller.signal
454
+ })
455
+ );
456
+ const rawText = await response.text();
457
+ const data = parseJsonOrNull(rawText);
362
458
  if (!response.ok) {
363
- const errorMessage = typeof data?.error === "string" ? data.error : typeof data?.message === "string" ? data.message : `HTTP ${response.status} ${response.statusText}`;
364
- return { success: false, error: errorMessage };
459
+ return {
460
+ success: false,
461
+ error: extractGoogleAdsErrorMessage(
462
+ response.status,
463
+ response.statusText,
464
+ data,
465
+ rawText
466
+ )
467
+ };
365
468
  }
366
- const customerIds = (data.resourceNames ?? []).map(
469
+ const customerIds = (data?.resourceNames ?? []).map(
367
470
  (rn) => rn.replace(/^customers\//, "")
368
471
  );
369
472
  const customers = [];
370
473
  for (const cid of customerIds) {
371
474
  try {
372
- const detailResponse = await fetch(proxyUrl, {
373
- method: "POST",
374
- headers: {
375
- "Content-Type": "application/json",
376
- Authorization: `Bearer ${token}`
377
- },
378
- body: JSON.stringify({
379
- url: `${BASE_URL2}customers/${cid}/googleAds:searchStream`,
475
+ const detailResponse = await fetchGoogleAdsWithVersionFallback(
476
+ (baseUrl) => fetch(proxyUrl, {
380
477
  method: "POST",
381
478
  headers: {
382
479
  "Content-Type": "application/json",
383
- "login-customer-id": cid
480
+ Authorization: `Bearer ${token}`
384
481
  },
385
482
  body: JSON.stringify({
386
- query: "SELECT customer.id, customer.descriptive_name FROM customer LIMIT 1"
387
- })
388
- }),
389
- signal: controller.signal
390
- });
483
+ url: `${baseUrl}customers/${cid}/googleAds:searchStream`,
484
+ method: "POST",
485
+ headers: {
486
+ "Content-Type": "application/json",
487
+ "login-customer-id": cid
488
+ },
489
+ body: JSON.stringify({
490
+ query: "SELECT customer.id, customer.descriptive_name FROM customer LIMIT 1"
491
+ })
492
+ }),
493
+ signal: controller.signal
494
+ })
495
+ );
391
496
  if (detailResponse.ok) {
392
497
  const detailData = await detailResponse.json();
393
498
  const customer = detailData?.[0]?.results?.[0]?.customer;
@@ -458,7 +563,6 @@ var googleAdsOnboarding = new ConnectorOnboarding({
458
563
 
459
564
  // ../connectors/src/connectors/google-ads/tools/request.ts
460
565
  import { z as z2 } from "zod";
461
- var BASE_URL3 = "https://googleads.googleapis.com/v18/";
462
566
  var REQUEST_TIMEOUT_MS2 = 6e4;
463
567
  var cachedToken2 = null;
464
568
  async function getProxyToken2(config) {
@@ -498,7 +602,7 @@ var inputSchema2 = z2.object({
498
602
  connectionId: z2.string().describe("ID of the Google Ads OAuth connection to use"),
499
603
  method: z2.enum(["GET", "POST"]).describe("HTTP method"),
500
604
  path: z2.string().describe(
501
- "API path appended to https://googleads.googleapis.com/v18/ (e.g., 'customers/{customerId}/googleAds:searchStream'). {customerId} is automatically replaced."
605
+ "API path appended to https://googleads.googleapis.com/<version>/ (e.g., 'customers/{customerId}/googleAds:searchStream'). The API version is auto-managed and {customerId} is automatically replaced."
502
606
  ),
503
607
  body: z2.record(z2.string(), z2.unknown()).optional().describe("POST request body (JSON)")
504
608
  });
@@ -515,7 +619,7 @@ var outputSchema2 = z2.discriminatedUnion("success", [
515
619
  ]);
516
620
  var requestTool = new ConnectorTool({
517
621
  name: "request",
518
- description: `Send authenticated requests to the Google Ads API v18.
622
+ description: `Send authenticated requests to the Google Ads API.
519
623
  Authentication is handled automatically via OAuth proxy.
520
624
  {customerId} in the path is automatically replaced with the connection's customer ID (hyphens removed).`,
521
625
  inputSchema: inputSchema2,
@@ -535,34 +639,42 @@ Authentication is handled automatically via OAuth proxy.
535
639
  const rawCustomerId = parameters.customerId.tryGetValue(connection2);
536
640
  const customerId = rawCustomerId?.replace(/-/g, "") ?? "";
537
641
  const resolvedPath = customerId ? path2.replace(/\{customerId\}/g, customerId) : path2;
538
- const url = `${BASE_URL3}${resolvedPath}`;
539
642
  const token = await getProxyToken2(config.oauthProxy);
540
643
  const proxyUrl = `https://${config.oauthProxy.sandboxId}.${config.oauthProxy.previewBaseDomain}/_sqcore/connections/${connectionId}/request`;
541
644
  const controller = new AbortController();
542
645
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
543
646
  try {
544
- const response = await fetch(proxyUrl, {
545
- method: "POST",
546
- headers: {
547
- "Content-Type": "application/json",
548
- Authorization: `Bearer ${token}`
549
- },
550
- body: JSON.stringify({
551
- url,
552
- method,
647
+ const response = await fetchGoogleAdsWithVersionFallback(
648
+ (baseUrl) => fetch(proxyUrl, {
649
+ method: "POST",
553
650
  headers: {
554
651
  "Content-Type": "application/json",
555
- ...customerId ? { "login-customer-id": customerId } : {}
652
+ Authorization: `Bearer ${token}`
556
653
  },
557
- ...method === "POST" && body ? { body: JSON.stringify(body) } : {}
558
- }),
559
- signal: controller.signal
560
- });
561
- const data = await response.json();
654
+ body: JSON.stringify({
655
+ url: `${baseUrl}${resolvedPath}`,
656
+ method,
657
+ headers: {
658
+ "Content-Type": "application/json",
659
+ ...customerId ? { "login-customer-id": customerId } : {}
660
+ },
661
+ ...method === "POST" && body ? { body: JSON.stringify(body) } : {}
662
+ }),
663
+ signal: controller.signal
664
+ })
665
+ );
666
+ const rawText = await response.text();
667
+ const data = parseJsonOrNull(rawText);
562
668
  if (!response.ok) {
563
- const dataObj = data;
564
- const errorMessage = typeof dataObj?.error === "string" ? dataObj.error : typeof dataObj?.message === "string" ? dataObj.message : `HTTP ${response.status} ${response.statusText}`;
565
- return { success: false, error: errorMessage };
669
+ return {
670
+ success: false,
671
+ error: extractGoogleAdsErrorMessage(
672
+ response.status,
673
+ response.statusText,
674
+ data,
675
+ rawText
676
+ )
677
+ };
566
678
  }
567
679
  return { success: true, status: response.status, data };
568
680
  } finally {
@@ -732,18 +844,22 @@ const customerIds = await ads.listAccessibleCustomers();
732
844
  if (!customerId) {
733
845
  return { success: true };
734
846
  }
735
- const url = `https://googleads.googleapis.com/v18/customers/${customerId}/googleAds:searchStream`;
736
847
  try {
737
- const res = await proxyFetch(url, {
738
- method: "POST",
739
- headers: {
740
- "Content-Type": "application/json",
741
- "login-customer-id": customerId
742
- },
743
- body: JSON.stringify({
744
- query: "SELECT customer.id FROM customer LIMIT 1"
745
- })
746
- });
848
+ const res = await fetchGoogleAdsWithVersionFallback(
849
+ (baseUrl) => proxyFetch(
850
+ `${baseUrl}customers/${customerId}/googleAds:searchStream`,
851
+ {
852
+ method: "POST",
853
+ headers: {
854
+ "Content-Type": "application/json",
855
+ "login-customer-id": customerId
856
+ },
857
+ body: JSON.stringify({
858
+ query: "SELECT customer.id FROM customer LIMIT 1"
859
+ })
860
+ }
861
+ )
862
+ );
747
863
  if (!res.ok) {
748
864
  const errorText = await res.text().catch(() => res.statusText);
749
865
  return {