{"version":3,"file":"network-error-DR9FX5hb.mjs","names":[],"sources":["../src/errors/base.ts","../src/errors/api-error.ts","../src/errors/rate-limit.ts","../src/errors/network-error.ts"],"sourcesContent":["/**\n * Options for constructing an {@link OpenCloudError}.\n *\n * @since 0.3.0\n */\nexport interface OpenCloudErrorOptions extends ErrorOptions {\n\t/**\n\t * Machine-readable classifier for the failure, when the error has one. An\n\t * `ApiError` and `RateLimitError` fill it from the response body; a\n\t * `ValidationError` narrows it to its own closed union. Errors with nothing\n\t * to classify (transport failures and poll timeouts) leave it `undefined`.\n\t */\n\treadonly code?: string | undefined;\n}\n\n/**\n * Base error class for all Open Cloud SDK errors.\n *\n * All specific error types (RateLimitError, ApiError, NetworkError)\n * extend this class, enabling `instanceof OpenCloudError` checks.\n *\n * `code` is declared here rather than on the subclasses that populate it, so a\n * caller holding the `OpenCloudError` that `Result.err` is typed as can branch\n * on it without first narrowing to a subclass.\n *\n * @since 0.1.0\n *\n * @example\n *\n * ```ts\n * import { ApiError, OpenCloudError } from \"@bedrock-rbx/ocale\";\n *\n * // `Result.err` is typed as OpenCloudError, so a caller draining a queue\n * // branches on the canonical status without narrowing to a subclass first.\n * const err: OpenCloudError = new ApiError(\"HTTP 404: Queue items not found.\", {\n *     code: \"NOT_FOUND\",\n *     statusCode: 404,\n * });\n *\n * expect(err.code).toBe(\"NOT_FOUND\");\n * expect(new OpenCloudError(\"no classifier\").code).toBeUndefined();\n * ```\n */\nexport class OpenCloudError extends Error {\n\t/** Machine-readable classifier, or `undefined` when the error has none. */\n\tpublic readonly code: string | undefined;\n\tpublic override readonly name: string = \"OpenCloudError\";\n\n\t/**\n\t * Creates a new OpenCloudError.\n\t *\n\t * @param message - Human-readable error description.\n\t * @param options - Error options including the optional `cause` and the\n\t *   machine-readable `code`.\n\t */\n\tconstructor(message?: string, options?: OpenCloudErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.code = options?.code;\n\t}\n}\n","import { OpenCloudError, type OpenCloudErrorOptions } from \"./base.ts\";\n\n/**\n * Options for constructing an {@link ApiError}.\n *\n * @since 0.1.0\n */\nexport interface ApiErrorOptions extends OpenCloudErrorOptions {\n\t/** Parsed response body, when present. */\n\tdetails?: JSONValue | undefined;\n\t/**\n\t * Wall-clock time the request was in flight before this error, in\n\t * milliseconds. Present for errors built by the transport; a long elapsed\n\t * time on an intermittent failure points at a load or timeout correlation.\n\t */\n\telapsedMs?: number | undefined;\n\t/**\n\t * Human-readable summary extracted from an HTML gateway error page, set\n\t * when the error body was such a page (an HAProxy-style load-balancer\n\t * rejection) rather than an Open Cloud response. When present, the raw HTML\n\t * is not retained on {@link ApiError.details}.\n\t */\n\tgatewaySummary?: string | undefined;\n\t/** HTTP method of the request that produced this error. */\n\tmethod?: string | undefined;\n\t/**\n\t * Allowlisted response headers useful for diagnosis and escalation (request\n\t * ids, edge/server identifiers). The full header set is never retained, to\n\t * avoid surfacing anything sensitive and to keep errors light.\n\t */\n\tresponseHeaders?: Readonly<Record<string, string>> | undefined;\n\t/** HTTP status code from the API response. */\n\tstatusCode: number;\n\t/**\n\t * Length, in decoded characters, of a 2xx body that could not be parsed as\n\t * JSON. Set only by the transport, and only for that failure — nothing else\n\t * builds an {@link ApiError} over a successful status — so its presence is\n\t * also how a body-parse failure is told apart from an API rejection. The\n\t * number is the diagnostic one: a body that stops mid-token at exactly the\n\t * length the edge delivered is a truncated read, not malformed JSON.\n\t */\n\tunparsedBodyLength?: number | undefined;\n\t/** Fully-qualified URL of the request that produced this error. */\n\turl?: string | undefined;\n}\n\n/**\n * Everything in {@link ApiErrorOptions} except what the API answered ({@link\n * ApiErrorOptions.code}, {@link ApiErrorOptions.details}, and {@link\n * ApiErrorOptions.statusCode}): the request the transport made, how long it was\n * in flight, and what the response carried alongside its body. Every field is\n * required, and every one accepts `undefined` for a field the transport did not\n * capture.\n *\n * @since 0.2.0\n */\nexport type ApiRequestContext = {\n\t[K in Exclude<keyof ApiErrorOptions, \"cause\" | \"code\" | \"details\" | \"statusCode\">]-?:\n\t\t| ApiErrorOptions[K]\n\t\t| undefined;\n};\n\n/**\n * Thrown when the Roblox Open Cloud API returns a non-2xx response\n * that is not a rate limit (429).\n *\n * @since 0.1.0\n *\n * @example\n *\n * ```ts\n * import { ApiError } from \"@bedrock-rbx/ocale\";\n *\n * const error = new ApiError(\"HTTP 404: Pass not found (code NotFound)\", {\n *     code: \"NotFound\",\n *     details: { errorCode: \"NotFound\", message: \"Pass not found\" },\n *     statusCode: 404,\n * });\n *\n * expect(error).toBeInstanceOf(ApiError);\n * expect(error.statusCode).toBe(404);\n * expect(error.code).toBe(\"NotFound\");\n * expect(error.details).toEqual({\n *     errorCode: \"NotFound\",\n *     message: \"Pass not found\",\n * });\n * ```\n */\nexport class ApiError extends OpenCloudError {\n\tpublic readonly details: JSONValue | undefined;\n\tpublic readonly elapsedMs: number | undefined;\n\tpublic readonly gatewaySummary: string | undefined;\n\tpublic readonly method: string | undefined;\n\tpublic override readonly name: string = \"ApiError\";\n\tpublic readonly responseHeaders: Readonly<Record<string, string>> | undefined;\n\tpublic readonly statusCode: number;\n\tpublic readonly unparsedBodyLength: number | undefined;\n\tpublic readonly url: string | undefined;\n\n\t/**\n\t * Creates a new ApiError.\n\t *\n\t * @param message - Human-readable error description.\n\t * @param options - Error options including status code, optional error\n\t *   code, the parsed response body when present, and the request context\n\t *   (method, url, elapsed time, allowlisted response headers) when built by\n\t *   the transport.\n\t */\n\tconstructor(message: string, options: ApiErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.statusCode = options.statusCode;\n\t\tthis.details = options.details;\n\t\tthis.method = options.method;\n\t\tthis.url = options.url;\n\t\tthis.elapsedMs = options.elapsedMs;\n\t\tthis.responseHeaders = options.responseHeaders;\n\t\tthis.gatewaySummary = options.gatewaySummary;\n\t\tthis.unparsedBodyLength = options.unparsedBodyLength;\n\t}\n}\n\n/**\n * Reads the request context off an {@link ApiError} so a replacement error can\n * carry it. Spread the result into the options of the new error. {@link\n * OpenCloudError.code}, {@link ApiError.details}, and {@link\n * ApiError.statusCode} describe the API's answer and are left to the caller.\n *\n * @since 0.2.0\n *\n * @param err - The error to read the request context from.\n * @returns The transport-captured fields, each undefined when unset.\n *\n * @example\n *\n * ```ts\n * import { ApiError, requestContextOf } from \"@bedrock-rbx/ocale\";\n *\n * const original = new ApiError(\"HTTP 404\", {\n *     elapsedMs: 512,\n *     method: \"GET\",\n *     statusCode: 404,\n *     url: \"https://apis.roblox.com/cloud/v2/universes/1\",\n * });\n *\n * const rewrapped = new ApiError(\"Universe 1 was not found; adoption failed\", {\n *     ...requestContextOf(original),\n *     statusCode: 404,\n * });\n *\n * expect(rewrapped.method).toBe(\"GET\");\n * expect(rewrapped.url).toBe(\"https://apis.roblox.com/cloud/v2/universes/1\");\n * expect(rewrapped.elapsedMs).toBe(512);\n * ```\n */\nexport function requestContextOf(err: ApiError): ApiRequestContext {\n\treturn {\n\t\telapsedMs: err.elapsedMs,\n\t\tgatewaySummary: err.gatewaySummary,\n\t\tmethod: err.method,\n\t\tresponseHeaders: err.responseHeaders,\n\t\tunparsedBodyLength: err.unparsedBodyLength,\n\t\turl: err.url,\n\t};\n}\n","import { OpenCloudError, type OpenCloudErrorOptions } from \"./base.ts\";\n\n/**\n * Options for constructing a {@link RateLimitError}.\n *\n * @since 0.1.0\n */\nexport interface RateLimitErrorOptions extends OpenCloudErrorOptions {\n\t/**\n\t * Parsed 429 response body, when present. Holds the server's 429\n\t * explanation (JSON when the body parses, otherwise the truncated raw\n\t * text) so a rate limit stays diagnosable from the error alone. A literal\n\t * JSON `null` remains `null`; an absent body is `undefined`.\n\t */\n\tdetails?: JSONValue | undefined;\n\t/**\n\t * Requests left in the reported rate-limit window. Read from\n\t * `x-ratelimit-remaining` using the smallest valid token.\n\t *\n\t * `undefined` when the header has no valid non-negative integer token.\n\t *\n\t * Parsed separately from `x-ratelimit-reset`; a valid value survives an\n\t * invalid reset.\n\t *\n\t * This is one budget reading, not a classifier for the cause of the 429.\n\t */\n\tremaining?: number | undefined;\n\t/**\n\t * Allowlisted response headers useful for diagnosing the 429. Values are\n\t * preserved exactly as the Fetch API presents them, including comma-joined\n\t * multi-window values. The full header set is never retained.\n\t */\n\tresponseHeaders?: Readonly<Record<string, string>> | undefined;\n\t/** Seconds to wait before retrying the request. */\n\tretryAfterSeconds: number;\n\t/**\n\t * HTTP status code that produced the error. Always `429` when minted by the\n\t * SDK transport; `undefined` when constructed without one.\n\t */\n\tstatusCode?: number | undefined;\n}\n\n/**\n * Thrown when the Roblox Open Cloud API returns a 429 Too Many Requests\n * response. Contains the server-suggested retry delay and safe,\n * machine-readable response evidence. Generic 429 evidence can be ambiguous:\n * no individual header, body code, or remaining-budget value guarantees the\n * upstream cause.\n *\n * @since 0.1.0\n *\n * @example\n *\n * ```ts\n * import { RateLimitError } from \"@bedrock-rbx/ocale\";\n *\n * const error = new RateLimitError(\"Too many requests\", {\n *     code: \"RESOURCE_EXHAUSTED\",\n *     remaining: 3,\n *     responseHeaders: {\n *         \"retry-after\": \"1856\",\n *         \"x-ratelimit-limit\": \"5, 5;w=60, 5;w=60\",\n *     },\n *     retryAfterSeconds: 1856,\n * });\n *\n * // Inspect the available evidence without assuming it identifies the cause.\n * const evidence = {\n *     code: error.code,\n *     limit: error.responseHeaders?.[\"x-ratelimit-limit\"],\n *     retryAfter: error.responseHeaders?.[\"retry-after\"],\n * };\n *\n * expect(evidence).toEqual({\n *     code: \"RESOURCE_EXHAUSTED\",\n *     limit: \"5, 5;w=60, 5;w=60\",\n *     retryAfter: \"1856\",\n * });\n * ```\n */\nexport class RateLimitError extends OpenCloudError {\n\t/**\n\t * Parsed 429 response body. A literal JSON `null` remains `null`; an absent\n\t * body is `undefined`.\n\t */\n\tpublic readonly details: JSONValue | undefined;\n\tpublic override readonly name = \"RateLimitError\";\n\t/**\n\t * Requests left in the reported window, or `undefined` if not reported.\n\t */\n\tpublic readonly remaining: number | undefined;\n\t/** Allowlisted raw response headers, or `undefined` if not set. */\n\tpublic readonly responseHeaders: Readonly<Record<string, string>> | undefined;\n\tpublic readonly retryAfterSeconds: number;\n\t/** HTTP status code that produced the error, or `undefined` if not set. */\n\tpublic readonly statusCode: number | undefined;\n\n\t/**\n\t * Creates a new RateLimitError.\n\t *\n\t * @param message - Human-readable error description.\n\t * @param options - Error options including the retry delay.\n\t */\n\tconstructor(message: string, options: RateLimitErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.retryAfterSeconds = options.retryAfterSeconds;\n\t\tthis.remaining = options.remaining;\n\t\tthis.responseHeaders = options.responseHeaders;\n\t\tthis.details = options.details;\n\t\tthis.statusCode = options.statusCode;\n\t}\n}\n\nconst GUIDED_ERRORS = new WeakSet<RateLimitError>();\n\n/**\n * Marks a transport-minted error whose zero-second delay is explicit guidance.\n *\n * @param error - The classified rate-limit failure.\n * @returns The same error, marked for retry orchestration.\n */\nexport function markServerRetryGuidance(error: RateLimitError): RateLimitError {\n\tGUIDED_ERRORS.add(error);\n\treturn error;\n}\n\n/**\n * Checks whether an error carries applicable server retry guidance.\n *\n * @param error - The rate-limit failure to inspect.\n * @returns Whether its delay should override caller backoff.\n */\nexport function hasServerRetryGuidance(error: RateLimitError): boolean {\n\treturn error.retryAfterSeconds > 0 || GUIDED_ERRORS.has(error);\n}\n","import { OpenCloudError } from \"./base.ts\";\n\n/**\n * Options for constructing a {@link NetworkError}.\n *\n * @since 0.1.0\n */\nexport interface NetworkErrorOptions extends ErrorOptions {\n\t/** HTTP method of the request that failed. */\n\tmethod?: string | undefined;\n\t/** Fully-qualified URL of the request that failed. */\n\turl?: string | undefined;\n}\n\n/**\n * Thrown when a network-level failure prevents the request from reaching\n * the Roblox Open Cloud API (e.g., DNS resolution failure, connection reset).\n * The `method` and `url` name the failing call so a transport failure that\n * survives every retry can be diagnosed; the underlying transport error is\n * carried on `cause`.\n *\n * @since 0.1.0\n */\nexport class NetworkError extends OpenCloudError {\n\tpublic readonly method: string | undefined;\n\tpublic override readonly name: string = \"NetworkError\";\n\tpublic readonly url: string | undefined;\n\n\t/**\n\t * Creates a new NetworkError.\n\t *\n\t * @param message - Human-readable error description.\n\t * @param options - Error options including the optional `cause` and the\n\t *   `method` / `url` of the request that failed.\n\t */\n\tconstructor(message: string, options?: NetworkErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.method = options?.method;\n\t\tthis.url = options?.url;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,iBAAb,cAAoC,MAAM;;CAEzC;CACA,OAAwC;;;;;;;;CASxC,YAAY,SAAkB,SAAiC;EAC9D,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO,SAAS;CACtB;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC6BA,IAAa,WAAb,cAA8B,eAAe;CAC5C;CACA;CACA;CACA;CACA,OAAwC;CACxC;CACA;CACA;CACA;;;;;;;;;;CAWA,YAAY,SAAiB,SAA0B;EACtD,MAAM,SAAS,OAAO;EACtB,KAAK,aAAa,QAAQ;EAC1B,KAAK,UAAU,QAAQ;EACvB,KAAK,SAAS,QAAQ;EACtB,KAAK,MAAM,QAAQ;EACnB,KAAK,YAAY,QAAQ;EACzB,KAAK,kBAAkB,QAAQ;EAC/B,KAAK,iBAAiB,QAAQ;EAC9B,KAAK,qBAAqB,QAAQ;CACnC;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,iBAAiB,KAAkC;CAClE,OAAO;EACN,WAAW,IAAI;EACf,gBAAgB,IAAI;EACpB,QAAQ,IAAI;EACZ,iBAAiB,IAAI;EACrB,oBAAoB,IAAI;EACxB,KAAK,IAAI;CACV;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACnFA,IAAa,iBAAb,cAAoC,eAAe;;;;;CAKlD;CACA,OAAgC;;;;CAIhC;;CAEA;CACA;;CAEA;;;;;;;CAQA,YAAY,SAAiB,SAAgC;EAC5D,MAAM,SAAS,OAAO;EACtB,KAAK,oBAAoB,QAAQ;EACjC,KAAK,YAAY,QAAQ;EACzB,KAAK,kBAAkB,QAAQ;EAC/B,KAAK,UAAU,QAAQ;EACvB,KAAK,aAAa,QAAQ;CAC3B;AACD;AAEA,MAAM,gCAAgB,IAAI,QAAwB;;;;;;;AAQlD,SAAgB,wBAAwB,OAAuC;CAC9E,cAAc,IAAI,KAAK;CACvB,OAAO;AACR;;;;;;;AAQA,SAAgB,uBAAuB,OAAgC;CACtE,OAAO,MAAM,oBAAoB,KAAK,cAAc,IAAI,KAAK;AAC9D;;;;;;;;;;;;AC/GA,IAAa,eAAb,cAAkC,eAAe;CAChD;CACA,OAAwC;CACxC;;;;;;;;CASA,YAAY,SAAiB,SAA+B;EAC3D,MAAM,SAAS,OAAO;EACtB,KAAK,SAAS,SAAS;EACvB,KAAK,MAAM,SAAS;CACrB;AACD"}