{"version":3,"file":"retry-Cc4LcXYZ.mjs","names":["CONTENT_TYPE_HEADER","exhaustive"],"sources":["../src/internal/utils/try-catch.ts","../src/internal/http/diagnostics.ts","../src/internal/http/http1-dispatcher.ts","../src/internal/http/rate-limit-sample.ts","../src/internal/http/retry-guidance.ts","../src/internal/http/rate-limit-response.ts","../src/errors/request-aborted.ts","../src/internal/utils/abort.ts","../src/internal/http/request-signal.ts","../src/internal/http/upload-request.ts","../src/internal/http/fetch-client.ts","../src/errors/permission-error.ts","../src/errors/request-deadline-exceeded.ts","../src/errors/retry-delay-exceeded.ts","../src/internal/utils/find-error-code.ts","../src/internal/http/retry.ts"],"sourcesContent":["import type { Result } from \"../../types.ts\";\n\n/**\n * Wraps a promise into a {@link Result}, catching rejections.\n *\n * @template T - The resolved value type.\n * @param promise - The promise to wrap.\n * @returns A Result containing the resolved value or the rejection error.\n */\nexport async function tryCatchAsync<T>(promise: Promise<T>): Promise<Result<T>> {\n\ttry {\n\t\tconst data = await promise;\n\t\treturn { data, success: true };\n\t} catch (err) {\n\t\treturn { err: err instanceof Error ? err : new Error(String(err)), success: false };\n\t}\n}\n","import { ApiError } from \"../../errors/api-error.ts\";\nimport type { Result } from \"../../types.ts\";\n\n// Caps the raw body retained when a response cannot be parsed, so a multi-KB\n// HTML error page is not surfaced or logged whole.\nconst MAX_DETAIL_LENGTH = 500;\n\nconst CONTENT_TYPE_HEADER = \"content-type\";\n\n/**\n * The request-level context threaded from the transport into error\n * construction: which call failed and how long it was in flight.\n */\nexport interface RequestContext {\n\t/** Wall-clock time the request was in flight, in milliseconds. */\n\treadonly elapsedMs: number;\n\t/** HTTP method of the request. */\n\treadonly method: string;\n\t/** Fully-qualified URL of the request. */\n\treadonly url: string;\n}\n\n/** Inputs to {@link parseFailureError}. */\nexport interface ParseFailureArgs {\n\t/** The `SyntaxError` thrown by the failed `JSON.parse`. */\n\treadonly cause: Error;\n\t/** The request that produced the unusable response. */\n\treadonly context: RequestContext;\n\t/** The 2xx response whose body would not parse. */\n\treadonly response: Response;\n\t/** The raw body text, read once by the transport. */\n\treadonly text: string;\n}\n\n// A small allowlist of response headers worth retaining on an ApiError for\n// diagnosis and escalation to Roblox. apis.roblox.com returns `server` (e.g.\n// `public-gateway`, or `haproxy` on a load-balancer error page) and\n// `x-roblox-edge` on every response; `via`, `x-request-id`, and `cf-ray` are\n// standard proxy/CDN request-id headers kept in case an edge adds them. The\n// full header set is never retained, to avoid surfacing anything sensitive.\nconst DIAGNOSTIC_HEADER_ALLOWLIST: ReadonlySet<string> = new Set([\n\t\"cf-ray\",\n\t\"server\",\n\t\"via\",\n\t\"x-request-id\",\n]);\n\nconst DIAGNOSTIC_HEADER_PREFIX = \"x-roblox-\";\n\nconst RATE_LIMIT_HEADER_ALLOWLIST: ReadonlySet<string> = new Set([\n\t\"date\",\n\t\"retry-after\",\n\t\"x-envoy-ratelimited\",\n\t\"x-ratelimit-limit\",\n\t\"x-ratelimit-remaining\",\n\t\"x-ratelimit-reset\",\n\t\"x-retry-after-coverage\",\n]);\n\n// Only the opening tag is matched; the closing tag is located with indexOf so\n// the inner text needs no unbounded pattern, which would backtrack\n// super-linearly on a hostile error page.\nconst TITLE_OPEN_PATTERN = /<title[^>]*>/i;\nconst H1_OPEN_PATTERN = /<h1[^>]*>/i;\nconst TAG_PATTERN = /<[^<>]*>/g;\nconst WHITESPACE_PATTERN = /\\s+/g;\n\n/**\n * Extracts a one-line human summary from an HTML gateway error page, or returns\n * `undefined` when the body is not such a page. A load balancer (HAProxy-style)\n * rejects a request before it reaches Open Cloud and answers with an HTML page,\n * not a JSON Open Cloud error; dumping that HTML whole is noise. The body is\n * treated as HTML when the content-type is `text/html` or the trimmed body is\n * tag-led (`<html`/`<!doctype html`), and the summary is taken from the\n * `<title>` (falling back to the first `<h1>`), tags stripped and whitespace\n * collapsed.\n *\n * @param contentType - The response `content-type` header, if present.\n * @param rawText - The raw response body text.\n * @returns The extracted summary, or `undefined` when the body is not an HTML\n *   gateway page (or carries no title/h1 text).\n */\nexport function extractGatewaySummary(\n\tcontentType: string | undefined,\n\trawText: string,\n): string | undefined {\n\tif (!isHtmlBody(contentType, rawText)) {\n\t\treturn undefined;\n\t}\n\n\treturn (\n\t\tfirstTagText(rawText, { close: \"</title>\", open: TITLE_OPEN_PATTERN }) ??\n\t\tfirstTagText(rawText, { close: \"</h1>\", open: H1_OPEN_PATTERN })\n\t);\n}\n\n/**\n * Filters a lowercased header record down to the diagnostic allowlist: a few\n * named escalation headers plus any `x-roblox-*` header. Keeps errors light and\n * avoids retaining anything sensitive from the full response header set.\n *\n * @param headers - The full header record (lowercased keys).\n * @returns A record containing only the allowlisted headers that were present.\n */\nexport function pickDiagnosticHeaders(\n\theaders: Record<string, string>,\n): Readonly<Record<string, string>> {\n\treturn pickHeaders(headers);\n}\n\n/**\n * Filters a lowercased header record to the raw evidence useful for diagnosing\n * a 429: the general diagnostic headers plus rate-limit guidance and counters.\n * Values are copied without parsing so comma-joined windows remain available.\n *\n * @param headers - The full header record (lowercased keys).\n * @returns Only allowlisted diagnostic and rate-limit headers.\n */\nexport function pickRateLimitHeaders(\n\theaders: Record<string, string>,\n): Readonly<Record<string, string>> {\n\treturn pickHeaders(headers, RATE_LIMIT_HEADER_ALLOWLIST);\n}\n\n/**\n * Converts a `Headers` object to a plain record with lowercased keys.\n *\n * @param headers - The `Headers` instance to convert.\n * @returns A record mapping lowercased header names to their values.\n */\nexport function headersToRecord(headers: Headers): Record<string, string> {\n\treturn Object.fromEntries(headers);\n}\n\n/**\n * Projects a read body to the detail carried on an error: the parsed JSON when\n * it parsed, otherwise the raw text truncated to {@link MAX_DETAIL_LENGTH}.\n *\n * @param text - The raw response body text.\n * @param parsed - The best-effort parse result from the transport's body read.\n * @returns The parsed body, or the truncated raw text on a parse failure.\n */\nexport function bodyDetail(\n\ttext: string,\n\tparsed: Result<JSONValue | undefined>,\n): JSONValue | undefined {\n\treturn parsed.success ? parsed.data : text.slice(0, MAX_DETAIL_LENGTH);\n}\n\n/**\n * Builds the error for a 2xx response whose body could not be parsed as JSON,\n * preserving the parse `cause`, the (truncated) raw body, the declared\n * content-type, and the request that produced it so the failure can be\n * diagnosed after the fact.\n *\n * The received length is on the message rather than only on the error, because\n * `details` retains just the first {@link MAX_DETAIL_LENGTH} characters: for a\n * body that stopped mid-token the head says nothing and the length says\n * everything. A `SyntaxError` position equal to this number is a body the edge\n * cut short, not one the API mis-serialized.\n *\n * @param args - The Response, raw body text, request context, and underlying\n *   parse error.\n * @returns An ApiError carrying the diagnostic context.\n */\nexport function parseFailureError({ cause, context, response, text }: ParseFailureArgs): ApiError {\n\tconst contentType = response.headers.get(CONTENT_TYPE_HEADER) ?? \"unknown\";\n\treturn new ApiError(\n\t\t`Failed to parse response body (content-type: ${contentType}, ${String(text.length)} chars read)`,\n\t\t{\n\t\t\tcause,\n\t\t\tdetails: text.slice(0, MAX_DETAIL_LENGTH),\n\t\t\telapsedMs: context.elapsedMs,\n\t\t\tmethod: context.method,\n\t\t\tresponseHeaders: pickDiagnosticHeaders(headersToRecord(response.headers)),\n\t\t\tstatusCode: response.status,\n\t\t\tunparsedBodyLength: text.length,\n\t\t\turl: context.url,\n\t\t},\n\t);\n}\n\nfunction firstTagText(html: string, tag: { close: string; open: RegExp }): string | undefined {\n\tconst open = tag.open.exec(html);\n\tif (open === null) {\n\t\treturn undefined;\n\t}\n\n\tconst start = open.index + open[0].length;\n\tconst end = html.toLowerCase().indexOf(tag.close, start);\n\tif (end === -1) {\n\t\treturn undefined;\n\t}\n\n\tconst inner = html.slice(start, end);\n\n\tconst text = inner.replace(TAG_PATTERN, \" \").replace(WHITESPACE_PATTERN, \" \").trim();\n\treturn text === \"\" ? undefined : text;\n}\n\nfunction isHtmlBody(contentType: string | undefined, rawText: string): boolean {\n\tif (contentType?.toLowerCase().includes(\"text/html\") === true) {\n\t\treturn true;\n\t}\n\n\tconst head = rawText.trimStart().toLowerCase();\n\treturn head.startsWith(\"<html\") || head.startsWith(\"<!doctype html\");\n}\n\nfunction pickHeaders(\n\theaders: Record<string, string>,\n\tadditionalAllowlist?: ReadonlySet<string>,\n): Record<string, string> {\n\tconst picked: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(headers)) {\n\t\tif (\n\t\t\tDIAGNOSTIC_HEADER_ALLOWLIST.has(name) ||\n\t\t\tname.startsWith(DIAGNOSTIC_HEADER_PREFIX) ||\n\t\t\tadditionalAllowlist?.has(name) === true\n\t\t) {\n\t\t\tpicked[name] = value;\n\t\t}\n\t}\n\n\treturn picked;\n}\n","/** The shape the global dispatcher's class is assumed to have. */\ntype AgentConstructor = new (options: { allowH2: boolean }) => object;\n\n/**\n * Well-known globals under which undici publishes its global dispatcher,\n * newest first. The suffix is undici's own contract version: it moved from\n * `.1` to `.2` in undici 8, so a future major may move it again. Both are\n * probed, and {@link createHttp1Dispatcher} degrades to `undefined` when\n * neither is present — which is also the Bun path, since Bun's `fetch` is not\n * undici-backed and never publishes these.\n */\nconst GLOBAL_DISPATCHER_KEYS: ReadonlyArray<string> = Object.freeze([\n\t\"undici.globalDispatcher.2\",\n\t\"undici.globalDispatcher.1\",\n]);\n\n/**\n * Narrow a value read off the global dispatcher to something\n * `Reflect.construct` accepts. Exported so the narrowing is unit-testable: at\n * the call site every rejected shape funnels into the same `undefined`, which\n * makes the branch unobservable from `createHttp1Dispatcher` alone.\n *\n * @param value - The candidate `constructor` read off the global dispatcher.\n * @returns Whether the value can be constructed.\n */\nexport function isAgentConstructor(value: unknown): value is AgentConstructor {\n\treturn typeof value === \"function\";\n}\n\n/**\n * Builds a request dispatcher that negotiates HTTP/1.1 only, by reusing the\n * class of the runtime's own global dispatcher.\n *\n * From Node 26 (undici 8) `fetch` offers h2 in ALPN, which defeats the\n * `connection: close` an upload sets: `connection` is a connection-specific\n * header, so an h2 transport drops it before the wire and pools every upload\n * onto one multiplexed session. One session death then fails every upload in\n * flight. Constructing the same agent with `allowH2: false` restores the\n * per-upload connection the directive was written for.\n *\n * There is no standard `fetch` option for this, so the agent is reached\n * through undici's versioned global symbol and its class rather than through\n * an import — which keeps the package dependency-free. The contract is\n * internal, so every step is guarded: an absent symbol, a non-constructible\n * value, or a constructor that throws all yield `undefined`, and the caller\n * sends the request unmodified.\n *\n * @param scope - The object to read the global dispatcher from. Defaults to\n *   `globalThis`; injectable so tests need not mutate the real global.\n * @returns A dispatcher restricted to HTTP/1.1, or `undefined` when this\n *   runtime offers no reachable one.\n */\nexport function createHttp1Dispatcher(scope: object = globalThis): object | undefined {\n\tfor (const key of GLOBAL_DISPATCHER_KEYS) {\n\t\tconst dispatcher = constructHttp1(Reflect.get(scope, Symbol.for(key)));\n\t\tif (dispatcher !== undefined) {\n\t\t\treturn dispatcher;\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Reconstructs `agent`'s class with h2 disabled.\n *\n * Most ways `agent` can be wrong already throw: `Reflect.get` rejects a\n * primitive, `null`, and an absent key, and `Reflect.construct` rejects a\n * `constructor` that is not one, or that refuses the options. One shape gets\n * through — a plain object, whose `constructor` is `Object`, so constructing\n * it hands back the options bag. The result is therefore checked against the\n * one part of undici's surface that is public and stable: a `Dispatcher`\n * exposes `dispatch`.\n *\n * @param agent - The candidate global dispatcher; any value, since it comes\n *   from an undocumented global.\n * @returns The new dispatcher, or `undefined` when `agent` is not an object\n *   whose constructor accepts undici's agent options.\n */\nfunction constructHttp1(agent: unknown): object | undefined {\n\ttry {\n\t\t// No shape guard ahead of this: `Reflect.get` already rejects a\n\t\t// primitive, `null`, and an absent key, so every malformed value lands\n\t\t// in the same catch.\n\t\tconst agentClass = Reflect.get(agent, \"constructor\");\n\t\tif (!isAgentConstructor(agentClass)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst dispatcher = Reflect.construct(agentClass, [{ allowH2: false }]);\n\t\treturn typeof Reflect.get(dispatcher, \"dispatch\") === \"function\" ? dispatcher : undefined;\n\t} catch {\n\t\t// An internal contract that moved under us is not worth failing a\n\t\t// deploy over: fall back to the runtime's default transport.\n\t\treturn undefined;\n\t}\n}\n","/**\n * A point-in-time rate-limit budget reading parsed from Roblox Open Cloud\n * response headers. Both fields are non-negative integers.\n */\nexport interface RateLimitSample {\n\t/**\n\t * Requests still allowed in the current window (the most-constrained one).\n\t */\n\treadonly remaining: number;\n\t/** Seconds until the most-constrained window resets to full. */\n\treadonly resetSeconds: number;\n}\n\nconst NON_NEGATIVE_INTEGER_PATTERN = /^\\d+$/;\n\n/**\n * Reduces a comma-separated rate-limit header value (e.g. `\"0, 70000\"`) to a\n * single non-negative integer via `combine`. Tokens are trimmed; blank,\n * fractional, negative, and non-finite tokens are dropped so a stray value\n * cannot corrupt the result. Returns `undefined` when the header is absent or\n * has no valid tokens.\n *\n * @param headerValue - The raw header value, or `undefined` if missing.\n * @param combine - Pairwise reducer, `Math.min` for remaining, `Math.max` for reset.\n * @returns The reduced value, or `undefined`.\n */\nexport function reduceRateLimitTokens(\n\theaderValue: string | undefined,\n\tcombine: (a: number, b: number) => number,\n): number | undefined {\n\tif (headerValue === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst tokens = headerValue\n\t\t.split(\",\")\n\t\t.map((part) => part.trim())\n\t\t.filter((part) => NON_NEGATIVE_INTEGER_PATTERN.test(part))\n\t\t.map((part) => Number(part))\n\t\t.filter((value) => Number.isInteger(value));\n\tif (tokens.length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn tokens.reduce(combine);\n}\n\n/**\n * Parses the `x-ratelimit-remaining` and `x-ratelimit-reset` response headers\n * into a {@link RateLimitSample}. Each header may carry a comma-separated list\n * of per-window values; `remaining` takes the smallest (most constrained) and\n * `resetSeconds` takes the largest (longest wait). Returns `undefined` when\n * either header is missing or has no valid non-negative integer tokens, so a\n * caller can fall back to static pacing.\n *\n * @param headers - Response headers with lowercased keys.\n * @returns The parsed sample, or `undefined` when the budget cannot be read.\n */\nexport function parseRateLimitHeaders(\n\theaders: Readonly<Record<string, string>>,\n): RateLimitSample | undefined {\n\tconst remaining = reduceRateLimitTokens(headers[\"x-ratelimit-remaining\"], (a, b) => {\n\t\treturn Math.min(a, b);\n\t});\n\tconst resetSeconds = reduceRateLimitTokens(headers[\"x-ratelimit-reset\"], (a, b) => {\n\t\treturn Math.max(a, b);\n\t});\n\tif (remaining === undefined || resetSeconds === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { remaining, resetSeconds };\n}\n","import { reduceRateLimitTokens } from \"./rate-limit-sample.ts\";\n\nconst ABBREVIATED_WEEKDAYS: ReadonlyArray<string> = [\n\t\"Sun\",\n\t\"Mon\",\n\t\"Tue\",\n\t\"Wed\",\n\t\"Thu\",\n\t\"Fri\",\n\t\"Sat\",\n];\nconst ANSI_DATE_PATTERN =\n\t/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( \\d|\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}) (\\d{4})$/;\nconst DELAY_SECONDS_PATTERN = /^\\d+$/;\nconst FIXED_DATE_PATTERN =\n\t/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\\d{2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\\d{4}) (\\d{2}):(\\d{2}):(\\d{2}) GMT$/;\nconst FULL_WEEKDAY_TO_ABBREVIATED: Readonly<Record<string, string>> = {\n\tFriday: \"Fri\",\n\tMonday: \"Mon\",\n\tSaturday: \"Sat\",\n\tSunday: \"Sun\",\n\tThursday: \"Thu\",\n\tTuesday: \"Tue\",\n\tWednesday: \"Wed\",\n};\nconst MONTHS: ReadonlyArray<string> = [\n\t\"Jan\",\n\t\"Feb\",\n\t\"Mar\",\n\t\"Apr\",\n\t\"May\",\n\t\"Jun\",\n\t\"Jul\",\n\t\"Aug\",\n\t\"Sep\",\n\t\"Oct\",\n\t\"Nov\",\n\t\"Dec\",\n];\nconst OBSOLETE_DATE_PATTERN =\n\t/^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\\d{2})-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2}) GMT$/;\n\ninterface HttpDateParts {\n\treadonly day: number;\n\treadonly hour: number;\n\treadonly minute: number;\n\treadonly month: number;\n\treadonly second: number;\n\treadonly weekday: string;\n\treadonly year: number;\n}\n\ninterface ResolveRetryGuidanceOptions {\n\treadonly headers: Readonly<Record<string, string>>;\n\treadonly nowMs?: number;\n\treadonly remaining: number | undefined;\n}\n\n/**\n * Parses `x-ratelimit-reset` as seconds until quota replenishment. Roblox can\n * report multiple comma-separated windows; the longest valid window is the\n * earliest safe quota retry.\n *\n * @param headerValue - The raw reset value, or `undefined` if missing.\n * @returns The longest reset delay, or `undefined` when absent or invalid.\n */\nexport function parseQuotaResetSeconds(headerValue: string | undefined): number | undefined {\n\treturn reduceRateLimitTokens(headerValue, (a, b) => Math.max(a, b));\n}\n\n/**\n * Parses the standard `Retry-After` header. RFC 9110 permits either a\n * non-negative integer number of seconds or any HTTP-date form. A date is\n * rounded up so a retry never starts before the server's stated instant.\n *\n * @param headerValue - The raw `Retry-After` value, or `undefined` if missing.\n * @param nowMs - Current Unix time in milliseconds, injectable for tests.\n * @returns The delay in seconds, or `undefined` when absent or invalid.\n */\nexport function parseRetryAfterSeconds(\n\theaderValue: string | undefined,\n\tnowMs = Date.now(),\n): number | undefined {\n\tif (headerValue === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst value = headerValue.trim();\n\tif (DELAY_SECONDS_PATTERN.test(value)) {\n\t\tconst seconds = Number(value);\n\t\treturn Number.isFinite(seconds) ? seconds : undefined;\n\t}\n\n\tconst retryAt = parseHttpDate(value, nowMs);\n\treturn retryAt === undefined ? undefined : Math.max(0, Math.ceil((retryAt - nowMs) / 1000));\n}\n\n/**\n * Chooses the applicable server-directed delay for one 429 response.\n *\n * @param options - Response headers, remaining quota, and optional clock.\n * @returns The delay in seconds, or `undefined` when caller backoff applies.\n */\nexport function resolveRetryGuidance({\n\theaders,\n\tnowMs,\n\tremaining,\n}: ResolveRetryGuidanceOptions): number | undefined {\n\tconst retryAfter = parseRetryAfterSeconds(headers[\"retry-after\"], nowMs);\n\tif (remaining !== 0) {\n\t\treturn retryAfter;\n\t}\n\n\tconst quotaReset = parseQuotaResetSeconds(headers[\"x-ratelimit-reset\"]);\n\treturn quotaReset === undefined ? retryAfter : Math.max(retryAfter ?? 0, quotaReset);\n}\n\nfunction httpDateTimestamp(parts: HttpDateParts): number | undefined {\n\tif (parts.minute > 59 || parts.second > 59) {\n\t\treturn undefined;\n\t}\n\n\tconst date = new Date(0);\n\tdate.setUTCFullYear(parts.year, parts.month, parts.day);\n\tdate.setUTCHours(parts.hour, parts.minute, parts.second, 0);\n\n\tconst isExactDate =\n\t\tdate.getUTCDate() === parts.day && ABBREVIATED_WEEKDAYS[date.getUTCDay()] === parts.weekday;\n\treturn isExactDate ? date.getTime() : undefined;\n}\n\nfunction parseAnsiDate(value: string): number | undefined {\n\tconst match = ANSI_DATE_PATTERN.exec(value);\n\treturn match === null\n\t\t? undefined\n\t\t: httpDateTimestamp({\n\t\t\t\tday: Number(match[3]),\n\t\t\t\thour: Number(match[4]),\n\t\t\t\tminute: Number(match[5]),\n\t\t\t\tmonth: MONTHS.indexOf(String(match[2])),\n\t\t\t\tsecond: Number(match[6]),\n\t\t\t\tweekday: String(match[1]),\n\t\t\t\tyear: Number(match[7]),\n\t\t\t});\n}\n\nfunction parseFixedDate(value: string): number | undefined {\n\tconst match = FIXED_DATE_PATTERN.exec(value);\n\treturn match === null\n\t\t? undefined\n\t\t: httpDateTimestamp({\n\t\t\t\tday: Number(match[2]),\n\t\t\t\thour: Number(match[5]),\n\t\t\t\tminute: Number(match[6]),\n\t\t\t\tmonth: MONTHS.indexOf(String(match[3])),\n\t\t\t\tsecond: Number(match[7]),\n\t\t\t\tweekday: String(match[1]),\n\t\t\t\tyear: Number(match[4]),\n\t\t\t});\n}\n\nfunction parseObsoleteDate(value: string, nowMs: number): number | undefined {\n\tconst match = OBSOLETE_DATE_PATTERN.exec(value);\n\tif (match === null) {\n\t\treturn undefined;\n\t}\n\n\tconst currentDate = new Date(nowMs);\n\tconst currentYear = currentDate.getUTCFullYear();\n\tlet year = Math.floor(currentYear / 100) * 100 + Number(match[4]);\n\tif (year > currentYear + 50) {\n\t\tyear -= 100;\n\t}\n\n\treturn httpDateTimestamp({\n\t\tday: Number(match[2]),\n\t\thour: Number(match[5]),\n\t\tminute: Number(match[6]),\n\t\tmonth: MONTHS.indexOf(String(match[3])),\n\t\tsecond: Number(match[7]),\n\t\tweekday: String(FULL_WEEKDAY_TO_ABBREVIATED[String(match[1])]),\n\t\tyear,\n\t});\n}\n\nfunction parseHttpDate(value: string, nowMs: number): number | undefined {\n\treturn parseFixedDate(value) ?? parseObsoleteDate(value, nowMs) ?? parseAnsiDate(value);\n}\n","import { markServerRetryGuidance, RateLimitError } from \"../../errors/rate-limit.ts\";\nimport { headersToRecord, pickRateLimitHeaders } from \"./diagnostics.ts\";\nimport { reduceRateLimitTokens } from \"./rate-limit-sample.ts\";\nimport { resolveRetryGuidance } from \"./retry-guidance.ts\";\n\n/**\n * Builds a public rate-limit error from a 429 response and its already-read\n * body, retaining safe raw evidence alongside the existing parsed guidance.\n *\n * @param response - The 429 response to classify.\n * @param details - The parsed or safely truncated response body.\n * @returns A rate-limit error carrying the response evidence.\n */\nexport function createRateLimitError(\n\tresponse: Response,\n\tdetails: JSONValue | undefined,\n): RateLimitError {\n\tconst headers = headersToRecord(response.headers);\n\tconst remaining = reduceRateLimitTokens(headers[\"x-ratelimit-remaining\"], (a, b) => {\n\t\treturn Math.min(a, b);\n\t});\n\tconst guidedRetrySeconds = resolveRetryGuidance({ headers, remaining });\n\tconst error = new RateLimitError(\"Rate limited\", {\n\t\tcode: extractRateLimitErrorCode(details),\n\t\tdetails,\n\t\tremaining,\n\t\tresponseHeaders: pickRateLimitHeaders(headers),\n\t\tretryAfterSeconds: guidedRetrySeconds ?? 0,\n\t\tstatusCode: response.status,\n\t});\n\treturn guidedRetrySeconds === undefined ? error : markServerRetryGuidance(error);\n}\n\nfunction extractRateLimitErrorCode(body: unknown): string | undefined {\n\tif (body === null || typeof body !== \"object\") {\n\t\treturn undefined;\n\t}\n\n\tconst code = Reflect.get(body, \"code\");\n\treturn typeof code === \"string\" && code.trim() ? code : undefined;\n}\n","import { OpenCloudError } from \"./base.ts\";\n\n/**\n * Options for constructing a {@link RequestAbortedError}.\n *\n * @since 0.3.1\n */\nexport interface RequestAbortedErrorOptions extends ErrorOptions {\n\t/** Whatever `AbortSignal.reason` was at the moment of cancellation. */\n\treadonly reason?: unknown;\n}\n\n/**\n * Returned when a caller's `AbortSignal` cancels an Open Cloud request.\n * The reason is preserved so intentional cancellation can be distinguished\n * from transport failures and SDK-owned request timeouts.\n *\n * @since 0.3.1\n *\n * @example\n *\n * ```ts\n * import { RequestAbortedError } from \"@bedrock-rbx/ocale\";\n *\n * const error = new RequestAbortedError(\"Request was aborted\", {\n *     reason: \"superseded\",\n * });\n *\n * expect(error).toBeInstanceOf(RequestAbortedError);\n * expect(error.reason).toBe(\"superseded\");\n * ```\n */\nexport class RequestAbortedError extends OpenCloudError {\n\tpublic override readonly name: string = \"RequestAbortedError\";\n\tpublic readonly reason?: unknown;\n\n\t/**\n\t * Creates a new RequestAbortedError.\n\t *\n\t * @param message - Human-readable description of the cancellation.\n\t * @param options - Error options including the caller's abort reason.\n\t */\n\tconstructor(message: string, options: RequestAbortedErrorOptions = {}) {\n\t\tsuper(message, options);\n\t\tthis.reason = options.reason;\n\t}\n}\n","import { RequestAbortedError } from \"../../errors/request-aborted.ts\";\n\n/**\n * Sentinel returned when an asynchronous operation loses a race with a signal.\n */\nexport const ABORTED = Symbol(\"aborted\");\n\n/**\n * Result of an operation raced against an optional caller signal.\n *\n * @template T - Value produced when the operation wins the race.\n */\nexport type AbortableResult<T> = T | typeof ABORTED;\n\n/**\n * Runs an asynchronous operation and resolves with {@link ABORTED} when the\n * supplied signal fires first. The underlying operation still receives the\n * signal separately so cancellable implementations can release their work.\n *\n * @template T - Value produced by the operation.\n * @param operation - Operation to start after the abort observer is installed.\n * @param signal - Optional caller cancellation signal.\n * @returns The operation's value, or {@link ABORTED} when cancellation wins.\n * @rejects The operation's error when it fails before cancellation.\n */\nexport async function raceWithAbortAsync<T>(\n\toperation: () => Promise<T>,\n\tsignal: AbortSignal | undefined,\n): Promise<AbortableResult<T>> {\n\tif (signal === undefined) {\n\t\treturn operation();\n\t}\n\n\tif (signal.aborted) {\n\t\treturn ABORTED;\n\t}\n\n\tconst { promise, resolve } = Promise.withResolvers<typeof ABORTED>();\n\tfunction onAbort(): void {\n\t\tresolve(ABORTED);\n\t}\n\n\tsignal.addEventListener(\"abort\", onAbort);\n\ttry {\n\t\treturn await Promise.race([operation(), promise]);\n\t} finally {\n\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\n/**\n * Creates the canonical typed failure for caller-request cancellation.\n *\n * @param signal - The caller signal whose reason should be preserved.\n * @returns A cancellation error carrying the signal's reason.\n */\nexport function requestAbortedError(signal: AbortSignal | undefined): RequestAbortedError {\n\treturn new RequestAbortedError(\"Request was aborted\", { reason: signal?.reason });\n}\n","import { NetworkError } from \"../../errors/network-error.ts\";\nimport type { RequestAbortedError } from \"../../errors/request-aborted.ts\";\nimport { requestAbortedError } from \"../utils/abort.ts\";\nimport type { RequestConfig } from \"./types.ts\";\n\ninterface RequestFailureArgs {\n\treadonly cause: Error;\n\treadonly config: RequestConfig;\n\treadonly effectiveSignal: AbortSignal | null | undefined;\n\treadonly target: { readonly method: string; readonly url: string };\n}\n\n/**\n * Composes caller cancellation with Ocale's transport-attempt timeout. The\n * winning signal's reason is retained by `AbortSignal.any`, allowing failure\n * classification to distinguish cancellation from timeout.\n *\n * @param config - Transport configuration for one request.\n * @returns A caller signal, timeout signal, their composition, or `undefined`.\n */\nexport function requestSignal(config: RequestConfig): AbortSignal | undefined {\n\tif (config.timeout === undefined) {\n\t\treturn config.signal;\n\t}\n\n\tconst timeout = AbortSignal.timeout(config.timeout);\n\treturn config.signal === undefined ? timeout : AbortSignal.any([config.signal, timeout]);\n}\n\n/**\n * Classifies a fetch rejection without confusing a caller abort with the\n * transport's own timeout.\n *\n * @param args - Failure, target, request config, and effective fetch signal.\n * @returns A typed caller cancellation or network failure.\n */\nexport function requestFailure({\n\tcause,\n\tconfig,\n\teffectiveSignal,\n\ttarget,\n}: RequestFailureArgs): NetworkError | RequestAbortedError {\n\tif (\n\t\tconfig.signal?.aborted === true &&\n\t\teffectiveSignal?.aborted === true &&\n\t\tObject.is(effectiveSignal.reason, config.signal.reason)\n\t) {\n\t\treturn requestAbortedError(config.signal);\n\t}\n\n\treturn new NetworkError(\"Network request failed\", {\n\t\tcause,\n\t\tmethod: target.method,\n\t\turl: target.url,\n\t});\n}\n","import type { HttpRequest } from \"../../client/types.ts\";\n\n/**\n * Reports whether a request is an upload: its body is `FormData`\n * (multipart) or `Uint8Array` (raw binary). Upload latency is\n * bandwidth-bound rather than compute-bound, so the SDK applies no default\n * request timeout to these requests; a sensible wall-clock budget depends on\n * payload size and link quality the SDK cannot know.\n *\n * @param request - The built request to classify.\n * @returns `true` when the body is `FormData` or `Uint8Array`.\n */\nexport function isUploadRequest(request: HttpRequest): boolean {\n\treturn request.body instanceof FormData || request.body instanceof Uint8Array;\n}\n","import { ApiError } from \"../../errors/api-error.ts\";\nimport type { OpenCloudError } from \"../../errors/base.ts\";\nimport type { Result } from \"../../types.ts\";\nimport { tryCatchAsync } from \"../utils/try-catch.ts\";\nimport {\n\tbodyDetail,\n\textractGatewaySummary,\n\theadersToRecord,\n\tparseFailureError,\n\tpickDiagnosticHeaders,\n\ttype RequestContext,\n} from \"./diagnostics.ts\";\nimport { createHttp1Dispatcher } from \"./http1-dispatcher.ts\";\nimport { createRateLimitError } from \"./rate-limit-response.ts\";\nimport { requestFailure, requestSignal } from \"./request-signal.ts\";\nimport type { HttpClient, HttpRequest, HttpResponse, RequestConfig } from \"./types.ts\";\nimport { isUploadRequest } from \"./upload-request.ts\";\n\nconst CONTENT_TYPE_HEADER = \"content-type\";\n\n// Uploads opt out of keep-alive reuse. Roblox's edge gateway discards idle\n// pooled connections faster than a pooling `fetch` expects, and a request\n// written into a discarded connection never reaches Open Cloud: it surfaces as\n// a gateway error page or a socket reset, minutes later, having done nothing.\n// An upload holds a connection far longer than a JSON call, so it is the shape\n// that loses this race. The cost is a fresh handshake and a cold congestion\n// window per upload (real for a multi-megabyte body, and paid again on each\n// retry), but cheaper than a lost write. Small, frequent calls keep pooling.\nconst CONNECTION_HEADER = \"connection\";\n\n/**\n * `RequestInit` plus undici's non-standard `dispatcher`, the only way to\n * select a transport from `fetch`. Declared locally because it is absent from\n * the DOM lib and runtimes that do not understand it ignore it.\n */\ninterface FetchOptions extends RequestInit {\n\tdispatcher?: object | undefined;\n}\n\n/**\n * Collaborators of {@link createFetchHttpClient}, bundled so the factory keeps\n * a two-argument signature as they accumulate. Both have production defaults;\n * both are overridable, which is also what makes them testable.\n */\ninterface FetchHttpClientSeams {\n\t/**\n\t * Builds the HTTP/1.1-only transport uploads use, so the\n\t * `connection: close` directive is not dropped by an h2 transport.\n\t */\n\treadonly createDispatcher?: () => object | undefined;\n\t/** Monotonic-ish clock used to measure request elapsed time. */\n\treadonly now?: () => number;\n}\n\ninterface SendRequestArgs {\n\treadonly config: RequestConfig;\n\treadonly dispatcherFor: (request: HttpRequest) => object | undefined;\n\treadonly fetchFunc: (url: string, init: RequestInit) => Promise<Response>;\n\treadonly httpRequest: HttpRequest;\n\treadonly now: () => number;\n}\n\ninterface ErrorResponseArgs {\n\treadonly context: RequestContext;\n\treadonly parsed: Result<JSONValue | undefined>;\n\treadonly rawText: string;\n\treadonly response: Response;\n}\n\ninterface ApiErrorMessageParts {\n\treadonly code: string | undefined;\n\treadonly message: string | undefined;\n\treadonly status: number;\n}\n\n/**\n * Permissively extracts a machine-readable error code from a response body.\n *\n * Three shapes are checked, in precedence order. Modern Open Cloud responses\n * use `{ errorCode: string, message: string }`; Open Cloud v2 endpoints carry\n * the canonical status in `error` (`{ error: \"NOT_FOUND\", message: string }`);\n * the legacy game-internationalization endpoints use\n * `{ errors: [{ code: number, message: string }, ...] }`. Numeric legacy codes\n * are returned as strings so callers see one consistent type.\n *\n * `error` is read only when it holds a string. A Google-style nested envelope\n * (`{ error: { code, message, status } }`) puts an object there, and coercing\n * that to a string would hand callers `\"[object Object]\"` as a status.\n *\n * @param body - The parsed response body (unknown shape).\n * @returns The error code if present, otherwise `undefined`.\n */\nexport function extractErrorCode(body: unknown): string | undefined {\n\tif (body === null || typeof body !== \"object\") {\n\t\treturn undefined;\n\t}\n\n\tconst errorCode = Reflect.get(body, \"errorCode\");\n\tif (typeof errorCode === \"string\") {\n\t\treturn errorCode;\n\t}\n\n\tconst v2Error = Reflect.get(body, \"error\");\n\tif (typeof v2Error === \"string\") {\n\t\treturn v2Error;\n\t}\n\n\treturn extractLegacyCode(body);\n}\n\n/**\n * Permissively extracts a human-readable error message from a response body.\n *\n * Modern Open Cloud responses expose `message` at the top level; the legacy\n * game-internationalization endpoints nest it under `errors[0].message`.\n *\n * @param body - The parsed response body (unknown shape).\n * @returns The message if present, otherwise `undefined`.\n */\nexport function extractErrorMessage(body: unknown): string | undefined {\n\tif (body === null || typeof body !== \"object\") {\n\t\treturn undefined;\n\t}\n\n\tconst message = Reflect.get(body, \"message\");\n\tif (typeof message === \"string\") {\n\t\treturn message;\n\t}\n\n\treturn extractLegacyMessage(body);\n}\n\n/**\n * Joins the base URL from config with the relative path from the request.\n *\n * @param request - The HTTP request containing the relative URL.\n * @param config - The request config containing the base URL.\n * @returns The fully-qualified URL string.\n */\nexport function buildUrl(request: HttpRequest, config: RequestConfig): string {\n\tconst base = config.baseUrl.endsWith(\"/\") ? config.baseUrl.slice(0, -1) : config.baseUrl;\n\treturn `${base}${request.url}`;\n}\n\n/**\n * Constructs the `RequestInit` options for a `fetch` call.\n *\n * @param request - The HTTP request to build options for.\n * @param config - The request config containing API key and timeout.\n * @returns A `RequestInit` object ready for `fetch`.\n */\nexport function buildFetchOptions(request: HttpRequest, config: RequestConfig): FetchOptions {\n\tconst headers = new Headers({\n\t\t\"x-api-key\": config.apiKey,\n\t});\n\n\tconst options: FetchOptions = {\n\t\theaders,\n\t\tmethod: request.method,\n\t};\n\n\tif (request.body instanceof FormData) {\n\t\toptions.body = request.body;\n\t} else if (request.body instanceof Uint8Array) {\n\t\theaders.set(CONTENT_TYPE_HEADER, \"application/octet-stream\");\n\t\toptions.body = request.body;\n\t} else if (request.body !== undefined) {\n\t\theaders.set(CONTENT_TYPE_HEADER, \"application/json\");\n\t\toptions.body = JSON.stringify(request.body);\n\t}\n\n\tapplyRequestHeaders(headers, request);\n\n\tconst signal = requestSignal(config);\n\tif (signal !== undefined) {\n\t\toptions.signal = signal;\n\t}\n\n\treturn options;\n}\n\n/**\n * Creates an {@link HttpClient} backed by the Fetch API.\n *\n * @param fetchFunc - The fetch implementation to use. Defaults to `globalThis.fetch`.\n * @param seams - Injectable clock and dispatcher factory, so tests need not\n *   depend on wall-clock time or on the runtime's global dispatcher.\n * @returns An HttpClient that classifies responses into typed Results.\n */\nexport function createFetchHttpClient(\n\tfetchFunc: (url: string, init: RequestInit) => Promise<Response> = fetch,\n\t{ createDispatcher = createHttp1Dispatcher, now = Date.now }: FetchHttpClientSeams = {},\n): HttpClient {\n\tconst dispatcherFor = createUploadDispatcherCache(createDispatcher);\n\n\treturn {\n\t\tasync request(httpRequest, config) {\n\t\t\treturn sendRequestAsync({ config, dispatcherFor, fetchFunc, httpRequest, now });\n\t\t},\n\t};\n}\n\nfunction readLegacyErrorEntry(body: object): object | undefined {\n\tconst errors = Reflect.get(body, \"errors\");\n\tif (!Array.isArray(errors)) {\n\t\treturn undefined;\n\t}\n\n\tconst [first] = errors;\n\tif (typeof first !== \"object\" || first === null) {\n\t\treturn undefined;\n\t}\n\n\treturn first;\n}\n\nfunction extractLegacyCode(body: object): string | undefined {\n\tconst first = readLegacyErrorEntry(body);\n\tif (first === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst code = Reflect.get(first, \"code\");\n\tif (typeof code === \"string\") {\n\t\treturn code;\n\t}\n\n\treturn typeof code === \"number\" ? String(code) : undefined;\n}\n\nfunction extractLegacyMessage(body: object): string | undefined {\n\tconst first = readLegacyErrorEntry(body);\n\tif (first === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst message = Reflect.get(first, \"message\");\n\treturn typeof message === \"string\" ? message : undefined;\n}\n\n/**\n * Merges the request's own headers onto the transport's, then applies the\n * transport-owned connection directive. `x-api-key` is skipped so a request\n * cannot override the configured credential, and the upload directive is set\n * last so a request header cannot silently re-enable pooling for an upload.\n *\n * @param headers - The headers being built for the fetch call. Mutated.\n * @param request - The request whose headers and body shape drive the merge.\n */\nfunction applyRequestHeaders(headers: Headers, request: HttpRequest): void {\n\tconst requestHeaders = request.headers ?? {};\n\tfor (const [name, value] of Object.entries(requestHeaders)) {\n\t\tif (name.toLowerCase() === \"x-api-key\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\theaders.set(name, value);\n\t}\n\n\tif (isUploadRequest(request)) {\n\t\theaders.set(CONNECTION_HEADER, \"close\");\n\t}\n}\n\nfunction failureResult(args: Parameters<typeof requestFailure>[0]): Result<never, OpenCloudError> {\n\treturn { err: requestFailure(args), success: false };\n}\n\n/**\n * Runs `send` and reports both its Result and how long it was in flight,\n * measured with `now`. Isolated so the timing start need not sit in the request\n * body ahead of the transport-failure early return.\n *\n * @param now - The clock used to bound the call.\n * @param send - A thunk that issues the fetch.\n * @returns The fetch Result and the elapsed milliseconds.\n */\nasync function timedFetchAsync(\n\tnow: () => number,\n\tsend: () => Promise<Response>,\n): Promise<{ elapsedMs: number; fetchResult: Result<Response> }> {\n\tconst start = now();\n\tconst fetchResult = await tryCatchAsync(send());\n\t// Clamp to zero: `Date.now` is wall-clock, so an NTP adjustment mid-request\n\t// could otherwise report a negative \"after -0.1s\".\n\treturn { elapsedMs: Math.max(0, now() - start), fetchResult };\n}\n\nfunction formatApiErrorMessage({ code, message, status }: ApiErrorMessageParts): string {\n\tconst base = `HTTP ${status}`;\n\tif (message === undefined && code === undefined) {\n\t\treturn base;\n\t}\n\n\tif (message === undefined) {\n\t\treturn `${base} (code ${code})`;\n\t}\n\n\tif (code === undefined) {\n\t\treturn `${base}: ${message}`;\n\t}\n\n\treturn `${base}: ${message} (code ${code})`;\n}\n\nfunction createApiError(args: ErrorResponseArgs): ApiError {\n\tconst { context, rawText, response } = args;\n\tconst { status } = response;\n\tconst headers = headersToRecord(response.headers);\n\tconst requestContext = {\n\t\telapsedMs: context.elapsedMs,\n\t\tmethod: context.method,\n\t\tresponseHeaders: pickDiagnosticHeaders(headers),\n\t\tstatusCode: status,\n\t\turl: context.url,\n\t};\n\n\t// An HTML body is a load-balancer error page, not an Open Cloud response;\n\t// summarize it rather than retaining the raw HTML on `details`.\n\tconst gatewaySummary = extractGatewaySummary(headers[CONTENT_TYPE_HEADER], rawText);\n\tif (gatewaySummary !== undefined) {\n\t\treturn new ApiError(`HTTP ${status}`, { ...requestContext, gatewaySummary });\n\t}\n\n\tconst body = bodyDetail(rawText, args.parsed);\n\tconst code = extractErrorCode(body);\n\tconst message = extractErrorMessage(body);\n\treturn new ApiError(formatApiErrorMessage({ code, message, status }), {\n\t\t...requestContext,\n\t\tcode,\n\t\tdetails: body,\n\t});\n}\n\n/**\n * Parses response text as JSON, returning the underlying `SyntaxError` on\n * failure rather than throwing. The synchronous sibling of {@link\n * tryCatchAsync}.\n *\n * @param text - The raw response body text.\n * @returns A Result wrapping the parsed value, or the parse error.\n */\nfunction parseJson(text: string): Result<JSONValue> {\n\ttry {\n\t\treturn { data: JSON.parse(text), success: true };\n\t} catch (err) {\n\t\treturn { err: err instanceof Error ? err : new Error(String(err)), success: false };\n\t}\n}\n\n/**\n * Reads a response body once and parses it best-effort: an empty body is a\n * successful `undefined`, otherwise the JSON parse result (which carries the\n * `SyntaxError` on failure). Returns the raw `text` alongside so callers that\n * need the original bytes (parse-failure diagnostics) do not re-read the\n * consumed stream.\n *\n * @param response - The Response whose body to read.\n * @returns The parse result and the raw text.\n */\nasync function readResponseBodyAsync(\n\tresponse: Response,\n): Promise<{ parsed: Result<JSONValue | undefined>; text: string }> {\n\tconst text = await response.text();\n\treturn {\n\t\tparsed: text === \"\" ? { data: undefined, success: true } : parseJson(text),\n\t\ttext,\n\t};\n}\n\n/**\n * Classifies a fetch `Response` into a typed `Result`.\n *\n * The body is read once and parsed best-effort. Error responses (status >= 300)\n * never require valid JSON: an error body that is not valid JSON degrades to a\n * status-based {@link ApiError} carrying the raw text, and an HTML gateway page\n * is summarized rather than dumped. A parse failure is only fatal on a 2xx,\n * where a parseable body is part of the contract.\n *\n * @param response - The raw fetch Response to classify.\n * @param context - The request context (method, url, elapsed time) threaded\n *   onto every {@link ApiError} built here, whether for an error response or\n *   for a 2xx whose body would not parse.\n * @returns A Result containing an HttpResponse on success or an OpenCloudError on failure.\n */\nasync function classifyResponseAsync(\n\tresponse: Response,\n\tcontext: RequestContext,\n): Promise<Result<HttpResponse, OpenCloudError>> {\n\tconst { parsed, text } = await readResponseBodyAsync(response);\n\n\tif (response.status === 429) {\n\t\treturn { err: createRateLimitError(response, bodyDetail(text, parsed)), success: false };\n\t}\n\n\tif (response.status >= 300) {\n\t\treturn {\n\t\t\terr: createApiError({ context, parsed, rawText: text, response }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tif (!parsed.success) {\n\t\treturn {\n\t\t\terr: parseFailureError({ cause: parsed.err, context, response, text }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tbody: parsed.data,\n\t\t\theaders: headersToRecord(response.headers),\n\t\t\tstatus: response.status,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nasync function sendRequestAsync({\n\tconfig,\n\tdispatcherFor,\n\tfetchFunc,\n\thttpRequest,\n\tnow,\n}: SendRequestArgs): Promise<Result<HttpResponse, OpenCloudError>> {\n\tconst url = buildUrl(httpRequest, config);\n\tconst options = buildFetchOptions(httpRequest, config);\n\toptions.dispatcher = dispatcherFor(httpRequest);\n\tconst target = { method: httpRequest.method, url };\n\tconst { elapsedMs, fetchResult } = await timedFetchAsync(now, async () => {\n\t\treturn fetchFunc(url, options);\n\t});\n\tif (!fetchResult.success) {\n\t\treturn failureResult({\n\t\t\tcause: fetchResult.err,\n\t\t\tconfig,\n\t\t\teffectiveSignal: options.signal,\n\t\t\ttarget,\n\t\t});\n\t}\n\n\tconst context: RequestContext = { elapsedMs, method: target.method, url: target.url };\n\tconst classified = await tryCatchAsync(classifyResponseAsync(fetchResult.data, context));\n\treturn classified.success\n\t\t? classified.data\n\t\t: failureResult({ cause: classified.err, config, effectiveSignal: options.signal, target });\n}\n\n/**\n * Wraps a dispatcher factory in the caching policy uploads need.\n *\n * Resolution happens on the first upload rather than at construction: undici\n * publishes its global dispatcher lazily, so before a process's first `fetch`\n * there is nothing to read. A resolved dispatcher is kept, and an unresolved\n * one is retried on the next upload — so a runtime that publishes late is\n * still picked up, and the first request of a process, which has no pooled\n * connection to lose, is safe either way.\n *\n * @param createDispatcher - Builds the HTTP/1.1-only transport.\n * @returns A function yielding the dispatcher for a request, or `undefined`\n *   when the request is not an upload or no dispatcher is available.\n */\nfunction createUploadDispatcherCache(\n\tcreateDispatcher: () => object | undefined,\n): (request: HttpRequest) => object | undefined {\n\tlet cached: object | undefined;\n\treturn (request) => {\n\t\tif (!isUploadRequest(request)) {\n\t\t\treturn;\n\t\t}\n\n\t\tcached ??= createDispatcher();\n\t\treturn cached;\n\t};\n}\n","import { ApiError, type ApiErrorOptions } from \"./api-error.ts\";\n\n/**\n * Options for constructing a {@link PermissionError}.\n *\n * @since 0.1.0\n */\nexport interface PermissionErrorOptions extends ApiErrorOptions {\n\t/**\n\t * Stable identifier of the Open Cloud operation that returned the\n\t * permission failure (matches `OperationLimit.operationKey`, e.g.\n\t * `\"developer-products.create\"`).\n\t */\n\toperationKey: string;\n\t/**\n\t * Scope strings the API key or OAuth token must carry for the failing\n\t * operation, sourced from the vendored OpenAPI schema's `x-roblox-scopes`\n\t * for that operationId.\n\t */\n\trequiredScopes: ReadonlyArray<string>;\n}\n\n/**\n * Thrown when the Roblox Open Cloud API returns a 401 or 403 for an operation\n * whose required scopes are known. Subclass of {@link ApiError} carrying the\n * scope strings the operation requires plus the operation key, so a consumer\n * can name them when guiding the user to their API key settings.\n *\n * The scopes are what the operation needs, not a diagnosis of what the\n * credential lacks: a 403 does mean the scopes fall short, but Roblox also\n * answers 401 for a key that is invalid, disabled, or expired. Check\n * {@link ApiError.statusCode} before wording the failure as a missing scope.\n *\n * @since 0.1.0\n *\n * @example\n *\n * ```ts\n * import { PermissionError } from \"@bedrock-rbx/ocale\";\n *\n * const error = new PermissionError(\"HTTP 403\", {\n *     operationKey: \"developer-products.create\",\n *     requiredScopes: [\"creator-store-product:write\"],\n *     statusCode: 403,\n * });\n *\n * expect(error).toBeInstanceOf(PermissionError);\n * expect(error.requiredScopes).toStrictEqual([\"creator-store-product:write\"]);\n * expect(error.operationKey).toBe(\"developer-products.create\");\n * ```\n */\nexport class PermissionError extends ApiError {\n\tpublic override readonly name: string = \"PermissionError\";\n\tpublic readonly operationKey: string;\n\tpublic readonly requiredScopes: ReadonlyArray<string>;\n\n\t/**\n\t * Creates a new PermissionError.\n\t *\n\t * @param message - Human-readable error description.\n\t * @param options - Error options including status code, the operation key,\n\t *   and the scopes the caller's credential must carry.\n\t */\n\tconstructor(message: string, options: PermissionErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.operationKey = options.operationKey;\n\t\tthis.requiredScopes = options.requiredScopes;\n\t}\n}\n","import type { AdmissionWaitReason } from \"../client/types.ts\";\nimport { OpenCloudError } from \"./base.ts\";\n\n/**\n * Options for constructing a {@link RequestDeadlineExceededError}.\n *\n * @since 0.3.2\n */\nexport interface RequestDeadlineExceededErrorOptions extends ErrorOptions {\n\t/** Absolute caller-supplied deadline, as Unix epoch milliseconds. */\n\treadonly deadlineMs: number;\n\t/** Time left when the SDK refused or ended the wait. */\n\treadonly remainingMs: number;\n\t/** Intended wait duration, when it was known before waiting. */\n\treadonly waitMs?: number | undefined;\n\t/** SDK admission mechanism whose wait could not meet the deadline. */\n\treadonly waitReason?: AdmissionWaitReason | undefined;\n}\n\n/**\n * Returned when a logical request cannot complete by its absolute deadline.\n * Optional wait details identify an SDK-managed admission wait that was\n * refused. This is distinct from caller cancellation so consumers can report\n * exhausted wall-clock budget accurately.\n *\n * @since 0.3.2\n *\n * @example\n *\n * ```ts\n * import { RequestDeadlineExceededError } from \"@bedrock-rbx/ocale\";\n *\n * const error = new RequestDeadlineExceededError(\"Request deadline elapsed\", {\n *     deadlineMs: 1_000_000,\n *     remainingMs: 0,\n *     waitReason: \"operation-queue\",\n * });\n *\n * expect(error.remainingMs).toBe(0);\n * expect(error.waitReason).toBe(\"operation-queue\");\n * ```\n */\nexport class RequestDeadlineExceededError extends OpenCloudError {\n\t/** Absolute caller-supplied deadline, as Unix epoch milliseconds. */\n\tpublic readonly deadlineMs: number;\n\tpublic override readonly name: string = \"RequestDeadlineExceededError\";\n\t/** Time left when the SDK refused or ended the wait. */\n\tpublic readonly remainingMs: number;\n\t/** Intended wait duration, when known. */\n\tpublic readonly waitMs: number | undefined;\n\t/** SDK admission mechanism whose wait could not meet the deadline. */\n\tpublic readonly waitReason: AdmissionWaitReason | undefined;\n\n\t/**\n\t * Creates a new RequestDeadlineExceededError.\n\t *\n\t * @param message - Human-readable description of the exhausted deadline.\n\t * @param options - Deadline, remaining budget, and optional wait details.\n\t */\n\tconstructor(message: string, options: RequestDeadlineExceededErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.deadlineMs = options.deadlineMs;\n\t\tthis.remainingMs = options.remainingMs;\n\t\tthis.waitMs = options.waitMs;\n\t\tthis.waitReason = options.waitReason;\n\t}\n}\n","import { RequestDeadlineExceededError } from \"./request-deadline-exceeded.ts\";\n\n/**\n * Options for constructing a {@link RetryDelayExceededError}.\n *\n * @since 0.3.2\n */\nexport interface RetryDelayExceededErrorOptions extends ErrorOptions {\n\t/** Absolute caller-supplied deadline, as Unix epoch milliseconds. */\n\treadonly deadlineMs: number;\n\t/** Time left when the SDK refused the retry delay. */\n\treadonly remainingMs: number;\n\t/** Computed retry delay that could not fit before the deadline. */\n\treadonly retryAfterMs: number;\n}\n\n/**\n * Returned when the SDK refuses a retry delay that cannot complete before the\n * request deadline. This is distinct from cancellation so consumers can\n * report the server's stated retry time without waiting for it.\n *\n * @since 0.3.2\n *\n * @example\n *\n * ```ts\n * import { RetryDelayExceededError } from \"@bedrock-rbx/ocale\";\n *\n * const error = new RetryDelayExceededError(\"Retry delay exceeds the request deadline\", {\n *     deadlineMs: 1_000_000,\n *     remainingMs: 495_000,\n *     retryAfterMs: 1_856_000,\n * });\n *\n * expect(error.retryAfterMs).toBe(1_856_000);\n * expect(error.remainingMs).toBe(495_000);\n * ```\n */\nexport class RetryDelayExceededError extends RequestDeadlineExceededError {\n\tpublic override readonly name: string = \"RetryDelayExceededError\";\n\t/** Computed retry delay refused by the SDK, in milliseconds. */\n\tpublic readonly retryAfterMs: number;\n\t/** Computed retry delay refused by the SDK, in seconds. */\n\tpublic readonly retryAfterSeconds: number;\n\n\t/**\n\t * Creates a new RetryDelayExceededError.\n\t *\n\t * @param message - Human-readable description of the refused retry delay.\n\t * @param options - Refused delay, deadline budget, and original failure.\n\t */\n\tconstructor(message: string, options: RetryDelayExceededErrorOptions) {\n\t\tsuper(message, {\n\t\t\t...options,\n\t\t\twaitMs: options.retryAfterMs,\n\t\t\twaitReason: \"retry-delay\",\n\t\t});\n\t\tthis.retryAfterMs = options.retryAfterMs;\n\t\tthis.retryAfterSeconds = options.retryAfterMs / 1000;\n\t}\n}\n","/**\n * Maximum cause-chain depth walked by {@link findErrorCode}. Caps pathological\n * self-referential or deeply nested chains; transport failures surface as\n * `NetworkError → TypeError(\"fetch failed\") → OS Error{code}`, so three\n * levels is the expected shape and five leaves headroom.\n */\nconst MAX_DEPTH = 5;\n\n/**\n * Walks an error's `cause` chain and returns the first node-style string\n * `code` it finds (for example `\"ECONNRESET\"`, `\"ETIMEDOUT\"`). Native `fetch`\n * surfaces a transport reset as a `NetworkError` wrapping a\n * `TypeError(\"fetch failed\")` whose own cause carries the OS-level `code`, so\n * the code lives several links down the chain.\n *\n * @example\n *\n * ```ts\n * import { findErrorCode } from \"./find-error-code\";\n *\n * const root = Object.assign(new Error(\"read ECONNRESET\"), { code: \"ECONNRESET\" });\n * const outer = new Error(\"Network request failed\", {\n *     cause: new TypeError(\"fetch failed\", { cause: root }),\n * });\n *\n * expect(findErrorCode(outer)).toBe(\"ECONNRESET\");\n * ```\n *\n * @param error - The error to inspect; typically a `NetworkError`.\n * @returns The first string `code` in the chain, or `undefined` if none.\n */\nexport function findErrorCode(error: unknown): string | undefined {\n\tlet current: unknown = error;\n\tfor (let depth = 0; depth < MAX_DEPTH && current instanceof Error; depth += 1) {\n\t\tconst code = readCode(current);\n\t\tif (code !== undefined) {\n\t\t\treturn code;\n\t\t}\n\n\t\tcurrent = current.cause;\n\t}\n\n\treturn undefined;\n}\n\nfunction readCode(error: Error): string | undefined {\n\tconst code = Reflect.get(error, \"code\");\n\treturn typeof code === \"string\" ? code : undefined;\n}\n\n/**\n * `DOMException.name` produced when an `AbortSignal.timeout` fires. This is the\n * web-standard discriminator (stable across Node and Bun, unlike the\n * runtime-specific message) and distinguishes the SDK's own request timeout\n * from a caller-supplied cancellation, which surfaces as `\"AbortError\"`.\n */\nconst TIMEOUT_ABORT_NAME = \"TimeoutError\";\n\n/**\n * Reports whether an error chain was produced by the SDK's own\n * `AbortSignal.timeout` self-abort. Such a `DOMException` carries a numeric\n * `code` (23), so {@link findErrorCode} (which only reads string codes)\n * cannot classify it; this walk keys on `name` instead. A caller-supplied\n * abort (`\"AbortError\"`) is deliberately not matched.\n *\n * @example\n *\n * ```ts\n * import { isTimeoutAbort } from \"./find-error-code\";\n *\n * const error = new Error(\"Network request failed\", {\n *     cause: new DOMException(\"timed out\", \"TimeoutError\"),\n * });\n *\n * expect(isTimeoutAbort(error)).toBe(true);\n * expect(isTimeoutAbort(new DOMException(\"cancelled\", \"AbortError\"))).toBe(false);\n * ```\n *\n * @param error - The error to inspect; typically a `NetworkError`.\n * @returns `true` when a `TimeoutError` abort sits within the cause chain.\n */\nexport function isTimeoutAbort(error: unknown): boolean {\n\tlet current: unknown = error;\n\tfor (let depth = 0; depth < MAX_DEPTH && current instanceof Error; depth += 1) {\n\t\tif (Reflect.get(current, \"name\") === TIMEOUT_ABORT_NAME) {\n\t\t\treturn true;\n\t\t}\n\n\t\tcurrent = current.cause;\n\t}\n\n\treturn false;\n}\n","import { ApiError } from \"../../errors/api-error.ts\";\nimport { NetworkError } from \"../../errors/network-error.ts\";\nimport { hasServerRetryGuidance, RateLimitError } from \"../../errors/rate-limit.ts\";\nimport { findErrorCode, isTimeoutAbort } from \"../utils/find-error-code.ts\";\n\n/**\n * Fully-resolved retry config shape that {@link mergeConfig} and\n * {@link shouldRetry} operate on. Fields are required because this represents\n * the post-defaulting, internal view; callers should supply every field (or\n * resolve them via a test factory / client constructor). The partial,\n * user-facing type lives on client construction options; method defaults and\n * per-request overrides use `Partial<RetryResolvable>`.\n */\nexport interface RetryResolvable {\n\t/** Roblox Open Cloud API key. */\n\treadonly apiKey: string;\n\t/** Base URL for the Open Cloud API. */\n\treadonly baseUrl: string;\n\t/** Maximum retry attempts before giving up. */\n\treadonly maxRetries: number;\n\t/** Status codes that are eligible for retry. */\n\treadonly retryableStatuses: ReadonlyArray<number>;\n\t/**\n\t * Codes for transport-level failures eligible for retry: node-style\n\t * transport codes ({@link findErrorCode}) surfaced as a\n\t * {@link NetworkError}, plus the synthetic {@link GATEWAY_REJECTED} for a\n\t * response served by an edge gateway. Not all of them prove the request\n\t * went unprocessed — see {@link TRANSIENT_TRANSPORT_CODES}. Empty for\n\t * create operations by default; consumers opt a create in via a per-request\n\t * override.\n\t */\n\treadonly retryableTransportCodes: ReadonlyArray<string>;\n\t/** Fallback delay function when no server hint is available. */\n\treadonly retryDelay: (attempt: number) => number;\n\t/** Per-request timeout in milliseconds. */\n\treadonly timeout: number;\n}\n\n/**\n * Transient transport error codes that are safe to retry for idempotent\n * operations. Connection resets, timeouts, and DNS hiccups are recoverable on\n * a retry. A self-aborted request timeout carries no OS-level `code`, so\n * {@link shouldRetry} folds it into this set as `ETIMEDOUT` (via\n * {@link isTimeoutAbort}) for idempotent methods; create methods retry no\n * transport codes and so still never re-issue a timed-out write.\n *\n * `ERR_HTTP2_STREAM_ERROR`, `ERR_HTTP2_SESSION_ERROR`, and `UND_ERR_INFO` are\n * the same deaths as spelled by a runtime whose `fetch` negotiates HTTP/2\n * (Node 26 and later). They differ from the socket codes in one way that\n * matters: they do not prove the request went unprocessed. `UND_ERR_INFO`\n * covers both a `GOAWAY` declaring a stream was never started and a stream\n * that was fully sent, and `UND_ERR_SOCKET` can fire once a response is\n * already streaming. Retrying them is therefore justified by the operation\n * being safe to repeat, never by the request having gone unseen — which is\n * why {@link UPLOAD_METHOD_DEFAULTS}, the one write policy that includes\n * them, documents its own grounds.\n *\n * @since 0.1.0\n */\nexport const TRANSIENT_TRANSPORT_CODES: ReadonlyArray<string> = Object.freeze([\n\t\"ECONNRESET\",\n\t\"ECONNREFUSED\",\n\t\"ETIMEDOUT\",\n\t\"EPIPE\",\n\t\"ENETUNREACH\",\n\t\"EHOSTDOWN\",\n\t\"EAI_AGAIN\",\n\t\"UND_ERR_SOCKET\",\n\t\"ERR_HTTP2_STREAM_ERROR\",\n\t\"ERR_HTTP2_SESSION_ERROR\",\n\t\"UND_ERR_INFO\",\n]);\n\n/**\n * Synthetic transport code for a response that came from an edge gateway\n * rather than Open Cloud ({@link ApiError.gatewaySummary}). Such a response\n * proves the request was rejected before any Open Cloud handler saw it, so it\n * is classified alongside {@link TRANSIENT_TRANSPORT_CODES} rather than by its\n * HTTP status: the status belongs to the gateway, not to the API, and a\n * gateway `400` says nothing about the validity of the request.\n *\n * Name it in a per-request `retryableTransportCodes` override to opt an\n * operation into (or out of) gateway-rejection retry.\n *\n * @since 0.1.2\n */\nexport const GATEWAY_REJECTED = \"GATEWAY_REJECTED\";\n\n/**\n * Synthetic transport code for a 2xx whose body could not be parsed as JSON\n * ({@link ApiError.unparsedBodyLength}). Open Cloud does not answer a success\n * status with a malformed document; what this failure describes in practice is\n * a body the edge delivered short, ending mid-token at exactly the length that\n * arrived. The next read is a fresh body and usually a whole one.\n *\n * Classifying it by transport code rather than by HTTP status is what makes it\n * recoverable at all: the status is a 200, which no `retryableStatuses` list\n * contains, so a status-keyed decision could only ever say \"do not retry\".\n *\n * Retrying is justified by the operation being safe to repeat, never by the\n * request having gone unprocessed — a 200 proves it was processed. So this code\n * is in {@link IDEMPOTENT_METHOD_DEFAULTS} only. Creates and uploads leave it\n * out: their write landed, and re-issuing it to re-read the answer would risk a\n * second resource for a response body, which is the wrong trade. A consumer who\n * can tolerate that duplicate names this code in a per-request\n * `retryableTransportCodes` override.\n *\n * @since 0.1.5\n */\nexport const RESPONSE_UNPARSEABLE = \"RESPONSE_UNPARSEABLE\";\n\n/** Method-level retry defaults, keyed by {@link MethodKind}. */\ntype MethodDefaults = Readonly<\n\tPick<RetryResolvable, \"retryableStatuses\" | \"retryableTransportCodes\">\n>;\n\n/**\n * Default retry policy for idempotent operations (read, list, update,\n * delete). Safe to retry on rate limits, transient server errors, and\n * transient transport failures.\n */\nexport const IDEMPOTENT_METHOD_DEFAULTS: MethodDefaults = Object.freeze({\n\tretryableStatuses: Object.freeze([429, 500, 502, 503, 504] as const),\n\tretryableTransportCodes: Object.freeze([...TRANSIENT_TRANSPORT_CODES, RESPONSE_UNPARSEABLE]),\n});\n\n/**\n * Default retry policy for create operations. Retries rate limits only (no\n * 5xx and no transport-error retries) to prevent duplicate resources, since\n * Roblox Open Cloud has no idempotency-key support. Consumers who can tolerate\n * a duplicate opt in per request.\n */\nexport const CREATE_METHOD_DEFAULTS: MethodDefaults = Object.freeze({\n\tretryableStatuses: Object.freeze([429] as const),\n\tretryableTransportCodes: Object.freeze([] as const),\n});\n\n/**\n * Default retry policy for upload operations (place publish and save). Keeps\n * {@link CREATE_METHOD_DEFAULTS}'s 5xx guard (a 5xx comes from Open Cloud and\n * may describe a write that partly landed), but retries transport failures:\n * {@link TRANSIENT_TRANSPORT_CODES} and {@link GATEWAY_REJECTED}.\n *\n * This is the one write policy that retries codes which do not prove the\n * request went unprocessed, and the grounds are specific to a place version:\n * Roblox dedupes identical place content, so a retry that races a publish\n * which did land returns that same version rather than creating a second one.\n * That is load-bearing, not incidental — an upload operation without content\n * dedupe would need its own allowlist.\n */\nexport const UPLOAD_METHOD_DEFAULTS: MethodDefaults = Object.freeze({\n\t...CREATE_METHOD_DEFAULTS,\n\tretryableTransportCodes: Object.freeze([...TRANSIENT_TRANSPORT_CODES, GATEWAY_REJECTED]),\n});\n\n/** Kind of HTTP method the merge is being performed for. */\nexport type MethodKind = \"create\" | \"idempotent\";\n\n/**\n * Options for {@link mergeConfig}.\n *\n * @template T - Concrete `RetryResolvable` subtype being merged.\n */\ninterface MergeConfigOptions<T> {\n\t/** Method-level defaults (e.g. {@link CREATE_METHOD_DEFAULTS}). */\n\treadonly methodDefaults: Partial<T>;\n\t/** Whether the method is a create or idempotent operation. */\n\treadonly methodKind: MethodKind;\n\t/** Optional per-request overrides; always win when provided. */\n\treadonly requestOptions?: Partial<T>;\n}\n\n/**\n * Options for {@link computeRetryWaitMs}.\n */\ninterface ComputeRetryWaitMsOptions {\n\t/** Zero-indexed retry attempt number. */\n\treadonly attempt: number;\n\t/** Fallback delay function when no server hint is available. */\n\treadonly retryDelay: (attempt: number) => number;\n}\n\n/**\n * Default exponential backoff: 1s → 2s → 4s → 8s → 16s → 30s (capped).\n *\n * @example\n *\n * ```ts\n * import { defaultRetryDelay } from \"./retry\";\n *\n * expect(defaultRetryDelay(0)).toBe(1000);\n * expect(defaultRetryDelay(4)).toBe(16_000);\n * expect(defaultRetryDelay(10)).toBe(30_000);\n * ```\n *\n * @param attempt - Zero-indexed retry attempt number.\n * @returns Wait duration in milliseconds.\n */\nexport function defaultRetryDelay(attempt: number): number {\n\treturn Math.min(1000 * 2 ** attempt, 30_000);\n}\n\n/**\n * Computes how long to wait before the next retry. Prefers valid server\n * guidance on a {@link RateLimitError}, including an explicit zero-second\n * delay; otherwise falls through to `retryDelay(attempt)`.\n *\n * @example\n *\n * ```ts\n * import { RateLimitError } from \"../../errors/rate-limit.ts\";\n * import { computeRetryWaitMs, defaultRetryDelay } from \"./retry\";\n *\n * const error = new RateLimitError(\"slow down\", { retryAfterSeconds: 3 });\n *\n * expect(computeRetryWaitMs(error, { attempt: 0, retryDelay: defaultRetryDelay })).toBe(\n *     3000,\n * );\n * ```\n *\n * @example\n *\n * ```ts\n * import { ApiError } from \"../../errors/api-error.ts\";\n * import { computeRetryWaitMs, defaultRetryDelay } from \"./retry\";\n *\n * const error = new ApiError(\"server error\", { statusCode: 503 });\n *\n * expect(computeRetryWaitMs(error, { attempt: 2, retryDelay: defaultRetryDelay })).toBe(\n *     4000,\n * );\n * ```\n *\n * @param error - The error returned by the failing request.\n * @param options - Retry attempt index and fallback delay function.\n * @returns Wait duration in milliseconds before the next attempt.\n */\nexport function computeRetryWaitMs(\n\terror: ApiError | NetworkError | RateLimitError,\n\toptions: ComputeRetryWaitMsOptions,\n): number {\n\tif (error instanceof RateLimitError && hasServerRetryGuidance(error)) {\n\t\treturn error.retryAfterSeconds * 1000;\n\t}\n\n\treturn options.retryDelay(options.attempt);\n}\n\n/**\n * Decides whether a failed request is eligible for retry. {@link\n * RateLimitError} (checked against 429) and {@link ApiError} (checked against\n * its `statusCode`) are retryable when their status is in `retryableStatuses`.\n * Two {@link ApiError} shapes are the exception, both checked against\n * `retryableTransportCodes` with their status never consulted: one carrying a\n * `gatewaySummary` is checked against {@link GATEWAY_REJECTED}, and one\n * carrying an `unparsedBodyLength` — a 2xx whose body would not parse —\n * against {@link RESPONSE_UNPARSEABLE}. A {@link NetworkError} is\n * retryable when its transport code ({@link findErrorCode}) is in\n * `retryableTransportCodes`. This is how transient connection resets recover.\n * A self-aborted request timeout ({@link isTimeoutAbort}) carries no transport\n * code, so it is classified as `ETIMEDOUT`: recovered for idempotent methods,\n * never for creates (whose list is empty). All other failures return `false`.\n *\n * @example\n *\n * ```ts\n * import { RateLimitError } from \"../../errors/rate-limit.ts\";\n * import { shouldRetry } from \"./retry\";\n *\n * const error = new RateLimitError(\"\", { retryAfterSeconds: 1 });\n *\n * expect(shouldRetry(error, { retryableStatuses: [429], retryableTransportCodes: [] })).toBe(\n *     true,\n * );\n * ```\n *\n * @example\n *\n * ```ts\n * import { NetworkError } from \"../../errors/network-error.ts\";\n * import { shouldRetry } from \"./retry\";\n *\n * const reset = Object.assign(new Error(\"read ECONNRESET\"), { code: \"ECONNRESET\" });\n * const error = new NetworkError(\"Network request failed\", { cause: reset });\n *\n * expect(\n *     shouldRetry(error, { retryableStatuses: [], retryableTransportCodes: [\"ECONNRESET\"] }),\n * ).toBe(true);\n * ```\n *\n * @param error - The error returned by the failing request.\n * @param config - Object carrying the retry-eligible status and transport-code lists.\n * @returns `true` if the error should be retried, `false` otherwise.\n */\nexport function shouldRetry(\n\terror: unknown,\n\tconfig: {\n\t\treadonly retryableStatuses: ReadonlyArray<number>;\n\t\treadonly retryableTransportCodes: ReadonlyArray<string>;\n\t},\n): error is ApiError | NetworkError | RateLimitError {\n\tif (error instanceof RateLimitError) {\n\t\treturn config.retryableStatuses.includes(429);\n\t}\n\n\tif (error instanceof ApiError) {\n\t\tif (error.gatewaySummary !== undefined) {\n\t\t\treturn config.retryableTransportCodes.includes(GATEWAY_REJECTED);\n\t\t}\n\n\t\tif (error.unparsedBodyLength !== undefined) {\n\t\t\treturn config.retryableTransportCodes.includes(RESPONSE_UNPARSEABLE);\n\t\t}\n\n\t\treturn config.retryableStatuses.includes(error.statusCode);\n\t}\n\n\tif (error instanceof NetworkError) {\n\t\tconst code = findErrorCode(error) ?? (isTimeoutAbort(error) ? \"ETIMEDOUT\" : undefined);\n\t\treturn code !== undefined && config.retryableTransportCodes.includes(code);\n\t}\n\n\treturn false;\n}\n\n/**\n * Resolves the effective config for a single request by shallow-merging the\n * client config, method defaults, and per-request options. Precedence depends\n * on `methodKind`:\n *\n * - `\"create\"`: method defaults override client config, so client-level\n *   settings cannot silently relax create-method safety. Only explicit\n *   per-request `requestOptions` can.\n * - `\"idempotent\"`: client config overrides method defaults, so consumers\n *   can loosen or tighten retry policy globally. `requestOptions` still wins\n *   when provided.\n *\n * Array-valued fields like `retryableStatuses` are *replaced*, not extended.\n *\n * @template T - Concrete `RetryResolvable` subtype being merged.\n *\n * @example\n *\n * ```ts\n * import {\n *     CREATE_METHOD_DEFAULTS,\n *     defaultRetryDelay,\n *     mergeConfig,\n *     type RetryResolvable,\n * } from \"./retry\";\n *\n * const clientConfig: RetryResolvable = {\n *     apiKey: \"k\",\n *     baseUrl: \"https://apis.roblox.com\",\n *     maxRetries: 3,\n *     retryableStatuses: [429, 500],\n *     retryableTransportCodes: [],\n *     retryDelay: defaultRetryDelay,\n *     timeout: 30_000,\n * };\n *\n * const merged = mergeConfig(clientConfig, {\n *     methodDefaults: CREATE_METHOD_DEFAULTS,\n *     methodKind: \"create\",\n * });\n *\n * expect(merged.retryableStatuses).toStrictEqual([429]);\n * ```\n *\n * @example\n *\n * ```ts\n * import {\n *     defaultRetryDelay,\n *     IDEMPOTENT_METHOD_DEFAULTS,\n *     mergeConfig,\n *     type RetryResolvable,\n * } from \"./retry\";\n *\n * const clientConfig: RetryResolvable = {\n *     apiKey: \"k\",\n *     baseUrl: \"https://apis.roblox.com\",\n *     maxRetries: 3,\n *     retryableStatuses: [429],\n *     retryableTransportCodes: [],\n *     retryDelay: defaultRetryDelay,\n *     timeout: 30_000,\n * };\n *\n * const merged = mergeConfig(clientConfig, {\n *     methodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n *     methodKind: \"idempotent\",\n *     requestOptions: { timeout: 10_000 },\n * });\n *\n * expect(merged.retryableStatuses).toStrictEqual([429]);\n * expect(merged.timeout).toBe(10_000);\n * ```\n *\n * @param clientConfig - Config frozen at client construction.\n * @param options - Method defaults, method kind, and optional per-request overrides.\n * @returns A new merged config object. Inputs are not mutated.\n */\nexport function mergeConfig<T extends RetryResolvable>(\n\tclientConfig: T,\n\t{ methodDefaults, methodKind, requestOptions }: MergeConfigOptions<T>,\n): T {\n\tswitch (methodKind) {\n\t\tcase \"create\": {\n\t\t\treturn { ...clientConfig, ...methodDefaults, ...requestOptions };\n\t\t}\n\t\tcase \"idempotent\": {\n\t\t\treturn { ...methodDefaults, ...clientConfig, ...requestOptions };\n\t\t}\n\t\tdefault: {\n\t\t\tconst exhaustive: never = methodKind;\n\t\t\tthrow new Error(`Unexpected methodKind: ${String(exhaustive)}`);\n\t\t}\n\t}\n}\n"],"mappings":"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"}