@bedrock-rbx/ocale 0.1.0-beta.21 → 0.1.0-beta.22

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.
@@ -1 +1 @@
1
- {"version":3,"file":"universes.mjs","names":["#inner"],"sources":["../src/domains/cloud-v2/universes/builders.ts","../src/domains/cloud-v2/universes/operations.ts","../src/domains/cloud-v2/universes/parsers.ts","../src/domains/game-internationalization/game-icon/builders.ts","../src/domains/game-internationalization/game-icon/operations.ts","../src/domains/game-internationalization/game-icon/parsers.ts","../src/domains/game-internationalization/game-thumbnails/builders.ts","../src/domains/game-internationalization/game-thumbnails/operations.ts","../src/domains/game-internationalization/game-thumbnails/parsers.ts","../src/resources/universes/client.ts"],"sourcesContent":["import type { HttpRequest } from \"../../../client/types.ts\";\nimport type { OpenCloudError } from \"../../../errors/base.ts\";\nimport { ValidationError } from \"../../../errors/validation.ts\";\nimport { okRequest } from \"../../../internal/resource-client.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type { GetUniverseParameters, UpdateUniverseParameters } from \"./types.ts\";\n\n/**\n * Dodges `unicorn/no-null` while still emitting a literal `null` onto\n * the wire, which the Open Cloud `Cloud_UpdateUniverse` endpoint\n * requires to clear a nullable field (for example disabling private\n * servers or removing a social link).\n */\nconst NULL_SENTINEL = JSON.parse(\"null\");\n\n/**\n * Builds a `GET` request for the Open Cloud \"get universe\" endpoint.\n *\n * @param parameters - The universe identifier.\n * @returns A success result wrapping the request; the builder cannot fail.\n */\nexport function buildGetRequest(\n\tparameters: GetUniverseParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest({\n\t\tmethod: \"GET\",\n\t\turl: `/cloud/v2/universes/${parameters.universeId}`,\n\t});\n}\n\n/**\n * Builds a `PATCH` request for the Open Cloud \"update universe\"\n * endpoint. Derives the `updateMask` query string from the keys\n * present on `parameters` and emits a JSON body containing those same\n * fields, translating `undefined` values to JSON `null` so Roblox\n * clears the corresponding server-side value.\n *\n * @param parameters - The universe identifier plus the fields to update.\n * @returns A success result wrapping the request, or a\n * {@link ValidationError} when no updatable fields were supplied.\n */\nexport function buildUpdateRequest(\n\tparameters: UpdateUniverseParameters,\n): Result<HttpRequest, ValidationError> {\n\tconst fieldKeys = extractUpdateFieldKeys(parameters);\n\n\tif (fieldKeys.length === 0) {\n\t\treturn {\n\t\t\terr: new ValidationError(\"Update must include at least one field\", {\n\t\t\t\tcode: \"empty_update\",\n\t\t\t}),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tconst body: Record<string, unknown> = {};\n\tfor (const key of fieldKeys) {\n\t\tbody[key] = bodyValueFor(parameters, key);\n\t}\n\n\tconst updateMask = fieldKeys.join(\",\");\n\treturn {\n\t\tdata: {\n\t\t\tbody,\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tmethod: \"PATCH\",\n\t\t\turl: `/cloud/v2/universes/${parameters.universeId}?updateMask=${updateMask}`,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction extractUpdateFieldKeys(parameters: UpdateUniverseParameters): ReadonlyArray<string> {\n\treturn Object.keys(parameters).filter((key) => key !== \"universeId\");\n}\n\nfunction bodyValueFor(parameters: UpdateUniverseParameters, key: string): unknown {\n\tconst value = Reflect.get(parameters, key);\n\treturn value === undefined ? NULL_SENTINEL : value;\n}\n","import type { OperationLimit } from \"../../../internal/http/rate-limit-queue.ts\";\n\nconst PER_MINUTE = 100;\nconst SECONDS_PER_MINUTE = 60;\n\n/**\n * Per-second request ceiling for reading a universe, from the Open\n * Cloud OpenAPI schema (100 requests per minute per API key owner).\n */\nexport const GET_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: PER_MINUTE / SECONDS_PER_MINUTE,\n\toperationKey: \"universes.get\",\n});\n\n/**\n * Per-second request ceiling for updating a universe, from the Open\n * Cloud OpenAPI schema (100 requests per minute per API key owner).\n * Keyed independently from {@link GET_OPERATION_LIMIT} so reads and\n * updates do not share a queue; upstream quota accounting is not\n * documented as shared and the conservative default is fewer\n * cross-method contention surprises.\n */\nexport const UPDATE_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: PER_MINUTE / SECONDS_PER_MINUTE,\n\toperationKey: \"universes.update\",\n});\n\n/**\n * Scopes required to update a universe, sourced from `x-roblox-scopes`\n * on the `Cloud_UpdateUniverse` operation in the vendored OpenAPI schema.\n * `Cloud_GetUniverse` declares no scope, so the GET method intentionally\n * does not declare `requiredScopes` and a 401/403 there surfaces as a\n * generic ApiError.\n */\nexport const UPDATE_REQUIRED_SCOPES: ReadonlyArray<string> = Object.freeze([\"universe:write\"]);\n","import type { HttpResponse } from \"../../../client/types.ts\";\nimport { ApiError } from \"../../../errors/api-error.ts\";\nimport { isDateTimeString } from \"../../../internal/utils/is-date-time-string.ts\";\nimport { isRecord } from \"../../../internal/utils/is-record.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type {\n\tSocialLink,\n\tUniverse,\n\tUniverseAgeRating,\n\tUniverseOwner,\n\tUniverseVisibility,\n} from \"./types.ts\";\nimport type { AgeRatingWire, SocialLinkWire, UniverseWire, VisibilityWire } from \"./wire.ts\";\n\nconst VISIBILITY_MAP: Readonly<Record<VisibilityWire, UniverseVisibility>> = {\n\tPRIVATE: \"private\",\n\tPUBLIC: \"public\",\n\tVISIBILITY_UNSPECIFIED: \"unspecified\",\n};\n\nconst AGE_RATING_MAP: Readonly<Record<AgeRatingWire, UniverseAgeRating>> = {\n\tAGE_RATING_9_PLUS: \"9Plus\",\n\tAGE_RATING_13_PLUS: \"13Plus\",\n\tAGE_RATING_17_PLUS: \"17Plus\",\n\tAGE_RATING_ALL: \"all\",\n\tAGE_RATING_UNSPECIFIED: \"unspecified\",\n};\n\nconst MALFORMED_MESSAGE = \"Malformed universe response\";\n\ninterface ToUniverseArgs {\n\treadonly id: string;\n\treadonly body: UniverseWire;\n\treadonly owner: UniverseOwner;\n}\n\n/**\n * Parses a successful Open Cloud `Universe` response body into the\n * public {@link Universe} shape.\n *\n * @param response - The full {@link HttpResponse} from the Open Cloud API.\n * @returns A success result wrapping the parsed {@link Universe}, or\n * an {@link ApiError} when the body does not match the wire schema.\n */\nexport function parseUniverseResponse(response: HttpResponse): Result<Universe, ApiError> {\n\tconst { body, status: statusCode } = response;\n\n\tif (!isUniverseWire(body)) {\n\t\treturn malformed(statusCode);\n\t}\n\n\tconst ownerResult = resolveOwner(body);\n\tif (!ownerResult.success) {\n\t\treturn malformed(statusCode);\n\t}\n\n\tconst idMatch = /^universes\\/(\\d+)$/.exec(body.path);\n\tconst id = idMatch?.[1];\n\tif (id === undefined) {\n\t\treturn malformed(statusCode);\n\t}\n\n\treturn { data: toUniverse({ id, body, owner: ownerResult.data }), success: true };\n}\n\nfunction malformed(statusCode: number): Result<Universe, ApiError> {\n\treturn {\n\t\terr: new ApiError(MALFORMED_MESSAGE, { statusCode }),\n\t\tsuccess: false,\n\t};\n}\n\nfunction extractRootPlaceId(rootPlace: string | undefined): string | undefined {\n\tif (rootPlace === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst match = /\\/places\\/(\\d+)$/.exec(rootPlace);\n\treturn match?.[1];\n}\n\nfunction toSocialLink(wire: SocialLinkWire | undefined): SocialLink | undefined {\n\tif (wire === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { title: wire.title, uri: wire.uri };\n}\n\nfunction toUniverse(args: ToUniverseArgs): Universe {\n\tconst { id, body, owner } = args;\n\treturn {\n\t\tid,\n\t\tageRating: AGE_RATING_MAP[body.ageRating],\n\t\tconsoleEnabled: body.consoleEnabled ?? false,\n\t\tcreatedAt: new Date(body.createTime),\n\t\tdescription: body.description,\n\t\tdesktopEnabled: body.desktopEnabled ?? false,\n\t\tdiscordSocialLink: toSocialLink(body.discordSocialLink),\n\t\tdisplayName: body.displayName,\n\t\tfacebookSocialLink: toSocialLink(body.facebookSocialLink),\n\t\tguildedSocialLink: toSocialLink(body.guildedSocialLink),\n\t\tmobileEnabled: body.mobileEnabled ?? false,\n\t\towner,\n\t\tprivateServerPriceRobux: body.privateServerPriceRobux ?? undefined,\n\t\trobloxGroupSocialLink: toSocialLink(body.robloxGroupSocialLink),\n\t\trootPlaceId: extractRootPlaceId(body.rootPlace),\n\t\ttabletEnabled: body.tabletEnabled ?? false,\n\t\ttwitchSocialLink: toSocialLink(body.twitchSocialLink),\n\t\ttwitterSocialLink: toSocialLink(body.twitterSocialLink),\n\t\tupdatedAt: new Date(body.updateTime),\n\t\tvisibility: VISIBILITY_MAP[body.visibility],\n\t\tvoiceChatEnabled: body.voiceChatEnabled ?? false,\n\t\tvrEnabled: body.vrEnabled ?? false,\n\t\tyoutubeSocialLink: toSocialLink(body.youtubeSocialLink),\n\t};\n}\n\nfunction isVisibilityWire(value: unknown): value is VisibilityWire {\n\treturn value === \"PRIVATE\" || value === \"PUBLIC\" || value === \"VISIBILITY_UNSPECIFIED\";\n}\n\nfunction isAgeRatingWire(value: unknown): value is AgeRatingWire {\n\treturn (\n\t\tvalue === \"AGE_RATING_13_PLUS\" ||\n\t\tvalue === \"AGE_RATING_17_PLUS\" ||\n\t\tvalue === \"AGE_RATING_9_PLUS\" ||\n\t\tvalue === \"AGE_RATING_ALL\" ||\n\t\tvalue === \"AGE_RATING_UNSPECIFIED\"\n\t);\n}\n\nfunction hasValidRequiredFields(body: Record<string, unknown>): boolean {\n\treturn (\n\t\ttypeof body[\"path\"] === \"string\" &&\n\t\tisDateTimeString(body[\"createTime\"]) &&\n\t\tisDateTimeString(body[\"updateTime\"]) &&\n\t\ttypeof body[\"displayName\"] === \"string\" &&\n\t\ttypeof body[\"description\"] === \"string\" &&\n\t\tisVisibilityWire(body[\"visibility\"]) &&\n\t\tisAgeRatingWire(body[\"ageRating\"])\n\t);\n}\n\nfunction isSocialLinkWire(value: unknown): value is SocialLinkWire {\n\tif (!isRecord(value)) {\n\t\treturn false;\n\t}\n\n\treturn typeof value[\"title\"] === \"string\" && typeof value[\"uri\"] === \"string\";\n}\n\nfunction isOptionalSocialLink(value: unknown): boolean {\n\treturn value === undefined || value === null || isSocialLinkWire(value);\n}\n\nfunction isOptionalBoolean(value: unknown): boolean {\n\treturn value === undefined || value === null || typeof value === \"boolean\";\n}\n\nfunction hasValidOptionalFields(body: Record<string, unknown>): boolean {\n\tconst priceField = body[\"privateServerPriceRobux\"] ?? undefined;\n\tif (priceField !== undefined && typeof priceField !== \"number\") {\n\t\treturn false;\n\t}\n\n\tconst rootPlace = body[\"rootPlace\"] ?? undefined;\n\tif (rootPlace !== undefined && typeof rootPlace !== \"string\") {\n\t\treturn false;\n\t}\n\n\treturn (\n\t\tisOptionalBoolean(body[\"voiceChatEnabled\"]) &&\n\t\tisOptionalBoolean(body[\"desktopEnabled\"]) &&\n\t\tisOptionalBoolean(body[\"mobileEnabled\"]) &&\n\t\tisOptionalBoolean(body[\"tabletEnabled\"]) &&\n\t\tisOptionalBoolean(body[\"consoleEnabled\"]) &&\n\t\tisOptionalBoolean(body[\"vrEnabled\"]) &&\n\t\tisOptionalSocialLink(body[\"facebookSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"twitterSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"youtubeSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"twitchSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"discordSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"robloxGroupSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"guildedSocialLink\"])\n\t);\n}\n\nfunction isUniverseWire(body: unknown): body is UniverseWire {\n\tif (!isRecord(body)) {\n\t\treturn false;\n\t}\n\n\treturn hasValidRequiredFields(body) && hasValidOptionalFields(body);\n}\n\nfunction extractOwnerId(resourcePath: string): string | undefined {\n\tconst match = /^(?:users|groups)\\/(\\d+)$/.exec(resourcePath);\n\treturn match?.[1];\n}\n\nfunction resolveOwner(body: UniverseWire): Result<UniverseOwner, undefined> {\n\tif (typeof body.user === \"string\") {\n\t\tconst id = extractOwnerId(body.user);\n\t\tif (id !== undefined) {\n\t\t\treturn { data: { id, kind: \"user\" }, success: true };\n\t\t}\n\t}\n\n\tif (typeof body.group === \"string\") {\n\t\tconst id = extractOwnerId(body.group);\n\t\tif (id !== undefined) {\n\t\t\treturn { data: { id, kind: \"group\" }, success: true };\n\t\t}\n\t}\n\n\treturn { err: undefined, success: false };\n}\n","// The legacy `{gameId}` URL segment is in fact the universe ID; the public API\n// takes `universeId` and substitutes it into the path.\n\nimport type { HttpRequest } from \"../../../internal/http/types.ts\";\nimport { toBlob } from \"../../../internal/utils/to-blob.ts\";\nimport type {\n\tDeleteExperienceIconParameters,\n\tListExperienceIconsParameters,\n\tUploadExperienceIconParameters,\n} from \"./types.ts\";\n\n/**\n * Builds a `POST` request for the localized \"upload experience icon\"\n * endpoint. A successful upload replaces any existing icon for the same\n * `(universeId, languageCode)` pair.\n *\n * @param parameters - Universe and language identifiers plus the image\n * bytes to upload.\n * @returns A pure {@link HttpRequest} describing the upload call.\n */\nexport function buildUploadIconRequest(parameters: UploadExperienceIconParameters): HttpRequest {\n\tconst body = new FormData();\n\t// The legacy game-icon endpoint reads the upload from `request.files`.\n\tbody.append(\"request.files\", toBlob(parameters.image));\n\n\treturn {\n\t\tbody,\n\t\tmethod: \"POST\",\n\t\turl: `/legacy-game-internationalization/v1/game-icon/games/${parameters.universeId}/language-codes/${parameters.languageCode}`,\n\t};\n}\n\n/**\n * Builds a `DELETE` request for the localized \"delete experience icon\"\n * endpoint. Removing the source-language icon is rejected server-side;\n * deleting the icon for a non-source language clears that translation.\n *\n * @param parameters - Universe and language identifiers of the icon to\n * delete.\n * @returns A pure {@link HttpRequest} describing the delete call.\n */\nexport function buildDeleteIconRequest(parameters: DeleteExperienceIconParameters): HttpRequest {\n\treturn {\n\t\tmethod: \"DELETE\",\n\t\turl: `/legacy-game-internationalization/v1/game-icon/games/${parameters.universeId}/language-codes/${parameters.languageCode}`,\n\t};\n}\n\n/**\n * Builds a `GET` request for the \"list experience icons\" endpoint. The\n * server returns one entry per locale that has an icon registered.\n *\n * @param parameters - Universe identifier whose icons to list.\n * @returns A pure {@link HttpRequest} describing the list call.\n */\nexport function buildListIconsRequest(parameters: ListExperienceIconsParameters): HttpRequest {\n\treturn {\n\t\tmethod: \"GET\",\n\t\turl: `/legacy-game-internationalization/v1/game-icon/games/${parameters.universeId}`,\n\t};\n}\n","import type { OperationLimit } from \"../../../internal/http/rate-limit-queue.ts\";\n\n/**\n * Per-second request ceiling for every game-icon Operation bound on\n * `UniversesClient.icon`. The legacy `gameinternationalization` service caps\n * each API key at 100 requests per minute *shared across the entire service*\n * (see the `x-roblox-rate-limits` extension on every operation in the\n * vendored Open Cloud spec), so all methods queue against the same operation\n * key.\n */\nexport const ICON_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: 100 / 60,\n\toperationKey: \"experience-icon\",\n});\n\n/**\n * Scopes required for every game-icon operation, sourced from\n * `x-roblox-scopes` on the legacy `gameinternationalization` icon\n * endpoints in the vendored OpenAPI schema.\n */\nexport const ICON_REQUIRED_SCOPES: ReadonlyArray<string> = Object.freeze([\n\t\"legacy-universe:manage\",\n]);\n","import type { HttpResponse } from \"../../../client/types.ts\";\nimport { ApiError } from \"../../../errors/api-error.ts\";\nimport { isRecord } from \"../../../internal/utils/is-record.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type { ExperienceIcon } from \"./types.ts\";\nimport type { GameIconListWire, GameIconState, GetGameIconResponseWire } from \"./wire.ts\";\n\n/**\n * Parses a successful icon-list response into a public array of\n * {@link ExperienceIcon} entries.\n *\n * @param response - The full {@link HttpResponse} from the Open Cloud API.\n * @returns A success result wrapping the converted icon list, or an\n * `ApiError` when the body does not match the wire schema.\n */\nexport function parseIconListResponse(\n\tresponse: HttpResponse,\n): Result<ReadonlyArray<ExperienceIcon>, ApiError> {\n\tconst { body, status: statusCode } = response;\n\n\tif (!isGameIconListWire(body)) {\n\t\treturn {\n\t\t\terr: new ApiError(\"Malformed icon list response\", { statusCode }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: body.data.map(toExperienceIcon),\n\t\tsuccess: true,\n\t};\n}\n\nfunction isGameIconState(value: unknown): value is GameIconState {\n\treturn (\n\t\tvalue === \"Approved\" ||\n\t\tvalue === \"Error\" ||\n\t\tvalue === \"PendingReview\" ||\n\t\tvalue === \"Rejected\" ||\n\t\tvalue === \"UnAvailable\"\n\t);\n}\n\nfunction isGetGameIconResponseWire(value: unknown): value is GetGameIconResponseWire {\n\tif (!isRecord(value)) {\n\t\treturn false;\n\t}\n\n\treturn (\n\t\ttypeof value[\"imageId\"] === \"string\" &&\n\t\ttypeof value[\"imageUrl\"] === \"string\" &&\n\t\ttypeof value[\"languageCode\"] === \"string\" &&\n\t\tisGameIconState(value[\"state\"])\n\t);\n}\n\nfunction isGameIconListWire(body: unknown): body is GameIconListWire {\n\tif (!isRecord(body)) {\n\t\treturn false;\n\t}\n\n\tconst { data } = body;\n\tif (!Array.isArray(data)) {\n\t\treturn false;\n\t}\n\n\treturn data.every(isGetGameIconResponseWire);\n}\n\nfunction toExperienceIcon(wire: GetGameIconResponseWire): ExperienceIcon {\n\treturn {\n\t\timageId: wire.imageId,\n\t\timageUrl: wire.imageUrl,\n\t\tlanguageCode: wire.languageCode,\n\t\tstate: wire.state,\n\t};\n}\n","import { ValidationError } from \"../../../errors/validation.ts\";\nimport type { HttpRequest } from \"../../../internal/http/types.ts\";\nimport { toBlob } from \"../../../internal/utils/to-blob.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type {\n\tDeleteExperienceThumbnailParameters,\n\tReorderExperienceThumbnailsParameters,\n\tUploadExperienceThumbnailParameters,\n} from \"./types.ts\";\n\ntype ParsedIdsResult = Result<ReadonlyArray<number>, ValidationError>;\n\n/**\n * Builds a `POST` request for the localized \"upload experience thumbnail\"\n * endpoint. Each successful upload appends a new entry to the carousel.\n *\n * @param parameters - Universe and language identifiers plus the image\n * bytes to upload.\n * @returns A pure {@link HttpRequest} describing the upload call.\n */\nexport function buildUploadThumbnailRequest(\n\tparameters: UploadExperienceThumbnailParameters,\n): HttpRequest {\n\tconst body = new FormData();\n\t// The legacy game-thumbnails endpoint reads the upload from\n\t// `gameThumbnailRequest.files`, distinct from game-icon's `request.files`.\n\tbody.append(\"gameThumbnailRequest.files\", toBlob(parameters.image));\n\n\treturn {\n\t\tbody,\n\t\tmethod: \"POST\",\n\t\t// The `{gameId}` URL segment in this legacy path is in fact the\n\t\t// universe ID; the package surfaces only `universeId`.\n\t\turl: `/legacy-game-internationalization/v1/game-thumbnails/games/${parameters.universeId}/language-codes/${parameters.languageCode}/image`,\n\t};\n}\n\n/**\n * Builds a `DELETE` request for the \"delete experience thumbnail\" endpoint.\n *\n * @param parameters - Universe, language, and image identifiers of the\n * thumbnail to delete.\n * @returns A pure {@link HttpRequest} describing the delete call.\n */\nexport function buildDeleteThumbnailRequest(\n\tparameters: DeleteExperienceThumbnailParameters,\n): HttpRequest {\n\treturn {\n\t\tmethod: \"DELETE\",\n\t\turl: `/legacy-game-internationalization/v1/game-thumbnails/games/${parameters.universeId}/language-codes/${parameters.languageCode}/images/${parameters.imageId}`,\n\t};\n}\n\n/**\n * Builds a `POST` request for the \"reorder experience thumbnails\" endpoint.\n * Validates each supplied image ID at the wire boundary so a typo cannot\n * silently serialize as JSON `null` and corrupt the request.\n *\n * @param parameters - Universe, language, and the desired display order.\n * @returns A success result wrapping the request, or a\n * {@link ValidationError} when `orderedImageIds` is empty or any ID is not\n * a positive integer within the safe-integer range.\n */\nexport function buildReorderThumbnailsRequest(\n\tparameters: ReorderExperienceThumbnailsParameters,\n): Result<HttpRequest, ValidationError> {\n\tconst { languageCode, orderedImageIds, universeId } = parameters;\n\n\tconst idsResult = parseOrderedImageIds(orderedImageIds);\n\tif (!idsResult.success) {\n\t\treturn idsResult;\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tbody: { mediaAssetIds: idsResult.data },\n\t\t\tmethod: \"POST\",\n\t\t\turl: `/legacy-game-internationalization/v1/game-thumbnails/games/${universeId}/language-codes/${languageCode}/images/order`,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction parseImageId(value: string): number | undefined {\n\tif (!/^[1-9]\\d*$/.test(value)) {\n\t\treturn undefined;\n\t}\n\n\tconst parsed = Number(value);\n\tif (!Number.isSafeInteger(parsed)) {\n\t\treturn undefined;\n\t}\n\n\treturn parsed;\n}\n\nfunction appendParsedId(accumulator: ParsedIdsResult, id: string): ParsedIdsResult {\n\tif (!accumulator.success) {\n\t\treturn accumulator;\n\t}\n\n\tconst parsed = parseImageId(id);\n\tif (parsed === undefined) {\n\t\treturn {\n\t\t\terr: new ValidationError(\n\t\t\t\t`orderedImageIds entry ${JSON.stringify(id)} is not a positive integer ID`,\n\t\t\t\t{ code: \"invalid_image_id\" },\n\t\t\t),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: [...accumulator.data, parsed], success: true };\n}\n\nfunction parseOrderedImageIds(orderedImageIds: ReadonlyArray<string>): ParsedIdsResult {\n\tif (orderedImageIds.length === 0) {\n\t\treturn {\n\t\t\terr: new ValidationError(\"orderedImageIds must contain at least one image ID\", {\n\t\t\t\tcode: \"empty_image_ids\",\n\t\t\t}),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn orderedImageIds.reduce<ParsedIdsResult>(appendParsedId, { data: [], success: true });\n}\n","import type { OperationLimit } from \"../../../internal/http/rate-limit-queue.ts\";\n\n/**\n * Per-second request ceiling for every game-thumbnails Operation bound on\n * `UniversesClient.thumbnails`. The legacy `gameinternationalization`\n * service caps each API key at 100 requests per minute *shared across the\n * entire service* (see the `x-roblox-rate-limits` extension on every\n * operation in the vendored Open Cloud spec), so all methods queue against\n * the same operation key.\n */\nexport const THUMBNAILS_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: 100 / 60,\n\toperationKey: \"experience-thumbnails\",\n});\n\n/**\n * Scopes required for every game-thumbnails operation, sourced from\n * `x-roblox-scopes` on the legacy `gameinternationalization` thumbnail\n * endpoints in the vendored OpenAPI schema.\n */\nexport const THUMBNAILS_REQUIRED_SCOPES: ReadonlyArray<string> = Object.freeze([\n\t\"legacy-universe:manage\",\n]);\n","import type { HttpResponse } from \"../../../client/types.ts\";\nimport { ApiError } from \"../../../errors/api-error.ts\";\nimport { isRecord } from \"../../../internal/utils/is-record.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type { UploadedExperienceThumbnail } from \"./types.ts\";\nimport type { GameThumbnailUploadWire } from \"./wire.ts\";\n\n/**\n * Parses a successful thumbnail-upload response into the public\n * {@link UploadedExperienceThumbnail} shape, returning a {@link Result}\n * so callers can handle malformed payloads without exceptions.\n *\n * @param response - The full {@link HttpResponse} from the Open Cloud API.\n * @returns A success result wrapping the converted upload, or an\n * `ApiError` when the body does not match the wire schema.\n */\nexport function parseThumbnailUploadResponse(\n\tresponse: HttpResponse,\n): Result<UploadedExperienceThumbnail, ApiError> {\n\tconst { body, status: statusCode } = response;\n\n\tif (!isGameThumbnailUploadWire(body)) {\n\t\treturn {\n\t\t\terr: new ApiError(\"Malformed thumbnail upload response\", { statusCode }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: { mediaAssetId: body.mediaAssetId },\n\t\tsuccess: true,\n\t};\n}\n\nfunction isGameThumbnailUploadWire(body: unknown): body is GameThumbnailUploadWire {\n\tif (!isRecord(body)) {\n\t\treturn false;\n\t}\n\n\treturn typeof body[\"mediaAssetId\"] === \"string\";\n}\n","import type { OpenCloudClientOptions, RequestOptions } from \"../../client/types.ts\";\nimport { buildGetRequest, buildUpdateRequest } from \"../../domains/cloud-v2/universes/builders.ts\";\nimport {\n\tGET_OPERATION_LIMIT,\n\tUPDATE_OPERATION_LIMIT,\n\tUPDATE_REQUIRED_SCOPES,\n} from \"../../domains/cloud-v2/universes/operations.ts\";\nimport { parseUniverseResponse } from \"../../domains/cloud-v2/universes/parsers.ts\";\nimport type {\n\tGetUniverseParameters,\n\tUniverse,\n\tUpdateUniverseParameters,\n} from \"../../domains/cloud-v2/universes/types.ts\";\nimport {\n\tbuildDeleteIconRequest,\n\tbuildListIconsRequest,\n\tbuildUploadIconRequest,\n} from \"../../domains/game-internationalization/game-icon/builders.ts\";\nimport {\n\tICON_OPERATION_LIMIT,\n\tICON_REQUIRED_SCOPES,\n} from \"../../domains/game-internationalization/game-icon/operations.ts\";\nimport { parseIconListResponse } from \"../../domains/game-internationalization/game-icon/parsers.ts\";\nimport type {\n\tDeleteExperienceIconParameters,\n\tExperienceIcon,\n\tListExperienceIconsParameters,\n\tUploadExperienceIconParameters,\n} from \"../../domains/game-internationalization/game-icon/types.ts\";\nimport {\n\tbuildDeleteThumbnailRequest,\n\tbuildReorderThumbnailsRequest,\n\tbuildUploadThumbnailRequest,\n} from \"../../domains/game-internationalization/game-thumbnails/builders.ts\";\nimport {\n\tTHUMBNAILS_OPERATION_LIMIT,\n\tTHUMBNAILS_REQUIRED_SCOPES,\n} from \"../../domains/game-internationalization/game-thumbnails/operations.ts\";\nimport { parseThumbnailUploadResponse } from \"../../domains/game-internationalization/game-thumbnails/parsers.ts\";\nimport type {\n\tDeleteExperienceThumbnailParameters,\n\tReorderExperienceThumbnailsParameters,\n\tUploadedExperienceThumbnail,\n\tUploadExperienceThumbnailParameters,\n} from \"../../domains/game-internationalization/game-thumbnails/types.ts\";\nimport type { OpenCloudError } from \"../../errors/base.ts\";\nimport { CREATE_METHOD_DEFAULTS, IDEMPOTENT_METHOD_DEFAULTS } from \"../../internal/http/retry.ts\";\nimport type { HttpRequest } from \"../../internal/http/types.ts\";\nimport {\n\tokRequest,\n\tparseEmptyResponse,\n\tResourceClient,\n\ttype ResourceMethodSpec,\n} from \"../../internal/resource-client.ts\";\nimport type { Result } from \"../../types.ts\";\n\nconst GET_SPEC: ResourceMethodSpec<GetUniverseParameters, Universe> = Object.freeze({\n\tbuildRequest: buildGetRequest,\n\tmethodDefaults: {},\n\tmethodKind: \"idempotent\",\n\toperationLimit: GET_OPERATION_LIMIT,\n\tparse: parseUniverseResponse,\n});\n\nconst UPDATE_SPEC: ResourceMethodSpec<UpdateUniverseParameters, Universe> = Object.freeze({\n\tbuildRequest: buildUpdateRequest,\n\tmethodDefaults: {},\n\tmethodKind: \"idempotent\",\n\toperationLimit: UPDATE_OPERATION_LIMIT,\n\tparse: parseUniverseResponse,\n\trequiredScopes: UPDATE_REQUIRED_SCOPES,\n});\n\nfunction buildIconUploadOkRequest(\n\tparameters: UploadExperienceIconParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest(buildUploadIconRequest(parameters));\n}\n\nfunction buildIconDeleteOkRequest(\n\tparameters: DeleteExperienceIconParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest(buildDeleteIconRequest(parameters));\n}\n\nfunction buildIconListOkRequest(\n\tparameters: ListExperienceIconsParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest(buildListIconsRequest(parameters));\n}\n\nconst ICON_UPLOAD_SPEC: ResourceMethodSpec<UploadExperienceIconParameters, undefined> =\n\tObject.freeze({\n\t\tbuildRequest: buildIconUploadOkRequest,\n\t\tmethodDefaults: CREATE_METHOD_DEFAULTS,\n\t\tmethodKind: \"create\",\n\t\toperationLimit: ICON_OPERATION_LIMIT,\n\t\tparse: parseEmptyResponse,\n\t\trequiredScopes: ICON_REQUIRED_SCOPES,\n\t});\n\nconst ICON_DELETE_SPEC: ResourceMethodSpec<DeleteExperienceIconParameters, undefined> =\n\tObject.freeze({\n\t\tbuildRequest: buildIconDeleteOkRequest,\n\t\tmethodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n\t\tmethodKind: \"idempotent\",\n\t\toperationLimit: ICON_OPERATION_LIMIT,\n\t\tparse: parseEmptyResponse,\n\t\trequiredScopes: ICON_REQUIRED_SCOPES,\n\t});\n\nconst ICON_LIST_SPEC: ResourceMethodSpec<\n\tListExperienceIconsParameters,\n\tReadonlyArray<ExperienceIcon>\n> = Object.freeze({\n\tbuildRequest: buildIconListOkRequest,\n\tmethodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n\tmethodKind: \"idempotent\",\n\toperationLimit: ICON_OPERATION_LIMIT,\n\tparse: parseIconListResponse,\n\trequiredScopes: ICON_REQUIRED_SCOPES,\n});\n\nfunction buildThumbnailUploadOkRequest(\n\tparameters: UploadExperienceThumbnailParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest(buildUploadThumbnailRequest(parameters));\n}\n\nfunction buildThumbnailDeleteOkRequest(\n\tparameters: DeleteExperienceThumbnailParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest(buildDeleteThumbnailRequest(parameters));\n}\n\nconst THUMBNAIL_UPLOAD_SPEC: ResourceMethodSpec<\n\tUploadExperienceThumbnailParameters,\n\tUploadedExperienceThumbnail\n> = Object.freeze({\n\tbuildRequest: buildThumbnailUploadOkRequest,\n\tmethodDefaults: CREATE_METHOD_DEFAULTS,\n\tmethodKind: \"create\",\n\toperationLimit: THUMBNAILS_OPERATION_LIMIT,\n\tparse: parseThumbnailUploadResponse,\n\trequiredScopes: THUMBNAILS_REQUIRED_SCOPES,\n});\n\nconst THUMBNAIL_DELETE_SPEC: ResourceMethodSpec<DeleteExperienceThumbnailParameters, undefined> =\n\tObject.freeze({\n\t\tbuildRequest: buildThumbnailDeleteOkRequest,\n\t\tmethodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n\t\tmethodKind: \"idempotent\",\n\t\toperationLimit: THUMBNAILS_OPERATION_LIMIT,\n\t\tparse: parseEmptyResponse,\n\t\trequiredScopes: THUMBNAILS_REQUIRED_SCOPES,\n\t});\n\nconst THUMBNAIL_REORDER_SPEC: ResourceMethodSpec<ReorderExperienceThumbnailsParameters, undefined> =\n\tObject.freeze({\n\t\tbuildRequest: buildReorderThumbnailsRequest,\n\t\tmethodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n\t\tmethodKind: \"idempotent\",\n\t\toperationLimit: THUMBNAILS_OPERATION_LIMIT,\n\t\tparse: parseEmptyResponse,\n\t\trequiredScopes: THUMBNAILS_REQUIRED_SCOPES,\n\t});\n\ninterface UniverseIconHandle {\n\t/**\n\t * Deletes the localized icon registered against a universe for a given\n\t * language. Removing the source-language icon is rejected server-side;\n\t * consumers must replace it via {@link UniverseIconHandle.upload}\n\t * instead.\n\t *\n\t * @param parameters - Universe and language identifiers of the icon to\n\t * delete.\n\t * @param options - Optional per-request overrides.\n\t * @returns A success {@link Result} with no payload, or the\n\t * {@link OpenCloudError} that caused the request to fail.\n\t */\n\tdelete: (\n\t\tparameters: DeleteExperienceIconParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<undefined, OpenCloudError>>;\n\t/**\n\t * Lists every localized icon registered against an experience. The\n\t * server returns one entry per locale that has an icon registered.\n\t *\n\t * @param parameters - Universe identifier whose icons to list.\n\t * @param options - Optional per-request overrides.\n\t * @returns A {@link Result} wrapping the parsed array of\n\t * {@link ExperienceIcon} entries or the {@link OpenCloudError} that\n\t * caused the request to fail.\n\t */\n\tlist: (\n\t\tparameters: ListExperienceIconsParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<ReadonlyArray<ExperienceIcon>, OpenCloudError>>;\n\t/**\n\t * Uploads or replaces the localized icon for an experience. A\n\t * subsequent upload for the same `(universeId, languageCode)` pair\n\t * replaces the existing icon for that locale.\n\t *\n\t * No default request timeout applies to this upload; pass `options.timeout`\n\t * to set a per-call deadline.\n\t *\n\t * @param parameters - Universe and language identifiers plus the image\n\t * bytes to upload.\n\t * @param options - Optional per-request overrides (e.g. A different\n\t * {@link OpenCloudClientOptions.apiKey} for this call only).\n\t * @returns A success {@link Result} with no payload, or the\n\t * {@link OpenCloudError} that caused the request to fail.\n\t */\n\tupload: (\n\t\tparameters: UploadExperienceIconParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<undefined, OpenCloudError>>;\n}\n\ninterface UniverseThumbnailsHandle {\n\t/**\n\t * Deletes a single thumbnail by media asset ID. Idempotent: deleting an\n\t * already-removed thumbnail surfaces the server's 404 unchanged.\n\t *\n\t * @param parameters - Universe, language, and image identifiers of the\n\t * thumbnail to delete.\n\t * @param options - Optional per-request overrides.\n\t * @returns A success {@link Result} with no payload, or the\n\t * {@link OpenCloudError} that caused the request to fail.\n\t */\n\tdelete: (\n\t\tparameters: DeleteExperienceThumbnailParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<undefined, OpenCloudError>>;\n\t/**\n\t * Reorders the localized thumbnail carousel. The supplied\n\t * `orderedImageIds` describes the desired display order from first to\n\t * last. Image IDs must be positive integers within the safe-integer\n\t * range; invalid input is rejected with a {@link OpenCloudError} of\n\t * kind `ValidationError` before any HTTP round-trip.\n\t *\n\t * @param parameters - Universe, language, and the desired display order.\n\t * @param options - Optional per-request overrides.\n\t * @returns A success {@link Result} with no payload, or the\n\t * {@link OpenCloudError} that caused the request to fail.\n\t */\n\treorder: (\n\t\tparameters: ReorderExperienceThumbnailsParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<undefined, OpenCloudError>>;\n\t/**\n\t * Uploads a new thumbnail and appends it to the localized carousel. Use\n\t * {@link UniverseThumbnailsHandle.reorder} after multiple uploads to\n\t * set the display order.\n\t *\n\t * No default request timeout applies to this upload; pass `options.timeout`\n\t * to set a per-call deadline.\n\t *\n\t * @param parameters - Universe and language identifiers plus the image\n\t * bytes to upload.\n\t * @param options - Optional per-request overrides (e.g. A different\n\t * {@link OpenCloudClientOptions.apiKey} for this call only).\n\t * @returns A {@link Result} wrapping the parsed\n\t * {@link UploadedExperienceThumbnail} or the {@link OpenCloudError}\n\t * that caused the request to fail.\n\t */\n\tupload: (\n\t\tparameters: UploadExperienceThumbnailParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<UploadedExperienceThumbnail, OpenCloudError>>;\n}\n\n/**\n * Public client for the Roblox Open Cloud `Universe` resource. Wires\n * the request builders, the injected\n * {@link OpenCloudClientOptions.httpClient}, and the response parser\n * into a single ergonomic surface. Every method returns a\n * {@link Result} so callers handle failure explicitly; no thrown\n * {@link OpenCloudError} ever escapes the client.\n *\n * Partial updates use a Google-style `updateMask` query string derived\n * from the keys present on the update parameters. Setting a clearable\n * field (`privateServerPriceRobux` or any social link) to `undefined`\n * sends JSON `null` for that field so the server clears the\n * corresponding value.\n *\n * Localized experience-icon and experience-thumbnail Operations are\n * bound on the {@link UniversesClient.icon} and\n * {@link UniversesClient.thumbnails} Operation Groups so callers reach\n * for one client per universe.\n *\n * @since 0.1.0\n *\n * @example\n *\n * ```ts\n * import { UniversesClient } from \"@bedrock-rbx/ocale/universes\";\n *\n * const client = new UniversesClient({ apiKey: \"your-key\" });\n * expect(client).toBeInstanceOf(UniversesClient);\n * ```\n */\nexport class UniversesClient {\n\treadonly #inner: ResourceClient;\n\n\t/**\n\t * Operation Group exposing the localized experience-icon\n\t * Operations (`upload`, `delete`, `list`) backed by the\n\t * `legacy-game-internationalization` domain. Shares the parent\n\t * client's HTTP, rate-limit, and retry plumbing.\n\t */\n\tpublic readonly icon: UniverseIconHandle;\n\t/**\n\t * Operation Group exposing the localized experience-thumbnail\n\t * Operations (`upload`, `delete`, `reorder`) backed by the\n\t * `legacy-game-internationalization` domain. No list-thumbnails\n\t * endpoint is bridged; consumers must track uploaded\n\t * `mediaAssetId`s in their own state store to reconcile against\n\t * the existing carousel. Shares the parent client's HTTP,\n\t * rate-limit, and retry plumbing.\n\t */\n\tpublic readonly thumbnails: UniverseThumbnailsHandle;\n\n\t/**\n\t * Creates a new {@link UniversesClient}. Configuration is frozen\n\t * on construction; per-request overrides are accepted on each\n\t * method.\n\t *\n\t * @param options - Client-level configuration including the API key.\n\t */\n\tconstructor(options: OpenCloudClientOptions) {\n\t\tthis.#inner = new ResourceClient(options);\n\t\tthis.icon = createIconHandle(this.#inner);\n\t\tthis.thumbnails = createThumbnailsHandle(this.#inner);\n\t}\n\n\t/**\n\t * Fetches the current configuration of a universe.\n\t *\n\t * @param parameters - The universe identifier.\n\t * @param options - Optional per-request overrides (e.g. A different\n\t * {@link OpenCloudClientOptions.apiKey} for this call only).\n\t * @returns A {@link Result} wrapping the parsed {@link Universe}\n\t * or the {@link OpenCloudError} that caused the request to fail.\n\t */\n\tpublic async get(\n\t\tparameters: GetUniverseParameters,\n\t\toptions?: RequestOptions,\n\t): Promise<Result<Universe, OpenCloudError>> {\n\t\treturn this.#inner.execute({ options, parameters, spec: GET_SPEC });\n\t}\n\n\t/**\n\t * Partially updates a universe's configuration. The fields\n\t * supplied on `parameters` (excluding `universeId`) are forwarded\n\t * to the server via a Google-style `updateMask`; unmentioned\n\t * fields are left untouched.\n\t *\n\t * @param parameters - The universe identifier and the fields to\n\t * update. At least one updatable field must be supplied.\n\t * @param options - Optional per-request overrides (e.g. A different\n\t * {@link OpenCloudClientOptions.apiKey} for this call only).\n\t * @returns A {@link Result} wrapping the parsed {@link Universe}\n\t * or the {@link OpenCloudError} that caused the request to fail.\n\t */\n\tpublic async update(\n\t\tparameters: UpdateUniverseParameters,\n\t\toptions?: RequestOptions,\n\t): Promise<Result<Universe, OpenCloudError>> {\n\t\treturn this.#inner.execute({ options, parameters, spec: UPDATE_SPEC });\n\t}\n}\n\nfunction createIconHandle(inner: ResourceClient): UniverseIconHandle {\n\treturn {\n\t\tasync delete(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: ICON_DELETE_SPEC });\n\t\t},\n\t\tasync list(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: ICON_LIST_SPEC });\n\t\t},\n\t\tasync upload(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: ICON_UPLOAD_SPEC });\n\t\t},\n\t};\n}\n\nfunction createThumbnailsHandle(inner: ResourceClient): UniverseThumbnailsHandle {\n\treturn {\n\t\tasync delete(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: THUMBNAIL_DELETE_SPEC });\n\t\t},\n\t\tasync reorder(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: THUMBNAIL_REORDER_SPEC });\n\t\t},\n\t\tasync upload(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: THUMBNAIL_UPLOAD_SPEC });\n\t\t},\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"universes.mjs","names":["#inner"],"sources":["../src/domains/cloud-v2/universes/builders.ts","../src/domains/cloud-v2/universes/operations.ts","../src/domains/cloud-v2/universes/parsers.ts","../src/domains/game-internationalization/game-icon/builders.ts","../src/domains/game-internationalization/game-icon/operations.ts","../src/domains/game-internationalization/game-icon/parsers.ts","../src/domains/game-internationalization/game-thumbnails/builders.ts","../src/domains/game-internationalization/game-thumbnails/operations.ts","../src/domains/game-internationalization/game-thumbnails/parsers.ts","../src/resources/universes/client.ts"],"sourcesContent":["import type { HttpRequest } from \"../../../client/types.ts\";\nimport type { OpenCloudError } from \"../../../errors/base.ts\";\nimport { ValidationError } from \"../../../errors/validation.ts\";\nimport { okRequest } from \"../../../internal/resource-client.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type { GetUniverseParameters, UpdateUniverseParameters } from \"./types.ts\";\n\n/**\n * Dodges `unicorn/no-null` while still emitting a literal `null` onto\n * the wire, which the Open Cloud `Cloud_UpdateUniverse` endpoint\n * requires to clear a nullable field (for example disabling private\n * servers or removing a social link).\n */\nconst NULL_SENTINEL = JSON.parse(\"null\");\n\n/**\n * Builds a `GET` request for the Open Cloud \"get universe\" endpoint.\n *\n * @param parameters - The universe identifier.\n * @returns A success result wrapping the request; the builder cannot fail.\n */\nexport function buildGetRequest(\n\tparameters: GetUniverseParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest({\n\t\tmethod: \"GET\",\n\t\turl: `/cloud/v2/universes/${parameters.universeId}`,\n\t});\n}\n\n/**\n * Builds a `PATCH` request for the Open Cloud \"update universe\"\n * endpoint. Derives the `updateMask` query string from the keys\n * present on `parameters` and emits a JSON body containing those same\n * fields, translating `undefined` values to JSON `null` so Roblox\n * clears the corresponding server-side value.\n *\n * @param parameters - The universe identifier plus the fields to update.\n * @returns A success result wrapping the request, or a\n * {@link ValidationError} when no updatable fields were supplied.\n */\nexport function buildUpdateRequest(\n\tparameters: UpdateUniverseParameters,\n): Result<HttpRequest, ValidationError> {\n\tconst fieldKeys = extractUpdateFieldKeys(parameters);\n\n\tif (fieldKeys.length === 0) {\n\t\treturn {\n\t\t\terr: new ValidationError(\"Update must include at least one field\", {\n\t\t\t\tcode: \"empty_update\",\n\t\t\t}),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tconst body: Record<string, unknown> = {};\n\tfor (const key of fieldKeys) {\n\t\tbody[key] = bodyValueFor(parameters, key);\n\t}\n\n\tconst updateMask = fieldKeys.join(\",\");\n\treturn {\n\t\tdata: {\n\t\t\tbody,\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tmethod: \"PATCH\",\n\t\t\turl: `/cloud/v2/universes/${parameters.universeId}?updateMask=${updateMask}`,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction extractUpdateFieldKeys(parameters: UpdateUniverseParameters): ReadonlyArray<string> {\n\treturn Object.keys(parameters).filter((key) => key !== \"universeId\");\n}\n\nfunction bodyValueFor(parameters: UpdateUniverseParameters, key: string): unknown {\n\tconst value = Reflect.get(parameters, key);\n\treturn value === undefined ? NULL_SENTINEL : value;\n}\n","import type { OperationLimit } from \"../../../internal/http/rate-limit-queue.ts\";\n\nconst PER_MINUTE = 100;\nconst SECONDS_PER_MINUTE = 60;\n\n/**\n * Per-second request ceiling for reading a universe, from the Open\n * Cloud OpenAPI schema (100 requests per minute per API key owner).\n */\nexport const GET_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: PER_MINUTE / SECONDS_PER_MINUTE,\n\toperationKey: \"universes.get\",\n});\n\n/**\n * Per-second request ceiling for updating a universe, from the Open\n * Cloud OpenAPI schema (100 requests per minute per API key owner).\n * Keyed independently from {@link GET_OPERATION_LIMIT} so reads and\n * updates do not share a queue; upstream quota accounting is not\n * documented as shared and the conservative default is fewer\n * cross-method contention surprises.\n */\nexport const UPDATE_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: PER_MINUTE / SECONDS_PER_MINUTE,\n\toperationKey: \"universes.update\",\n});\n\n/**\n * Scopes required to update a universe, sourced from `x-roblox-scopes`\n * on the `Cloud_UpdateUniverse` operation in the vendored OpenAPI schema.\n * `Cloud_GetUniverse` declares no scope, so the GET method intentionally\n * does not declare `requiredScopes` and a 401/403 there surfaces as a\n * generic ApiError.\n */\nexport const UPDATE_REQUIRED_SCOPES: ReadonlyArray<string> = Object.freeze([\"universe:write\"]);\n","import type { HttpResponse } from \"../../../client/types.ts\";\nimport { ApiError } from \"../../../errors/api-error.ts\";\nimport { isDateTimeString } from \"../../../internal/utils/is-date-time-string.ts\";\nimport { isRecord } from \"../../../internal/utils/is-record.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type {\n\tSocialLink,\n\tUniverse,\n\tUniverseAgeRating,\n\tUniverseOwner,\n\tUniverseVisibility,\n} from \"./types.ts\";\nimport type { AgeRatingWire, SocialLinkWire, UniverseWire, VisibilityWire } from \"./wire.ts\";\n\nconst VISIBILITY_MAP: Readonly<Record<VisibilityWire, UniverseVisibility>> = {\n\tPRIVATE: \"private\",\n\tPUBLIC: \"public\",\n\tVISIBILITY_UNSPECIFIED: \"unspecified\",\n};\n\nconst AGE_RATING_MAP: Readonly<Record<AgeRatingWire, UniverseAgeRating>> = {\n\tAGE_RATING_9_PLUS: \"9Plus\",\n\tAGE_RATING_13_PLUS: \"13Plus\",\n\tAGE_RATING_17_PLUS: \"17Plus\",\n\tAGE_RATING_ALL: \"all\",\n\tAGE_RATING_UNSPECIFIED: \"unspecified\",\n};\n\nconst MALFORMED_MESSAGE = \"Malformed universe response\";\n\nconst UNIVERSE_PATH_PATTERN = /^universes\\/(\\d+)$/;\n\nconst ROOT_PLACE_PATH_PATTERN = /\\/places\\/(\\d+)$/;\n\nconst OWNER_PATH_PATTERN = /^(?:users|groups)\\/(\\d+)$/;\n\ninterface ToUniverseArgs {\n\treadonly id: string;\n\treadonly body: UniverseWire;\n\treadonly owner: UniverseOwner;\n}\n\n/**\n * Parses a successful Open Cloud `Universe` response body into the\n * public {@link Universe} shape.\n *\n * @param response - The full {@link HttpResponse} from the Open Cloud API.\n * @returns A success result wrapping the parsed {@link Universe}, or\n * an {@link ApiError} when the body does not match the wire schema.\n */\nexport function parseUniverseResponse(response: HttpResponse): Result<Universe, ApiError> {\n\tconst { body, status: statusCode } = response;\n\n\tif (!isUniverseWire(body)) {\n\t\treturn malformed(statusCode);\n\t}\n\n\tconst ownerResult = resolveOwner(body);\n\tif (!ownerResult.success) {\n\t\treturn malformed(statusCode);\n\t}\n\n\tconst idMatch = UNIVERSE_PATH_PATTERN.exec(body.path);\n\tconst id = idMatch?.[1];\n\tif (id === undefined) {\n\t\treturn malformed(statusCode);\n\t}\n\n\treturn { data: toUniverse({ id, body, owner: ownerResult.data }), success: true };\n}\n\nfunction malformed(statusCode: number): Result<Universe, ApiError> {\n\treturn {\n\t\terr: new ApiError(MALFORMED_MESSAGE, { statusCode }),\n\t\tsuccess: false,\n\t};\n}\n\nfunction extractRootPlaceId(rootPlace: string | undefined): string | undefined {\n\tif (rootPlace === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst match = ROOT_PLACE_PATH_PATTERN.exec(rootPlace);\n\treturn match?.[1];\n}\n\nfunction toSocialLink(wire: SocialLinkWire | undefined): SocialLink | undefined {\n\tif (wire === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { title: wire.title, uri: wire.uri };\n}\n\nfunction toUniverse(args: ToUniverseArgs): Universe {\n\tconst { id, body, owner } = args;\n\treturn {\n\t\tid,\n\t\tageRating: AGE_RATING_MAP[body.ageRating],\n\t\tconsoleEnabled: body.consoleEnabled ?? false,\n\t\tcreatedAt: new Date(body.createTime),\n\t\tdescription: body.description,\n\t\tdesktopEnabled: body.desktopEnabled ?? false,\n\t\tdiscordSocialLink: toSocialLink(body.discordSocialLink),\n\t\tdisplayName: body.displayName,\n\t\tfacebookSocialLink: toSocialLink(body.facebookSocialLink),\n\t\tguildedSocialLink: toSocialLink(body.guildedSocialLink),\n\t\tmobileEnabled: body.mobileEnabled ?? false,\n\t\towner,\n\t\tprivateServerPriceRobux: body.privateServerPriceRobux ?? undefined,\n\t\trobloxGroupSocialLink: toSocialLink(body.robloxGroupSocialLink),\n\t\trootPlaceId: extractRootPlaceId(body.rootPlace),\n\t\ttabletEnabled: body.tabletEnabled ?? false,\n\t\ttwitchSocialLink: toSocialLink(body.twitchSocialLink),\n\t\ttwitterSocialLink: toSocialLink(body.twitterSocialLink),\n\t\tupdatedAt: new Date(body.updateTime),\n\t\tvisibility: VISIBILITY_MAP[body.visibility],\n\t\tvoiceChatEnabled: body.voiceChatEnabled ?? false,\n\t\tvrEnabled: body.vrEnabled ?? false,\n\t\tyoutubeSocialLink: toSocialLink(body.youtubeSocialLink),\n\t};\n}\n\nfunction isVisibilityWire(value: unknown): value is VisibilityWire {\n\treturn value === \"PRIVATE\" || value === \"PUBLIC\" || value === \"VISIBILITY_UNSPECIFIED\";\n}\n\nfunction isAgeRatingWire(value: unknown): value is AgeRatingWire {\n\treturn (\n\t\tvalue === \"AGE_RATING_13_PLUS\" ||\n\t\tvalue === \"AGE_RATING_17_PLUS\" ||\n\t\tvalue === \"AGE_RATING_9_PLUS\" ||\n\t\tvalue === \"AGE_RATING_ALL\" ||\n\t\tvalue === \"AGE_RATING_UNSPECIFIED\"\n\t);\n}\n\nfunction hasValidRequiredFields(body: Record<string, unknown>): boolean {\n\treturn (\n\t\ttypeof body[\"path\"] === \"string\" &&\n\t\tisDateTimeString(body[\"createTime\"]) &&\n\t\tisDateTimeString(body[\"updateTime\"]) &&\n\t\ttypeof body[\"displayName\"] === \"string\" &&\n\t\ttypeof body[\"description\"] === \"string\" &&\n\t\tisVisibilityWire(body[\"visibility\"]) &&\n\t\tisAgeRatingWire(body[\"ageRating\"])\n\t);\n}\n\nfunction isSocialLinkWire(value: unknown): value is SocialLinkWire {\n\tif (!isRecord(value)) {\n\t\treturn false;\n\t}\n\n\treturn typeof value[\"title\"] === \"string\" && typeof value[\"uri\"] === \"string\";\n}\n\nfunction isOptionalSocialLink(value: unknown): boolean {\n\treturn value === undefined || value === null || isSocialLinkWire(value);\n}\n\nfunction isOptionalBoolean(value: unknown): boolean {\n\treturn value === undefined || value === null || typeof value === \"boolean\";\n}\n\nfunction hasValidOptionalFields(body: Record<string, unknown>): boolean {\n\tconst priceField = body[\"privateServerPriceRobux\"] ?? undefined;\n\tif (priceField !== undefined && typeof priceField !== \"number\") {\n\t\treturn false;\n\t}\n\n\tconst rootPlace = body[\"rootPlace\"] ?? undefined;\n\tif (rootPlace !== undefined && typeof rootPlace !== \"string\") {\n\t\treturn false;\n\t}\n\n\treturn (\n\t\tisOptionalBoolean(body[\"voiceChatEnabled\"]) &&\n\t\tisOptionalBoolean(body[\"desktopEnabled\"]) &&\n\t\tisOptionalBoolean(body[\"mobileEnabled\"]) &&\n\t\tisOptionalBoolean(body[\"tabletEnabled\"]) &&\n\t\tisOptionalBoolean(body[\"consoleEnabled\"]) &&\n\t\tisOptionalBoolean(body[\"vrEnabled\"]) &&\n\t\tisOptionalSocialLink(body[\"facebookSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"twitterSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"youtubeSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"twitchSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"discordSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"robloxGroupSocialLink\"]) &&\n\t\tisOptionalSocialLink(body[\"guildedSocialLink\"])\n\t);\n}\n\nfunction isUniverseWire(body: unknown): body is UniverseWire {\n\tif (!isRecord(body)) {\n\t\treturn false;\n\t}\n\n\treturn hasValidRequiredFields(body) && hasValidOptionalFields(body);\n}\n\nfunction extractOwnerId(resourcePath: string): string | undefined {\n\tconst match = OWNER_PATH_PATTERN.exec(resourcePath);\n\treturn match?.[1];\n}\n\nfunction resolveOwner(body: UniverseWire): Result<UniverseOwner, undefined> {\n\tif (typeof body.user === \"string\") {\n\t\tconst id = extractOwnerId(body.user);\n\t\tif (id !== undefined) {\n\t\t\treturn { data: { id, kind: \"user\" }, success: true };\n\t\t}\n\t}\n\n\tif (typeof body.group === \"string\") {\n\t\tconst id = extractOwnerId(body.group);\n\t\tif (id !== undefined) {\n\t\t\treturn { data: { id, kind: \"group\" }, success: true };\n\t\t}\n\t}\n\n\treturn { err: undefined, success: false };\n}\n","// The legacy `{gameId}` URL segment is in fact the universe ID; the public API\n// takes `universeId` and substitutes it into the path.\n\nimport type { HttpRequest } from \"../../../internal/http/types.ts\";\nimport { toBlob } from \"../../../internal/utils/to-blob.ts\";\nimport type {\n\tDeleteExperienceIconParameters,\n\tListExperienceIconsParameters,\n\tUploadExperienceIconParameters,\n} from \"./types.ts\";\n\n/**\n * Builds a `POST` request for the localized \"upload experience icon\"\n * endpoint. A successful upload replaces any existing icon for the same\n * `(universeId, languageCode)` pair.\n *\n * @param parameters - Universe and language identifiers plus the image\n * bytes to upload.\n * @returns A pure {@link HttpRequest} describing the upload call.\n */\nexport function buildUploadIconRequest(parameters: UploadExperienceIconParameters): HttpRequest {\n\tconst body = new FormData();\n\t// The legacy game-icon endpoint reads the upload from `request.files`.\n\tbody.append(\"request.files\", toBlob(parameters.image));\n\n\treturn {\n\t\tbody,\n\t\tmethod: \"POST\",\n\t\turl: `/legacy-game-internationalization/v1/game-icon/games/${parameters.universeId}/language-codes/${parameters.languageCode}`,\n\t};\n}\n\n/**\n * Builds a `DELETE` request for the localized \"delete experience icon\"\n * endpoint. Removing the source-language icon is rejected server-side;\n * deleting the icon for a non-source language clears that translation.\n *\n * @param parameters - Universe and language identifiers of the icon to\n * delete.\n * @returns A pure {@link HttpRequest} describing the delete call.\n */\nexport function buildDeleteIconRequest(parameters: DeleteExperienceIconParameters): HttpRequest {\n\treturn {\n\t\tmethod: \"DELETE\",\n\t\turl: `/legacy-game-internationalization/v1/game-icon/games/${parameters.universeId}/language-codes/${parameters.languageCode}`,\n\t};\n}\n\n/**\n * Builds a `GET` request for the \"list experience icons\" endpoint. The\n * server returns one entry per locale that has an icon registered.\n *\n * @param parameters - Universe identifier whose icons to list.\n * @returns A pure {@link HttpRequest} describing the list call.\n */\nexport function buildListIconsRequest(parameters: ListExperienceIconsParameters): HttpRequest {\n\treturn {\n\t\tmethod: \"GET\",\n\t\turl: `/legacy-game-internationalization/v1/game-icon/games/${parameters.universeId}`,\n\t};\n}\n","import type { OperationLimit } from \"../../../internal/http/rate-limit-queue.ts\";\n\n/**\n * Per-second request ceiling for every game-icon Operation bound on\n * `UniversesClient.icon`. The legacy `gameinternationalization` service caps\n * each API key at 100 requests per minute *shared across the entire service*\n * (see the `x-roblox-rate-limits` extension on every operation in the\n * vendored Open Cloud spec), so all methods queue against the same operation\n * key.\n */\nexport const ICON_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: 100 / 60,\n\toperationKey: \"experience-icon\",\n});\n\n/**\n * Scopes required for every game-icon operation, sourced from\n * `x-roblox-scopes` on the legacy `gameinternationalization` icon\n * endpoints in the vendored OpenAPI schema.\n */\nexport const ICON_REQUIRED_SCOPES: ReadonlyArray<string> = Object.freeze([\n\t\"legacy-universe:manage\",\n]);\n","import type { HttpResponse } from \"../../../client/types.ts\";\nimport { ApiError } from \"../../../errors/api-error.ts\";\nimport { isRecord } from \"../../../internal/utils/is-record.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type { ExperienceIcon } from \"./types.ts\";\nimport type { GameIconListWire, GameIconState, GetGameIconResponseWire } from \"./wire.ts\";\n\n/**\n * Parses a successful icon-list response into a public array of\n * {@link ExperienceIcon} entries.\n *\n * @param response - The full {@link HttpResponse} from the Open Cloud API.\n * @returns A success result wrapping the converted icon list, or an\n * `ApiError` when the body does not match the wire schema.\n */\nexport function parseIconListResponse(\n\tresponse: HttpResponse,\n): Result<ReadonlyArray<ExperienceIcon>, ApiError> {\n\tconst { body, status: statusCode } = response;\n\n\tif (!isGameIconListWire(body)) {\n\t\treturn {\n\t\t\terr: new ApiError(\"Malformed icon list response\", { statusCode }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: body.data.map(toExperienceIcon),\n\t\tsuccess: true,\n\t};\n}\n\nfunction isGameIconState(value: unknown): value is GameIconState {\n\treturn (\n\t\tvalue === \"Approved\" ||\n\t\tvalue === \"Error\" ||\n\t\tvalue === \"PendingReview\" ||\n\t\tvalue === \"Rejected\" ||\n\t\tvalue === \"UnAvailable\"\n\t);\n}\n\nfunction isGetGameIconResponseWire(value: unknown): value is GetGameIconResponseWire {\n\tif (!isRecord(value)) {\n\t\treturn false;\n\t}\n\n\treturn (\n\t\ttypeof value[\"imageId\"] === \"string\" &&\n\t\ttypeof value[\"imageUrl\"] === \"string\" &&\n\t\ttypeof value[\"languageCode\"] === \"string\" &&\n\t\tisGameIconState(value[\"state\"])\n\t);\n}\n\nfunction isGameIconListWire(body: unknown): body is GameIconListWire {\n\tif (!isRecord(body)) {\n\t\treturn false;\n\t}\n\n\tconst { data } = body;\n\tif (!Array.isArray(data)) {\n\t\treturn false;\n\t}\n\n\treturn data.every(isGetGameIconResponseWire);\n}\n\nfunction toExperienceIcon(wire: GetGameIconResponseWire): ExperienceIcon {\n\treturn {\n\t\timageId: wire.imageId,\n\t\timageUrl: wire.imageUrl,\n\t\tlanguageCode: wire.languageCode,\n\t\tstate: wire.state,\n\t};\n}\n","import { ValidationError } from \"../../../errors/validation.ts\";\nimport type { HttpRequest } from \"../../../internal/http/types.ts\";\nimport { toBlob } from \"../../../internal/utils/to-blob.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type {\n\tDeleteExperienceThumbnailParameters,\n\tReorderExperienceThumbnailsParameters,\n\tUploadExperienceThumbnailParameters,\n} from \"./types.ts\";\n\ntype ParsedIdsResult = Result<ReadonlyArray<number>, ValidationError>;\n\nconst POSITIVE_INTEGER_PATTERN = /^[1-9]\\d*$/;\n\n/**\n * Builds a `POST` request for the localized \"upload experience thumbnail\"\n * endpoint. Each successful upload appends a new entry to the carousel.\n *\n * @param parameters - Universe and language identifiers plus the image\n * bytes to upload.\n * @returns A pure {@link HttpRequest} describing the upload call.\n */\nexport function buildUploadThumbnailRequest(\n\tparameters: UploadExperienceThumbnailParameters,\n): HttpRequest {\n\tconst body = new FormData();\n\t// The legacy game-thumbnails endpoint reads the upload from\n\t// `gameThumbnailRequest.files`, distinct from game-icon's `request.files`.\n\tbody.append(\"gameThumbnailRequest.files\", toBlob(parameters.image));\n\n\treturn {\n\t\tbody,\n\t\tmethod: \"POST\",\n\t\t// The `{gameId}` URL segment in this legacy path is in fact the\n\t\t// universe ID; the package surfaces only `universeId`.\n\t\turl: `/legacy-game-internationalization/v1/game-thumbnails/games/${parameters.universeId}/language-codes/${parameters.languageCode}/image`,\n\t};\n}\n\n/**\n * Builds a `DELETE` request for the \"delete experience thumbnail\" endpoint.\n *\n * @param parameters - Universe, language, and image identifiers of the\n * thumbnail to delete.\n * @returns A pure {@link HttpRequest} describing the delete call.\n */\nexport function buildDeleteThumbnailRequest(\n\tparameters: DeleteExperienceThumbnailParameters,\n): HttpRequest {\n\treturn {\n\t\tmethod: \"DELETE\",\n\t\turl: `/legacy-game-internationalization/v1/game-thumbnails/games/${parameters.universeId}/language-codes/${parameters.languageCode}/images/${parameters.imageId}`,\n\t};\n}\n\n/**\n * Builds a `POST` request for the \"reorder experience thumbnails\" endpoint.\n * Validates each supplied image ID at the wire boundary so a typo cannot\n * silently serialize as JSON `null` and corrupt the request.\n *\n * @param parameters - Universe, language, and the desired display order.\n * @returns A success result wrapping the request, or a\n * {@link ValidationError} when `orderedImageIds` is empty or any ID is not\n * a positive integer within the safe-integer range.\n */\nexport function buildReorderThumbnailsRequest(\n\tparameters: ReorderExperienceThumbnailsParameters,\n): Result<HttpRequest, ValidationError> {\n\tconst { languageCode, orderedImageIds, universeId } = parameters;\n\n\tconst idsResult = parseOrderedImageIds(orderedImageIds);\n\tif (!idsResult.success) {\n\t\treturn idsResult;\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tbody: { mediaAssetIds: idsResult.data },\n\t\t\tmethod: \"POST\",\n\t\t\turl: `/legacy-game-internationalization/v1/game-thumbnails/games/${universeId}/language-codes/${languageCode}/images/order`,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction parseImageId(value: string): number | undefined {\n\tif (!POSITIVE_INTEGER_PATTERN.test(value)) {\n\t\treturn undefined;\n\t}\n\n\tconst parsed = Number(value);\n\tif (!Number.isSafeInteger(parsed)) {\n\t\treturn undefined;\n\t}\n\n\treturn parsed;\n}\n\nfunction appendParsedId(accumulator: ParsedIdsResult, id: string): ParsedIdsResult {\n\tif (!accumulator.success) {\n\t\treturn accumulator;\n\t}\n\n\tconst parsed = parseImageId(id);\n\tif (parsed === undefined) {\n\t\treturn {\n\t\t\terr: new ValidationError(\n\t\t\t\t`orderedImageIds entry ${JSON.stringify(id)} is not a positive integer ID`,\n\t\t\t\t{ code: \"invalid_image_id\" },\n\t\t\t),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn { data: [...accumulator.data, parsed], success: true };\n}\n\nfunction parseOrderedImageIds(orderedImageIds: ReadonlyArray<string>): ParsedIdsResult {\n\tif (orderedImageIds.length === 0) {\n\t\treturn {\n\t\t\terr: new ValidationError(\"orderedImageIds must contain at least one image ID\", {\n\t\t\t\tcode: \"empty_image_ids\",\n\t\t\t}),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn orderedImageIds.reduce<ParsedIdsResult>(appendParsedId, { data: [], success: true });\n}\n","import type { OperationLimit } from \"../../../internal/http/rate-limit-queue.ts\";\n\n/**\n * Per-second request ceiling for every game-thumbnails Operation bound on\n * `UniversesClient.thumbnails`. The legacy `gameinternationalization`\n * service caps each API key at 100 requests per minute *shared across the\n * entire service* (see the `x-roblox-rate-limits` extension on every\n * operation in the vendored Open Cloud spec), so all methods queue against\n * the same operation key.\n */\nexport const THUMBNAILS_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: 100 / 60,\n\toperationKey: \"experience-thumbnails\",\n});\n\n/**\n * Scopes required for every game-thumbnails operation, sourced from\n * `x-roblox-scopes` on the legacy `gameinternationalization` thumbnail\n * endpoints in the vendored OpenAPI schema.\n */\nexport const THUMBNAILS_REQUIRED_SCOPES: ReadonlyArray<string> = Object.freeze([\n\t\"legacy-universe:manage\",\n]);\n","import type { HttpResponse } from \"../../../client/types.ts\";\nimport { ApiError } from \"../../../errors/api-error.ts\";\nimport { isRecord } from \"../../../internal/utils/is-record.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type { UploadedExperienceThumbnail } from \"./types.ts\";\nimport type { GameThumbnailUploadWire } from \"./wire.ts\";\n\n/**\n * Parses a successful thumbnail-upload response into the public\n * {@link UploadedExperienceThumbnail} shape, returning a {@link Result}\n * so callers can handle malformed payloads without exceptions.\n *\n * @param response - The full {@link HttpResponse} from the Open Cloud API.\n * @returns A success result wrapping the converted upload, or an\n * `ApiError` when the body does not match the wire schema.\n */\nexport function parseThumbnailUploadResponse(\n\tresponse: HttpResponse,\n): Result<UploadedExperienceThumbnail, ApiError> {\n\tconst { body, status: statusCode } = response;\n\n\tif (!isGameThumbnailUploadWire(body)) {\n\t\treturn {\n\t\t\terr: new ApiError(\"Malformed thumbnail upload response\", { statusCode }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: { mediaAssetId: body.mediaAssetId },\n\t\tsuccess: true,\n\t};\n}\n\nfunction isGameThumbnailUploadWire(body: unknown): body is GameThumbnailUploadWire {\n\tif (!isRecord(body)) {\n\t\treturn false;\n\t}\n\n\treturn typeof body[\"mediaAssetId\"] === \"string\";\n}\n","import type { OpenCloudClientOptions, RequestOptions } from \"../../client/types.ts\";\nimport { buildGetRequest, buildUpdateRequest } from \"../../domains/cloud-v2/universes/builders.ts\";\nimport {\n\tGET_OPERATION_LIMIT,\n\tUPDATE_OPERATION_LIMIT,\n\tUPDATE_REQUIRED_SCOPES,\n} from \"../../domains/cloud-v2/universes/operations.ts\";\nimport { parseUniverseResponse } from \"../../domains/cloud-v2/universes/parsers.ts\";\nimport type {\n\tGetUniverseParameters,\n\tUniverse,\n\tUpdateUniverseParameters,\n} from \"../../domains/cloud-v2/universes/types.ts\";\nimport {\n\tbuildDeleteIconRequest,\n\tbuildListIconsRequest,\n\tbuildUploadIconRequest,\n} from \"../../domains/game-internationalization/game-icon/builders.ts\";\nimport {\n\tICON_OPERATION_LIMIT,\n\tICON_REQUIRED_SCOPES,\n} from \"../../domains/game-internationalization/game-icon/operations.ts\";\nimport { parseIconListResponse } from \"../../domains/game-internationalization/game-icon/parsers.ts\";\nimport type {\n\tDeleteExperienceIconParameters,\n\tExperienceIcon,\n\tListExperienceIconsParameters,\n\tUploadExperienceIconParameters,\n} from \"../../domains/game-internationalization/game-icon/types.ts\";\nimport {\n\tbuildDeleteThumbnailRequest,\n\tbuildReorderThumbnailsRequest,\n\tbuildUploadThumbnailRequest,\n} from \"../../domains/game-internationalization/game-thumbnails/builders.ts\";\nimport {\n\tTHUMBNAILS_OPERATION_LIMIT,\n\tTHUMBNAILS_REQUIRED_SCOPES,\n} from \"../../domains/game-internationalization/game-thumbnails/operations.ts\";\nimport { parseThumbnailUploadResponse } from \"../../domains/game-internationalization/game-thumbnails/parsers.ts\";\nimport type {\n\tDeleteExperienceThumbnailParameters,\n\tReorderExperienceThumbnailsParameters,\n\tUploadedExperienceThumbnail,\n\tUploadExperienceThumbnailParameters,\n} from \"../../domains/game-internationalization/game-thumbnails/types.ts\";\nimport type { OpenCloudError } from \"../../errors/base.ts\";\nimport { CREATE_METHOD_DEFAULTS, IDEMPOTENT_METHOD_DEFAULTS } from \"../../internal/http/retry.ts\";\nimport type { HttpRequest } from \"../../internal/http/types.ts\";\nimport {\n\tokRequest,\n\tparseEmptyResponse,\n\tResourceClient,\n\ttype ResourceMethodSpec,\n} from \"../../internal/resource-client.ts\";\nimport type { Result } from \"../../types.ts\";\n\nconst GET_SPEC: ResourceMethodSpec<GetUniverseParameters, Universe> = Object.freeze({\n\tbuildRequest: buildGetRequest,\n\tmethodDefaults: {},\n\tmethodKind: \"idempotent\",\n\toperationLimit: GET_OPERATION_LIMIT,\n\tparse: parseUniverseResponse,\n});\n\nconst UPDATE_SPEC: ResourceMethodSpec<UpdateUniverseParameters, Universe> = Object.freeze({\n\tbuildRequest: buildUpdateRequest,\n\tmethodDefaults: {},\n\tmethodKind: \"idempotent\",\n\toperationLimit: UPDATE_OPERATION_LIMIT,\n\tparse: parseUniverseResponse,\n\trequiredScopes: UPDATE_REQUIRED_SCOPES,\n});\n\nfunction buildIconUploadOkRequest(\n\tparameters: UploadExperienceIconParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest(buildUploadIconRequest(parameters));\n}\n\nfunction buildIconDeleteOkRequest(\n\tparameters: DeleteExperienceIconParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest(buildDeleteIconRequest(parameters));\n}\n\nfunction buildIconListOkRequest(\n\tparameters: ListExperienceIconsParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest(buildListIconsRequest(parameters));\n}\n\nconst ICON_UPLOAD_SPEC: ResourceMethodSpec<UploadExperienceIconParameters, undefined> =\n\tObject.freeze({\n\t\tbuildRequest: buildIconUploadOkRequest,\n\t\tmethodDefaults: CREATE_METHOD_DEFAULTS,\n\t\tmethodKind: \"create\",\n\t\toperationLimit: ICON_OPERATION_LIMIT,\n\t\tparse: parseEmptyResponse,\n\t\trequiredScopes: ICON_REQUIRED_SCOPES,\n\t});\n\nconst ICON_DELETE_SPEC: ResourceMethodSpec<DeleteExperienceIconParameters, undefined> =\n\tObject.freeze({\n\t\tbuildRequest: buildIconDeleteOkRequest,\n\t\tmethodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n\t\tmethodKind: \"idempotent\",\n\t\toperationLimit: ICON_OPERATION_LIMIT,\n\t\tparse: parseEmptyResponse,\n\t\trequiredScopes: ICON_REQUIRED_SCOPES,\n\t});\n\nconst ICON_LIST_SPEC: ResourceMethodSpec<\n\tListExperienceIconsParameters,\n\tReadonlyArray<ExperienceIcon>\n> = Object.freeze({\n\tbuildRequest: buildIconListOkRequest,\n\tmethodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n\tmethodKind: \"idempotent\",\n\toperationLimit: ICON_OPERATION_LIMIT,\n\tparse: parseIconListResponse,\n\trequiredScopes: ICON_REQUIRED_SCOPES,\n});\n\nfunction buildThumbnailUploadOkRequest(\n\tparameters: UploadExperienceThumbnailParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest(buildUploadThumbnailRequest(parameters));\n}\n\nfunction buildThumbnailDeleteOkRequest(\n\tparameters: DeleteExperienceThumbnailParameters,\n): Result<HttpRequest, OpenCloudError> {\n\treturn okRequest(buildDeleteThumbnailRequest(parameters));\n}\n\nconst THUMBNAIL_UPLOAD_SPEC: ResourceMethodSpec<\n\tUploadExperienceThumbnailParameters,\n\tUploadedExperienceThumbnail\n> = Object.freeze({\n\tbuildRequest: buildThumbnailUploadOkRequest,\n\tmethodDefaults: CREATE_METHOD_DEFAULTS,\n\tmethodKind: \"create\",\n\toperationLimit: THUMBNAILS_OPERATION_LIMIT,\n\tparse: parseThumbnailUploadResponse,\n\trequiredScopes: THUMBNAILS_REQUIRED_SCOPES,\n});\n\nconst THUMBNAIL_DELETE_SPEC: ResourceMethodSpec<DeleteExperienceThumbnailParameters, undefined> =\n\tObject.freeze({\n\t\tbuildRequest: buildThumbnailDeleteOkRequest,\n\t\tmethodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n\t\tmethodKind: \"idempotent\",\n\t\toperationLimit: THUMBNAILS_OPERATION_LIMIT,\n\t\tparse: parseEmptyResponse,\n\t\trequiredScopes: THUMBNAILS_REQUIRED_SCOPES,\n\t});\n\nconst THUMBNAIL_REORDER_SPEC: ResourceMethodSpec<ReorderExperienceThumbnailsParameters, undefined> =\n\tObject.freeze({\n\t\tbuildRequest: buildReorderThumbnailsRequest,\n\t\tmethodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n\t\tmethodKind: \"idempotent\",\n\t\toperationLimit: THUMBNAILS_OPERATION_LIMIT,\n\t\tparse: parseEmptyResponse,\n\t\trequiredScopes: THUMBNAILS_REQUIRED_SCOPES,\n\t});\n\ninterface UniverseIconHandle {\n\t/**\n\t * Deletes the localized icon registered against a universe for a given\n\t * language. Removing the source-language icon is rejected server-side;\n\t * consumers must replace it via {@link UniverseIconHandle.upload}\n\t * instead.\n\t *\n\t * @param parameters - Universe and language identifiers of the icon to\n\t * delete.\n\t * @param options - Optional per-request overrides.\n\t * @returns A success {@link Result} with no payload, or the\n\t * {@link OpenCloudError} that caused the request to fail.\n\t */\n\tdelete: (\n\t\tparameters: DeleteExperienceIconParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<undefined, OpenCloudError>>;\n\t/**\n\t * Lists every localized icon registered against an experience. The\n\t * server returns one entry per locale that has an icon registered.\n\t *\n\t * @param parameters - Universe identifier whose icons to list.\n\t * @param options - Optional per-request overrides.\n\t * @returns A {@link Result} wrapping the parsed array of\n\t * {@link ExperienceIcon} entries or the {@link OpenCloudError} that\n\t * caused the request to fail.\n\t */\n\tlist: (\n\t\tparameters: ListExperienceIconsParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<ReadonlyArray<ExperienceIcon>, OpenCloudError>>;\n\t/**\n\t * Uploads or replaces the localized icon for an experience. A\n\t * subsequent upload for the same `(universeId, languageCode)` pair\n\t * replaces the existing icon for that locale.\n\t *\n\t * No default request timeout applies to this upload; pass `options.timeout`\n\t * to set a per-call deadline.\n\t *\n\t * @param parameters - Universe and language identifiers plus the image\n\t * bytes to upload.\n\t * @param options - Optional per-request overrides (e.g. A different\n\t * {@link OpenCloudClientOptions.apiKey} for this call only).\n\t * @returns A success {@link Result} with no payload, or the\n\t * {@link OpenCloudError} that caused the request to fail.\n\t */\n\tupload: (\n\t\tparameters: UploadExperienceIconParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<undefined, OpenCloudError>>;\n}\n\ninterface UniverseThumbnailsHandle {\n\t/**\n\t * Deletes a single thumbnail by media asset ID. Idempotent: deleting an\n\t * already-removed thumbnail surfaces the server's 404 unchanged.\n\t *\n\t * @param parameters - Universe, language, and image identifiers of the\n\t * thumbnail to delete.\n\t * @param options - Optional per-request overrides.\n\t * @returns A success {@link Result} with no payload, or the\n\t * {@link OpenCloudError} that caused the request to fail.\n\t */\n\tdelete: (\n\t\tparameters: DeleteExperienceThumbnailParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<undefined, OpenCloudError>>;\n\t/**\n\t * Reorders the localized thumbnail carousel. The supplied\n\t * `orderedImageIds` describes the desired display order from first to\n\t * last. Image IDs must be positive integers within the safe-integer\n\t * range; invalid input is rejected with a {@link OpenCloudError} of\n\t * kind `ValidationError` before any HTTP round-trip.\n\t *\n\t * @param parameters - Universe, language, and the desired display order.\n\t * @param options - Optional per-request overrides.\n\t * @returns A success {@link Result} with no payload, or the\n\t * {@link OpenCloudError} that caused the request to fail.\n\t */\n\treorder: (\n\t\tparameters: ReorderExperienceThumbnailsParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<undefined, OpenCloudError>>;\n\t/**\n\t * Uploads a new thumbnail and appends it to the localized carousel. Use\n\t * {@link UniverseThumbnailsHandle.reorder} after multiple uploads to\n\t * set the display order.\n\t *\n\t * No default request timeout applies to this upload; pass `options.timeout`\n\t * to set a per-call deadline.\n\t *\n\t * @param parameters - Universe and language identifiers plus the image\n\t * bytes to upload.\n\t * @param options - Optional per-request overrides (e.g. A different\n\t * {@link OpenCloudClientOptions.apiKey} for this call only).\n\t * @returns A {@link Result} wrapping the parsed\n\t * {@link UploadedExperienceThumbnail} or the {@link OpenCloudError}\n\t * that caused the request to fail.\n\t */\n\tupload: (\n\t\tparameters: UploadExperienceThumbnailParameters,\n\t\toptions?: RequestOptions,\n\t) => Promise<Result<UploadedExperienceThumbnail, OpenCloudError>>;\n}\n\n/**\n * Public client for the Roblox Open Cloud `Universe` resource. Wires\n * the request builders, the injected\n * {@link OpenCloudClientOptions.httpClient}, and the response parser\n * into a single ergonomic surface. Every method returns a\n * {@link Result} so callers handle failure explicitly; no thrown\n * {@link OpenCloudError} ever escapes the client.\n *\n * Partial updates use a Google-style `updateMask` query string derived\n * from the keys present on the update parameters. Setting a clearable\n * field (`privateServerPriceRobux` or any social link) to `undefined`\n * sends JSON `null` for that field so the server clears the\n * corresponding value.\n *\n * Localized experience-icon and experience-thumbnail Operations are\n * bound on the {@link UniversesClient.icon} and\n * {@link UniversesClient.thumbnails} Operation Groups so callers reach\n * for one client per universe.\n *\n * @since 0.1.0\n *\n * @example\n *\n * ```ts\n * import { UniversesClient } from \"@bedrock-rbx/ocale/universes\";\n *\n * const client = new UniversesClient({ apiKey: \"your-key\" });\n * expect(client).toBeInstanceOf(UniversesClient);\n * ```\n */\nexport class UniversesClient {\n\treadonly #inner: ResourceClient;\n\n\t/**\n\t * Operation Group exposing the localized experience-icon\n\t * Operations (`upload`, `delete`, `list`) backed by the\n\t * `legacy-game-internationalization` domain. Shares the parent\n\t * client's HTTP, rate-limit, and retry plumbing.\n\t */\n\tpublic readonly icon: UniverseIconHandle;\n\t/**\n\t * Operation Group exposing the localized experience-thumbnail\n\t * Operations (`upload`, `delete`, `reorder`) backed by the\n\t * `legacy-game-internationalization` domain. No list-thumbnails\n\t * endpoint is bridged; consumers must track uploaded\n\t * `mediaAssetId`s in their own state store to reconcile against\n\t * the existing carousel. Shares the parent client's HTTP,\n\t * rate-limit, and retry plumbing.\n\t */\n\tpublic readonly thumbnails: UniverseThumbnailsHandle;\n\n\t/**\n\t * Creates a new {@link UniversesClient}. Configuration is frozen\n\t * on construction; per-request overrides are accepted on each\n\t * method.\n\t *\n\t * @param options - Client-level configuration including the API key.\n\t */\n\tconstructor(options: OpenCloudClientOptions) {\n\t\tthis.#inner = new ResourceClient(options);\n\t\tthis.icon = createIconHandle(this.#inner);\n\t\tthis.thumbnails = createThumbnailsHandle(this.#inner);\n\t}\n\n\t/**\n\t * Fetches the current configuration of a universe.\n\t *\n\t * @param parameters - The universe identifier.\n\t * @param options - Optional per-request overrides (e.g. A different\n\t * {@link OpenCloudClientOptions.apiKey} for this call only).\n\t * @returns A {@link Result} wrapping the parsed {@link Universe}\n\t * or the {@link OpenCloudError} that caused the request to fail.\n\t */\n\tpublic async get(\n\t\tparameters: GetUniverseParameters,\n\t\toptions?: RequestOptions,\n\t): Promise<Result<Universe, OpenCloudError>> {\n\t\treturn this.#inner.execute({ options, parameters, spec: GET_SPEC });\n\t}\n\n\t/**\n\t * Partially updates a universe's configuration. The fields\n\t * supplied on `parameters` (excluding `universeId`) are forwarded\n\t * to the server via a Google-style `updateMask`; unmentioned\n\t * fields are left untouched.\n\t *\n\t * @param parameters - The universe identifier and the fields to\n\t * update. At least one updatable field must be supplied.\n\t * @param options - Optional per-request overrides (e.g. A different\n\t * {@link OpenCloudClientOptions.apiKey} for this call only).\n\t * @returns A {@link Result} wrapping the parsed {@link Universe}\n\t * or the {@link OpenCloudError} that caused the request to fail.\n\t */\n\tpublic async update(\n\t\tparameters: UpdateUniverseParameters,\n\t\toptions?: RequestOptions,\n\t): Promise<Result<Universe, OpenCloudError>> {\n\t\treturn this.#inner.execute({ options, parameters, spec: UPDATE_SPEC });\n\t}\n}\n\nfunction createIconHandle(inner: ResourceClient): UniverseIconHandle {\n\treturn {\n\t\tasync delete(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: ICON_DELETE_SPEC });\n\t\t},\n\t\tasync list(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: ICON_LIST_SPEC });\n\t\t},\n\t\tasync upload(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: ICON_UPLOAD_SPEC });\n\t\t},\n\t};\n}\n\nfunction createThumbnailsHandle(inner: ResourceClient): UniverseThumbnailsHandle {\n\treturn {\n\t\tasync delete(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: THUMBNAIL_DELETE_SPEC });\n\t\t},\n\t\tasync reorder(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: THUMBNAIL_REORDER_SPEC });\n\t\t},\n\t\tasync upload(parameters, options) {\n\t\t\treturn inner.execute({ options, parameters, spec: THUMBNAIL_UPLOAD_SPEC });\n\t\t},\n\t};\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bedrock-rbx/ocale",
3
- "version": "0.1.0-beta.21",
3
+ "version": "0.1.0-beta.22",
4
4
  "description": "Roblox Open Cloud API client",
5
5
  "keywords": [
6
6
  "api",
@@ -54,8 +54,8 @@
54
54
  "type-fest": "5.6.0",
55
55
  "typescript": "npm:@typescript/typescript6@6.0.1",
56
56
  "vitest": "4.1.9",
57
- "@bedrock-rbx/testing": "0.0.0",
58
57
  "@bedrock-rbx/vite-config": "0.0.0",
58
+ "@bedrock-rbx/testing": "0.0.0",
59
59
  "@bedrock-rbx/typescript-config": "0.0.0"
60
60
  },
61
61
  "engines": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"polling-helpers-Cb4j4cq7.mjs","names":["SECONDS_PER_MINUTE","malformed","makeSpec"],"sources":["../src/domains/cloud-v2/luau-execution-task-logs/builders.ts","../src/domains/cloud-v2/luau-execution-task-logs/operations.ts","../src/domains/cloud-v2/luau-execution-task-logs/parsers.ts","../src/domains/cloud-v2/luau-execution-task-logs/specs.ts","../src/domains/cloud-v2/luau-execution-tasks/builders.ts","../src/domains/cloud-v2/luau-execution-tasks/operations.ts","../src/domains/cloud-v2/luau-execution-tasks/parsers.ts","../src/domains/cloud-v2/luau-execution-tasks/specs.ts","../src/resources/luau-execution/polling.ts","../src/resources/luau-execution/polling-helpers.ts"],"sourcesContent":["import type { HttpRequest } from \"../../../client/types.ts\";\nimport { ValidationError } from \"../../../errors/validation.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type { ListLogsParameters } from \"./types.ts\";\n\n/**\n * Builds a `GET` request for the Open Cloud \"list Luau execution session\n * task logs\" endpoint. The endpoint requires the maximal x-aep-resource\n * path shape (universe, place, version, session, task), so the supplied\n * ref must include `versionId` and `sessionId`; refs extracted from the\n * narrower path formats are rejected with a {@link ValidationError}.\n *\n * The `view` query parameter is hard-coded to `STRUCTURED` so callers\n * always receive typed structured messages. No public `view` parameter\n * is exposed.\n *\n * @param parameters - Task ref, and optional `pageSize` and `pageToken`\n * pagination controls.\n * @returns A success result wrapping the request, or a\n * {@link ValidationError} when the ref is missing `versionId` or\n * `sessionId`.\n */\nexport function buildListLogsRequest(\n\tparameters: ListLogsParameters,\n): Result<HttpRequest, ValidationError> {\n\tconst { pageSize, pageToken, ref } = parameters;\n\tconst { placeId, sessionId, taskId, universeId, versionId } = ref;\n\n\tif (versionId === undefined) {\n\t\treturn {\n\t\t\terr: new ValidationError(\"Task ref is missing versionId; cannot list logs\", {\n\t\t\t\tcode: \"incomplete_ref\",\n\t\t\t}),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tif (sessionId === undefined) {\n\t\treturn {\n\t\t\terr: new ValidationError(\"Task ref is missing sessionId; cannot list logs\", {\n\t\t\t\tcode: \"incomplete_ref\",\n\t\t\t}),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tconst base = `/cloud/v2/universes/${universeId}/places/${placeId}/versions/${versionId}/luau-execution-sessions/${sessionId}/tasks/${taskId}/logs`;\n\tconst url = `${base}?${buildQuery(pageSize, pageToken).toString()}`;\n\treturn { data: { method: \"GET\", url }, success: true };\n}\n\nfunction buildQuery(pageSize: number | undefined, pageToken: string | undefined): URLSearchParams {\n\tconst query = new URLSearchParams({ view: \"STRUCTURED\" });\n\n\tif (pageSize !== undefined) {\n\t\tquery.append(\"maxPageSize\", String(pageSize));\n\t}\n\n\tif (pageToken !== undefined) {\n\t\tquery.append(\"pageToken\", pageToken);\n\t}\n\n\treturn query;\n}\n","import type { OperationLimit } from \"../../../internal/http/rate-limit-queue.ts\";\n\nconst LIST_LOGS_PER_MINUTE = 45;\nconst SECONDS_PER_MINUTE = 60;\n\n/**\n * Per-second request ceiling for listing Luau execution task logs,\n * sourced from `x-roblox-rate-limits.perApiKeyOwner` on the\n * `Cloud_ListLuauExecutionSessionTaskLogs` operation (45 requests per\n * minute per API key owner).\n */\nexport const LIST_LOGS_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: LIST_LOGS_PER_MINUTE / SECONDS_PER_MINUTE,\n\toperationKey: \"luau-execution-task-logs.list\",\n});\n\n/**\n * Scopes required to list Luau execution task logs, sourced from\n * `x-roblox-scopes` on the list-logs operation in the vendored OpenAPI\n * schema. Surfaced via the `requiredScopes` field of the per-method\n * spec so a 401 or 403 ApiError is upgraded to a `PermissionError`\n * naming the missing scope. Only `:read` is required as the\n * minimum-privilege scope for this read-only operation.\n */\nexport const LIST_LOGS_REQUIRED_SCOPES: ReadonlyArray<string> = Object.freeze([\n\t\"universe.place.luau-execution-session:read\",\n]);\n","import type { HttpResponse } from \"../../../client/types.ts\";\nimport { ApiError } from \"../../../errors/api-error.ts\";\nimport { isRecord } from \"../../../internal/utils/is-record.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type { LogMessage, LogPage } from \"./types.ts\";\nimport type { LogChunkWire, LogMessageWire } from \"./wire.ts\";\n\nconst MALFORMED_LOGS_MESSAGE = \"Malformed list-luau-execution-task-logs response\";\n\n/**\n * Parses a successful Open Cloud list-luau-execution-task-logs response\n * body into the public {@link LogPage} shape. Chunks are flattened into\n * a single ordered array of {@link LogMessage} values. The\n * `MESSAGE_TYPE_UNSPECIFIED` sentinel is rejected.\n *\n * @param response - The full {@link HttpResponse} from the Open Cloud API.\n * @returns A success result wrapping the parsed {@link LogPage}, or an\n * {@link ApiError} when the body does not match a supported shape.\n */\nexport function parseListLogsResponse(response: HttpResponse): Result<LogPage, ApiError> {\n\tconst { body, status: statusCode } = response;\n\tif (!isRecord(body)) {\n\t\treturn malformed(statusCode);\n\t}\n\n\tconst rawChunks = body[\"luauExecutionSessionTaskLogs\"] ?? undefined;\n\tif (!isOptionalLogChunks(rawChunks)) {\n\t\treturn malformed(statusCode);\n\t}\n\n\tconst rawToken = body[\"nextPageToken\"] ?? undefined;\n\tif (rawToken !== undefined && typeof rawToken !== \"string\") {\n\t\treturn malformed(statusCode);\n\t}\n\n\tconst messages: Array<LogMessage> = [];\n\tfor (const chunk of rawChunks ?? []) {\n\t\tfor (const wireMessage of chunk.structuredMessages ?? []) {\n\t\t\tmessages.push({\n\t\t\t\tcreateTime: wireMessage.createTime,\n\t\t\t\tmessage: wireMessage.message,\n\t\t\t\tmessageType: wireMessage.messageType,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\tdata: { messages, nextPageToken: rawToken },\n\t\tsuccess: true,\n\t};\n}\n\nfunction isAcceptedMessageType(value: unknown): value is LogMessageWire[\"messageType\"] {\n\treturn value === \"OUTPUT\" || value === \"INFO\" || value === \"WARNING\" || value === \"ERROR\";\n}\n\nfunction isLogMessageWire(value: unknown): value is LogMessageWire {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value[\"createTime\"] === \"string\" &&\n\t\ttypeof value[\"message\"] === \"string\" &&\n\t\tisAcceptedMessageType(value[\"messageType\"])\n\t);\n}\n\nfunction isOptionalStructuredMessages(\n\tvalue: unknown,\n): value is ReadonlyArray<LogMessageWire> | undefined {\n\treturn (\n\t\tvalue === undefined ||\n\t\t(Array.isArray(value) && value.every((item: unknown) => isLogMessageWire(item)))\n\t);\n}\n\nfunction isLogChunkWire(value: unknown): value is LogChunkWire {\n\treturn isRecord(value) && isOptionalStructuredMessages(value[\"structuredMessages\"]);\n}\n\nfunction isOptionalLogChunks(value: unknown): value is ReadonlyArray<LogChunkWire> | undefined {\n\treturn (\n\t\tvalue === undefined ||\n\t\t(Array.isArray(value) && value.every((item: unknown) => isLogChunkWire(item)))\n\t);\n}\n\nfunction malformed(statusCode: number): Result<LogPage, ApiError> {\n\treturn { err: new ApiError(MALFORMED_LOGS_MESSAGE, { statusCode }), success: false };\n}\n","import { IDEMPOTENT_METHOD_DEFAULTS } from \"../../../internal/http/retry.ts\";\nimport type { ResourceMethodSpec } from \"../../../internal/resource-client.ts\";\nimport { buildListLogsRequest } from \"./builders.ts\";\nimport { LIST_LOGS_OPERATION_LIMIT, LIST_LOGS_REQUIRED_SCOPES } from \"./operations.ts\";\nimport { parseListLogsResponse } from \"./parsers.ts\";\nimport type { ListLogsParameters, LogPage } from \"./types.ts\";\n\nfunction makeSpec<P>(spec: ResourceMethodSpec<P, LogPage>): ResourceMethodSpec<P, LogPage> {\n\treturn Object.freeze(spec);\n}\n\n/**\n * Per-method dispatch spec for listing the structured log messages\n * produced by a Luau execution task. Frozen at module scope so both\n * the top-level `LuauExecutionClient` and the `luauExecution` Operation\n * Group on `PlacesClient` share the same instance reference.\n */\nexport const LIST_LOGS_SPEC = makeSpec<ListLogsParameters>({\n\tbuildRequest: buildListLogsRequest,\n\tmethodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n\tmethodKind: \"idempotent\",\n\toperationLimit: LIST_LOGS_OPERATION_LIMIT,\n\tparse: parseListLogsResponse,\n\trequiredScopes: LIST_LOGS_REQUIRED_SCOPES,\n});\n","import type { HttpRequest } from \"../../../client/types.ts\";\nimport { ValidationError } from \"../../../errors/validation.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type { GetParameters, SubmitAtHeadParameters, SubmitAtVersionParameters } from \"./types.ts\";\n\ninterface SubmitBodyInput {\n\treadonly binaryInput?: string | undefined;\n\treadonly enableBinaryOutput?: boolean | undefined;\n\treadonly script: string;\n\treadonly timeoutSeconds?: number;\n}\n\nconst JSON_HEADERS: Readonly<Record<string, string>> = { \"content-type\": \"application/json\" };\n\n/**\n * Builds a `POST` request for the Open Cloud \"create Luau execution\n * session task\" endpoint, targeting the place's head version. Serializes\n * `timeoutSeconds` into the wire's duration string format (`\"<n>s\"`)\n * when supplied.\n *\n * @param parameters - Universe and place identifiers, the script body,\n * and an optional `timeoutSeconds`.\n * @returns A pure {@link HttpRequest} describing the submit call.\n */\nexport function buildSubmitAtHeadRequest(parameters: SubmitAtHeadParameters): HttpRequest {\n\tconst { placeId, universeId } = parameters;\n\treturn {\n\t\tbody: buildSubmitBody(parameters),\n\t\theaders: JSON_HEADERS,\n\t\tmethod: \"POST\",\n\t\turl: `/cloud/v2/universes/${universeId}/places/${placeId}/luau-execution-session-tasks`,\n\t};\n}\n\n/**\n * Builds a `POST` request for the Open Cloud \"create Luau execution\n * session task\" endpoint, targeting a specific place version. Differs\n * from {@link buildSubmitAtHeadRequest} only in URL shape: the path\n * includes the `versions/{versionId}` segment so the script runs\n * against that exact place version instead of the live head.\n *\n * @param parameters - Universe, place, and version identifiers, the\n * script body, and an optional `timeoutSeconds`.\n * @returns A pure {@link HttpRequest} describing the submit call.\n */\nexport function buildSubmitAtVersionRequest(parameters: SubmitAtVersionParameters): HttpRequest {\n\tconst { placeId, universeId, versionId } = parameters;\n\treturn {\n\t\tbody: buildSubmitBody(parameters),\n\t\theaders: JSON_HEADERS,\n\t\tmethod: \"POST\",\n\t\turl: `/cloud/v2/universes/${universeId}/places/${placeId}/versions/${versionId}/luau-execution-session-tasks`,\n\t};\n}\n\n/**\n * Builds a `GET` request for the Open Cloud \"read Luau execution session\n * task\" endpoint. The endpoint accepts only the maximal x-aep-resource\n * path shape (universe, place, version, session, task), so the supplied\n * ref must include `versionId` and `sessionId`; refs extracted from the\n * narrower path formats are rejected with a {@link ValidationError}.\n *\n * @param parameters - Task ref and optional view selector. When `view`\n * is omitted, no `?view=` query is sent and the server applies its\n * own default (`BASIC`).\n * @returns A success result wrapping the request, or a\n * {@link ValidationError} when the ref is missing `versionId` or\n * `sessionId`.\n */\nexport function buildGetRequest(parameters: GetParameters): Result<HttpRequest, ValidationError> {\n\tconst { ref, view } = parameters;\n\tconst { placeId, sessionId, taskId, universeId, versionId } = ref;\n\n\tif (versionId === undefined) {\n\t\treturn {\n\t\t\terr: new ValidationError(\"Task ref is missing versionId; cannot GET\", {\n\t\t\t\tcode: \"incomplete_ref\",\n\t\t\t}),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tif (sessionId === undefined) {\n\t\treturn {\n\t\t\terr: new ValidationError(\"Task ref is missing sessionId; cannot GET\", {\n\t\t\t\tcode: \"incomplete_ref\",\n\t\t\t}),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tconst base = `/cloud/v2/universes/${universeId}/places/${placeId}/versions/${versionId}/luau-execution-sessions/${sessionId}/tasks/${taskId}`;\n\tconst url = view === undefined ? base : `${base}?view=${view}`;\n\treturn { data: { method: \"GET\", url }, success: true };\n}\n\nfunction buildSubmitBody(parameters: SubmitBodyInput): Record<string, unknown> {\n\tconst {\n\t\tbinaryInput,\n\t\tenableBinaryOutput: shouldEnableBinaryOutput,\n\t\tscript,\n\t\ttimeoutSeconds,\n\t} = parameters;\n\tconst body: Record<string, unknown> = { script };\n\tif (timeoutSeconds !== undefined) {\n\t\tbody[\"timeout\"] = `${timeoutSeconds}s`;\n\t}\n\n\tif (binaryInput !== undefined) {\n\t\tbody[\"binaryInput\"] = binaryInput;\n\t}\n\n\tif (shouldEnableBinaryOutput !== undefined) {\n\t\tbody[\"enableBinaryOutput\"] = shouldEnableBinaryOutput;\n\t}\n\n\treturn body;\n}\n","import type { OperationLimit } from \"../../../internal/http/rate-limit-queue.ts\";\n\nconst SUBMIT_PER_MINUTE = 40;\nconst GET_PER_MINUTE = 200;\nconst SECONDS_PER_MINUTE = 60;\n\n/**\n * Per-second request ceiling for submitting a Luau execution task,\n * sourced from `x-roblox-rate-limits.perApiKeyOwner` on the\n * `Cloud_CreateLuauExecutionSessionTask__Using_Universes` operation\n * (40 requests per minute per API key owner). The two URL shapes\n * (head and version) share this queue because Roblox attributes both\n * to the same per-minute quota.\n */\nexport const SUBMIT_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: SUBMIT_PER_MINUTE / SECONDS_PER_MINUTE,\n\toperationKey: \"luau-execution-tasks.submit\",\n});\n\n/**\n * Per-second request ceiling for fetching a Luau execution task,\n * sourced from `x-roblox-rate-limits.perApiKeyOwner` on the\n * `Cloud_GetLuauExecutionSessionTask` operation (200 requests per\n * minute per API key owner).\n */\nexport const GET_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: GET_PER_MINUTE / SECONDS_PER_MINUTE,\n\toperationKey: \"luau-execution-tasks.get\",\n});\n\n/**\n * Scopes required to submit a Luau execution task, sourced from\n * `x-roblox-scopes` on the create operation in the vendored OpenAPI\n * schema. Surfaced via the `requiredScopes` field of the per-method\n * spec so a 401 or 403 ApiError is upgraded to a `PermissionError`\n * naming the missing scope.\n */\nexport const SUBMIT_REQUIRED_SCOPES: ReadonlyArray<string> = Object.freeze([\n\t\"universe.place.luau-execution-session:write\",\n]);\n\n/**\n * Scopes required to fetch a Luau execution task, sourced from\n * `x-roblox-scopes` on the get operation. The `:write` scope also\n * grants read in upstream auth, but we surface only `:read` here as\n * the minimum-privilege requirement for this method.\n */\nexport const GET_REQUIRED_SCOPES: ReadonlyArray<string> = Object.freeze([\n\t\"universe.place.luau-execution-session:read\",\n]);\n","import type { HttpResponse } from \"../../../client/types.ts\";\nimport { ApiError } from \"../../../errors/api-error.ts\";\nimport { isDateTimeString } from \"../../../internal/utils/is-date-time-string.ts\";\nimport { isRecord } from \"../../../internal/utils/is-record.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type { LuauExecutionTask, LuauExecutionTaskRef } from \"./types.ts\";\nimport type {\n\tLuauExecutionTaskErrorWire,\n\tLuauExecutionTaskOutputWire,\n\tLuauExecutionTaskWire,\n} from \"./wire.ts\";\n\nconst MALFORMED_TASK_MESSAGE = \"Malformed luau-execution-session-task response\";\n\n// Matches any of the four x-aep-resource path formats for a luau\n// execution session task.\n//\n// Capture groups:\n// 1. universeId\n// 2. placeId\n// 3. versionId (when `/versions/{v}/` segment is present, else undefined)\n// 4. sessionId (when `/luau-execution-sessions/{s}/tasks/...` branch matched)\n// 5. taskId (when `/luau-execution-sessions/.../tasks/{t}` branch matched)\n// 6. taskId (when `/luau-execution-session-tasks/{t}` branch matched)\nconst PATH_PATTERN =\n\t/^universes\\/(\\d+)\\/places\\/(\\d+)(?:\\/versions\\/(\\d+))?(?:\\/luau-execution-sessions\\/([^/]+)\\/tasks\\/([^/]+)|\\/luau-execution-session-tasks\\/([^/]+))$/;\n\ntype InProgressWireState = Exclude<LuauExecutionTaskWire[\"state\"], \"COMPLETE\" | \"FAILED\">;\n\ninterface ParseVariantArgs {\n\treadonly body: LuauExecutionTaskWire;\n\treadonly createdAt: Date | undefined;\n\treadonly ref: LuauExecutionTaskRef;\n\treadonly statusCode: number;\n\treadonly timeoutSeconds: number | undefined;\n\treadonly updatedAt: Date | undefined;\n}\n\n/**\n * Parses a successful Open Cloud `LuauExecutionSessionTask` response\n * body into the public {@link LuauExecutionTask} discriminated union.\n * Handles every supported task state (in-progress, COMPLETE, FAILED)\n * across all four x-aep-resource path shapes the server returns.\n *\n * @param response - The full {@link HttpResponse} from the Open Cloud\n * API.\n * @returns A success result wrapping the parsed task, or an\n * {@link ApiError} when the body or path do not match a supported\n * shape.\n */\nexport function parseLuauExecutionTaskResponse(\n\tresponse: HttpResponse,\n): Result<LuauExecutionTask, ApiError> {\n\tconst { body, status: statusCode } = response;\n\tif (!isLuauExecutionTaskWire(body)) {\n\t\treturn malformed(statusCode);\n\t}\n\n\tconst ref = parseTaskRef(body.path);\n\tif (ref === undefined) {\n\t\treturn malformed(statusCode);\n\t}\n\n\tconst timeoutSeconds = parseTimeoutSeconds(body.timeout);\n\tconst createdAt = parseOptionalDate(body.createTime);\n\tconst updatedAt = parseOptionalDate(body.updateTime);\n\n\tif (body.state === \"COMPLETE\") {\n\t\treturn parseCompleteTask({ body, createdAt, ref, statusCode, timeoutSeconds, updatedAt });\n\t}\n\n\tif (body.state === \"FAILED\") {\n\t\treturn parseFailedTask({ body, createdAt, ref, statusCode, timeoutSeconds, updatedAt });\n\t}\n\n\treturn parseInProgressTask({\n\t\tbody,\n\t\tcreatedAt,\n\t\tref,\n\t\tstate: body.state,\n\t\tstatusCode,\n\t\ttimeoutSeconds,\n\t\tupdatedAt,\n\t});\n}\n\nfunction isAcceptedWireState(\n\tstate: unknown,\n): state is \"CANCELLED\" | \"COMPLETE\" | \"FAILED\" | \"PROCESSING\" | \"QUEUED\" {\n\treturn (\n\t\tstate === \"QUEUED\" ||\n\t\tstate === \"PROCESSING\" ||\n\t\tstate === \"CANCELLED\" ||\n\t\tstate === \"COMPLETE\" ||\n\t\tstate === \"FAILED\"\n\t);\n}\n\nfunction isErrorWireCode(code: unknown): code is LuauExecutionTaskErrorWire[\"code\"] {\n\treturn (\n\t\tcode === \"SCRIPT_ERROR\" ||\n\t\tcode === \"DEADLINE_EXCEEDED\" ||\n\t\tcode === \"OUTPUT_SIZE_LIMIT_EXCEEDED\" ||\n\t\tcode === \"INTERNAL_ERROR\"\n\t);\n}\n\nfunction isErrorWire(value: unknown): value is LuauExecutionTaskErrorWire {\n\treturn (\n\t\tisRecord(value) && isErrorWireCode(value[\"code\"]) && typeof value[\"message\"] === \"string\"\n\t);\n}\n\nfunction isOptionalErrorWire(value: unknown): value is LuauExecutionTaskErrorWire | undefined {\n\treturn value === undefined || isErrorWire(value);\n}\n\nfunction isOutputWire(value: unknown): value is LuauExecutionTaskOutputWire {\n\treturn isRecord(value) && Array.isArray(value[\"results\"]);\n}\n\nfunction isOptionalOutputWire(value: unknown): value is LuauExecutionTaskOutputWire | undefined {\n\treturn value === undefined || isOutputWire(value);\n}\n\nfunction isOptionalString(value: unknown): value is string | undefined {\n\treturn value === undefined || typeof value === \"string\";\n}\n\nfunction isOptionalBoolean(value: unknown): value is boolean | undefined {\n\treturn value === undefined || typeof value === \"boolean\";\n}\n\nfunction isOptionalDateTimeString(value: unknown): value is string | undefined {\n\treturn value === undefined || isDateTimeString(value);\n}\n\nfunction isLuauExecutionTaskWire(body: unknown): body is LuauExecutionTaskWire {\n\treturn (\n\t\tisRecord(body) &&\n\t\ttypeof body[\"path\"] === \"string\" &&\n\t\tisOptionalDateTimeString(body[\"createTime\"]) &&\n\t\tisOptionalDateTimeString(body[\"updateTime\"]) &&\n\t\tisAcceptedWireState(body[\"state\"]) &&\n\t\ttypeof body[\"user\"] === \"string\" &&\n\t\tisOptionalOutputWire(body[\"output\"]) &&\n\t\tisOptionalErrorWire(body[\"error\"]) &&\n\t\tisOptionalDurationWire(body[\"timeout\"]) &&\n\t\tisOptionalString(body[\"binaryInput\"]) &&\n\t\tisOptionalBoolean(body[\"enableBinaryOutput\"]) &&\n\t\tisOptionalString(body[\"binaryOutputUri\"])\n\t);\n}\n\nfunction parseOptionalDate(value: string | undefined): Date | undefined {\n\treturn value === undefined ? undefined : new Date(value);\n}\n\nconst DURATION_PATTERN = /^(\\d+)s$/;\n\nfunction isOptionalDurationWire(value: unknown): value is string | undefined {\n\treturn value === undefined || (typeof value === \"string\" && DURATION_PATTERN.test(value));\n}\n\nfunction parseTimeoutSeconds(value: string | undefined): number | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst match = DURATION_PATTERN.exec(value);\n\tconst seconds = match?.[1];\n\tif (seconds === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn Number.parseInt(seconds, 10);\n}\n\nfunction malformed(statusCode: number): Result<LuauExecutionTask, ApiError> {\n\treturn { err: new ApiError(MALFORMED_TASK_MESSAGE, { statusCode }), success: false };\n}\n\nfunction parseInProgressTask(\n\targs: ParseVariantArgs & { readonly state: InProgressWireState },\n): Result<LuauExecutionTask, ApiError> {\n\tconst { body, createdAt, ref, state, timeoutSeconds, updatedAt } = args;\n\treturn {\n\t\tdata: {\n\t\t\tbinaryInput: body.binaryInput,\n\t\t\tbinaryOutputUri: body.binaryOutputUri,\n\t\t\tcreatedAt,\n\t\t\tenableBinaryOutput: body.enableBinaryOutput,\n\t\t\tref,\n\t\t\tstate,\n\t\t\ttimeoutSeconds,\n\t\t\tupdatedAt,\n\t\t\tuser: body.user,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction parseCompleteTask(args: ParseVariantArgs): Result<LuauExecutionTask, ApiError> {\n\tconst { body, createdAt, ref, statusCode, timeoutSeconds, updatedAt } = args;\n\tif (body.output === undefined) {\n\t\treturn malformed(statusCode);\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tbinaryInput: body.binaryInput,\n\t\t\tbinaryOutputUri: body.binaryOutputUri,\n\t\t\tcreatedAt,\n\t\t\tenableBinaryOutput: body.enableBinaryOutput,\n\t\t\toutput: { results: body.output.results },\n\t\t\tref,\n\t\t\tstate: \"COMPLETE\",\n\t\t\ttimeoutSeconds,\n\t\t\tupdatedAt,\n\t\t\tuser: body.user,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction parseFailedTask(args: ParseVariantArgs): Result<LuauExecutionTask, ApiError> {\n\tconst { body, createdAt, ref, statusCode, timeoutSeconds, updatedAt } = args;\n\tif (body.error === undefined) {\n\t\treturn malformed(statusCode);\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tbinaryInput: body.binaryInput,\n\t\t\tbinaryOutputUri: body.binaryOutputUri,\n\t\t\tcreatedAt,\n\t\t\tenableBinaryOutput: body.enableBinaryOutput,\n\t\t\terror: { code: body.error.code, message: body.error.message },\n\t\t\tref,\n\t\t\tstate: \"FAILED\",\n\t\t\ttimeoutSeconds,\n\t\t\tupdatedAt,\n\t\t\tuser: body.user,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction parseTaskRef(path: string): LuauExecutionTaskRef | undefined {\n\tconst match = PATH_PATTERN.exec(path);\n\tif (match === null) {\n\t\treturn undefined;\n\t}\n\n\tconst [, universeId, placeId, versionId, sessionId, sessionTaskId, plainTaskId] = match;\n\tconst taskId = sessionTaskId ?? plainTaskId;\n\tif (universeId === undefined || placeId === undefined || taskId === undefined) {\n\t\treturn undefined;\n\t}\n\n\treturn { placeId, sessionId, taskId, universeId, versionId };\n}\n","import {\n\tCREATE_METHOD_DEFAULTS,\n\tIDEMPOTENT_METHOD_DEFAULTS,\n} from \"../../../internal/http/retry.ts\";\nimport { okRequest, type ResourceMethodSpec } from \"../../../internal/resource-client.ts\";\nimport {\n\tbuildGetRequest,\n\tbuildSubmitAtHeadRequest,\n\tbuildSubmitAtVersionRequest,\n} from \"./builders.ts\";\nimport {\n\tGET_OPERATION_LIMIT,\n\tGET_REQUIRED_SCOPES,\n\tSUBMIT_OPERATION_LIMIT,\n\tSUBMIT_REQUIRED_SCOPES,\n} from \"./operations.ts\";\nimport { parseLuauExecutionTaskResponse } from \"./parsers.ts\";\nimport type {\n\tGetParameters,\n\tLuauExecutionTask,\n\tSubmitAtHeadParameters,\n\tSubmitAtVersionParameters,\n} from \"./types.ts\";\n\nfunction makeSpec<P>(\n\tspec: ResourceMethodSpec<P, LuauExecutionTask>,\n): ResourceMethodSpec<P, LuauExecutionTask> {\n\treturn Object.freeze(spec);\n}\n\n/**\n * Per-method dispatch spec for submitting a Luau execution task at a\n * place's head version. Frozen at module scope so both the top-level\n * `LuauExecutionClient` and the `luauExecution` Operation Group on\n * `PlacesClient` share the same instance reference.\n */\nexport const SUBMIT_HEAD_SPEC = makeSpec<SubmitAtHeadParameters>({\n\tbuildRequest: (parameters) => okRequest(buildSubmitAtHeadRequest(parameters)),\n\tmethodDefaults: CREATE_METHOD_DEFAULTS,\n\tmethodKind: \"create\",\n\toperationLimit: SUBMIT_OPERATION_LIMIT,\n\tparse: parseLuauExecutionTaskResponse,\n\trequiredScopes: SUBMIT_REQUIRED_SCOPES,\n});\n\n/**\n * Per-method dispatch spec for submitting a Luau execution task at a\n * specific place version. Shares the rate-limit queue and required\n * scope set with {@link SUBMIT_HEAD_SPEC} because Roblox attributes\n * both URL shapes to one per-minute quota.\n */\nexport const SUBMIT_VERSION_SPEC = makeSpec<SubmitAtVersionParameters>({\n\tbuildRequest: (parameters) => okRequest(buildSubmitAtVersionRequest(parameters)),\n\tmethodDefaults: CREATE_METHOD_DEFAULTS,\n\tmethodKind: \"create\",\n\toperationLimit: SUBMIT_OPERATION_LIMIT,\n\tparse: parseLuauExecutionTaskResponse,\n\trequiredScopes: SUBMIT_REQUIRED_SCOPES,\n});\n\n/**\n * Per-method dispatch spec for fetching a Luau execution task. Uses\n * idempotent retry semantics (429 and 5xx both retried) so reads\n * recover transparently from transient server errors.\n */\nexport const GET_SPEC = makeSpec<GetParameters>({\n\tbuildRequest: buildGetRequest,\n\tmethodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n\tmethodKind: \"idempotent\",\n\toperationLimit: GET_OPERATION_LIMIT,\n\tparse: parseLuauExecutionTaskResponse,\n\trequiredScopes: GET_REQUIRED_SCOPES,\n});\n","import type { RequestOptions } from \"../../client/types.ts\";\nimport type { LuauExecutionTask } from \"../../domains/cloud-v2/luau-execution-tasks/types.ts\";\nimport type { OpenCloudError } from \"../../errors/base.ts\";\nimport { NetworkError } from \"../../errors/network-error.ts\";\nimport { PollAbortedError } from \"../../errors/poll-aborted.ts\";\nimport { PollTimeoutError } from \"../../errors/poll-timeout.ts\";\nimport { TRANSIENT_TRANSPORT_CODES } from \"../../internal/http/retry.ts\";\nimport { findErrorCode } from \"../../internal/utils/find-error-code.ts\";\nimport type { SleepFunc } from \"../../internal/utils/sleep.ts\";\nimport type { Result } from \"../../types.ts\";\n\n/** Default total polling budget in milliseconds (5 minutes). */\nexport const DEFAULT_POLL_TIMEOUT_MS = 300_000;\n\n/** One step of the default poll-cadence schedule. */\ninterface PollDelayTier {\n\t/** Delay in ms to wait between polls while within this tier. */\n\treadonly delayMs: number;\n\t/** Upper elapsed-time bound (exclusive) at which this tier stops applying. */\n\treadonly untilMs: number;\n}\n\n/** Steady-state delay once elapsed time reaches the final tier bound. */\nconst STEADY_POLL_DELAY_MS = 5_000;\n\n/**\n * Fast-to-slow poll-cadence tiers keyed on elapsed wall-clock time. Elapsed\n * times at or beyond the last `untilMs` fall through to\n * {@link STEADY_POLL_DELAY_MS}.\n */\nconst DEFAULT_POLL_TIERS: ReadonlyArray<PollDelayTier> = [\n\t{ delayMs: 500, untilMs: 20_000 },\n\t{ delayMs: 1_000, untilMs: 60_000 },\n];\n\n/**\n * Default poll cadence as a function of elapsed wall-clock time since\n * polling began. Polls quickly while a task is young so short runs resolve\n * snappily, then eases off so a long run leaves rate-limit headroom for\n * newer tasks: 0-20s is 500ms, 20-60s is 1000ms, 60s+ is 5000ms.\n *\n * @since 0.1.0\n *\n * @example\n * ```ts\n * import { defaultPollDelay } from \"@bedrock-rbx/ocale/luau-execution\";\n *\n * expect(defaultPollDelay(0)).toBe(500);\n * expect(defaultPollDelay(30_000)).toBe(1000);\n * expect(defaultPollDelay(120_000)).toBe(5000);\n * ```\n *\n * @param elapsedMs - Milliseconds elapsed since polling started.\n * @returns The delay in milliseconds to wait before the next poll.\n */\nexport function defaultPollDelay(elapsedMs: number): number {\n\tconst tier = DEFAULT_POLL_TIERS.find((candidate) => elapsedMs < candidate.untilMs);\n\treturn tier?.delayMs ?? STEADY_POLL_DELAY_MS;\n}\n\n/**\n * Default number of consecutive transport failures tolerated before the poll\n * loop gives up. With per-request retries already absorbing isolated blips,\n * three consecutive loop-level failures signals a genuinely unreachable\n * endpoint, so it bails in seconds rather than spinning out the wall-clock budget.\n */\nexport const DEFAULT_POLL_FAILURE_CAP = 3;\n\n/**\n * Injected dependencies for the deep-module polling loop. The `fetch`\n * callback is pre-bound by the wiring layer and closes over the task ref\n * and request options, keeping the core loop narrow.\n */\nexport interface PollDeps {\n\t/** Returns the current task or an error. Called on each loop iteration. */\n\treadonly fetch: () => Promise<Result<LuauExecutionTask, OpenCloudError>>;\n\t/** Returns the current wall-clock time in ms. */\n\treadonly now: () => number;\n\t/** Injectable sleep for deterministic tests. */\n\treadonly sleep: SleepFunc;\n}\n\n/**\n * Public options accepted by `pollUntilDone` and `runUntilDone` on both client surfaces.\n *\n * @since 0.1.0\n */\nexport type PollUntilDoneOptions = PollOptions & RequestOptions;\n\n/** Caller-supplied polling-loop options; all fields optional. */\ninterface PollOptions {\n\t/**\n\t * Consecutive transient transport failures tolerated before the loop gives\n\t * up. Defaults to {@link DEFAULT_POLL_FAILURE_CAP}. A successful poll resets\n\t * the count.\n\t */\n\treadonly maxConsecutivePollFailures?: number;\n\t/** Returns the sleep duration given ms elapsed since polling started. Defaults to {@link defaultPollDelay}. */\n\treadonly pollDelay?: (elapsedMs: number) => number;\n\t/** When aborted, the loop returns {@link PollAbortedError} rather than continuing. */\n\treadonly signal?: AbortSignal;\n\t/** Total wall-clock budget in ms before the loop returns {@link PollTimeoutError}. */\n\treadonly timeoutMs?: number;\n}\n\n/**\n * Defaults the per-request `timeout` to the effective poll budget when the\n * caller has not set one. A luau-execution submit and each poll `get` normally\n * answer in well under a second (the submit endpoint enqueues the task without\n * waiting for it to run), so the only job of a per-request deadline here is to\n * bound a black-hole connection. Leaving these requests on the client-wide 30s\n * default (tuned for snappy CRUD) turns a slow-but-alive backend into a\n * self-abort, an error the retry layer never retries by construction, before\n * the loop's wall-clock budget is ever consulted. Deriving the deadline from\n * `timeoutMs` keeps a single request alive exactly as long as the caller\n * already agreed to wait for the whole operation, so the backend can answer or\n * surface a retryable status instead.\n *\n * @param options - The caller's poll and per-request options.\n * @returns The options with `timeout` filled from the budget when it was unset.\n */\nexport function withBudgetRequestTimeout(options: PollUntilDoneOptions): PollUntilDoneOptions {\n\tif (options.timeout !== undefined) {\n\t\treturn options;\n\t}\n\n\treturn { ...options, timeout: options.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS };\n}\n\nconst ABORTED = Symbol(\"poll-aborted\");\ntype Aborted = typeof ABORTED;\n\ninterface AbortObserver {\n\treadonly cleanup: () => void;\n\treadonly promise: Promise<Aborted>;\n}\n\ninterface SleepWithAbortOptions {\n\treadonly ms: number;\n\treadonly signal: AbortSignal | undefined;\n\treadonly sleep: SleepFunc;\n}\n\ntype FetchOutcome =\n\t| { readonly error: NetworkError; readonly kind: \"transient\" }\n\t| { readonly error: OpenCloudError; readonly kind: \"failed\" }\n\t| { readonly kind: \"aborted\" }\n\t| { readonly kind: \"pending\"; readonly task: LuauExecutionTask }\n\t| { readonly kind: \"terminal\"; readonly task: LuauExecutionTask };\n\n/** Mutable-per-iteration loop state threaded through {@link applyOutcome}. */\ninterface LoopState {\n\treadonly consecutiveFailures: number;\n\treadonly lastTask: LuauExecutionTask | undefined;\n}\n\ntype LoopAction =\n\t| { readonly kind: \"continue\"; readonly state: LoopState }\n\t| { readonly kind: \"return\"; readonly result: Result<LuauExecutionTask, OpenCloudError> };\n\n/** Per-iteration inputs to {@link applyOutcome}. */\ninterface OutcomeContext {\n\treadonly maxFailures: number;\n\treadonly signal: AbortSignal | undefined;\n\treadonly state: LoopState;\n}\n\n/**\n * Core polling loop. Calls `deps.fetch()` repeatedly, sleeping\n * `pollDelay(elapsedMs)` ms between iterations, until a terminal state\n * is observed, the wall-clock budget is exhausted, or an `AbortSignal`\n * fires. A transient transport failure ({@link NetworkError}) is tolerated\n * and the loop continues, giving up only after `maxConsecutivePollFailures`\n * consecutive failures; any other failure aborts immediately, since an API\n * response (a 404 for a vanished task, a 403) means there is nothing left to\n * poll. A successful poll resets the failure count.\n *\n * @param deps - Injected fetch, now, and sleep callbacks.\n * @param options - Optional poll delay, timeout, failure cap, and abort signal.\n * @returns The terminal task, or an error if aborted, timed out, or the transport keeps failing.\n */\nexport async function pollUntilDoneCore(\n\tdeps: PollDeps,\n\toptions: PollOptions = {},\n): Promise<Result<LuauExecutionTask, OpenCloudError>> {\n\tconst timeoutMs = options.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;\n\tconst pollDelay = options.pollDelay ?? defaultPollDelay;\n\tconst maxFailures = options.maxConsecutivePollFailures ?? DEFAULT_POLL_FAILURE_CAP;\n\tconst sig = options.signal;\n\tconst startedAt = deps.now();\n\tif (sig?.aborted === true) {\n\t\treturn abortedResult(sig);\n\t}\n\n\tlet state: LoopState = { consecutiveFailures: 0, lastTask: undefined };\n\tfor (;;) {\n\t\tconst elapsedMs = deps.now() - startedAt;\n\t\tif (elapsedMs >= timeoutMs) {\n\t\t\treturn { err: makeTimeout(state.lastTask, timeoutMs), success: false };\n\t\t}\n\n\t\tconst outcome = await fetchOnce(deps, sig);\n\t\tconst action = applyOutcome(outcome, { maxFailures, signal: sig, state });\n\t\tif (action.kind === \"return\") {\n\t\t\treturn action.result;\n\t\t}\n\n\t\t({ state } = action);\n\t\tif (await sleepWithAbort({ ms: pollDelay(elapsedMs), signal: sig, sleep: deps.sleep })) {\n\t\t\treturn abortedResult(sig);\n\t\t}\n\t}\n}\n\nfunction makeAborted(signal: AbortSignal | undefined): PollAbortedError {\n\treturn new PollAbortedError(\"Polling was aborted\", { reason: signal?.reason });\n}\n\nfunction abortedResult(signal: AbortSignal | undefined): Result<LuauExecutionTask, OpenCloudError> {\n\treturn { err: makeAborted(signal), success: false };\n}\n\n/**\n * Maps a single fetch outcome to the next loop action. Terminal, failed, and\n * aborted outcomes return immediately; a transient transport failure advances\n * the consecutive-failure count and returns once it reaches `maxFailures`; a\n * pending task resets the count and continues.\n *\n * @param outcome - The classified result of one poll fetch.\n * @param context - The loop state, failure cap, and abort signal.\n * @returns Whether to return a final Result or continue with updated state.\n */\nfunction applyOutcome(outcome: FetchOutcome, context: OutcomeContext): LoopAction {\n\tconst { maxFailures, signal, state } = context;\n\tswitch (outcome.kind) {\n\t\tcase \"aborted\": {\n\t\t\treturn { kind: \"return\", result: abortedResult(signal) };\n\t\t}\n\t\tcase \"failed\": {\n\t\t\treturn { kind: \"return\", result: { err: outcome.error, success: false } };\n\t\t}\n\t\tcase \"pending\": {\n\t\t\treturn { kind: \"continue\", state: { consecutiveFailures: 0, lastTask: outcome.task } };\n\t\t}\n\t\tcase \"terminal\": {\n\t\t\treturn { kind: \"return\", result: { data: outcome.task, success: true } };\n\t\t}\n\t\tcase \"transient\": {\n\t\t\tconst consecutiveFailures = state.consecutiveFailures + 1;\n\t\t\tif (consecutiveFailures >= maxFailures) {\n\t\t\t\treturn { kind: \"return\", result: { err: outcome.error, success: false } };\n\t\t\t}\n\n\t\t\treturn { kind: \"continue\", state: { consecutiveFailures, lastTask: state.lastTask } };\n\t\t}\n\t}\n}\n\nfunction abortObserver(signal: AbortSignal): AbortObserver {\n\tconst { promise, resolve } = Promise.withResolvers<Aborted>();\n\tfunction onAbort(): void {\n\t\tresolve(ABORTED);\n\t}\n\n\tsignal.addEventListener(\"abort\", onAbort);\n\tfunction cleanup(): void {\n\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t}\n\n\treturn { cleanup, promise };\n}\n\nasync function raceWithAbort<T>(\n\tpromise: Promise<T>,\n\tsignal: AbortSignal | undefined,\n): Promise<Aborted | T> {\n\tif (signal === undefined) {\n\t\treturn promise;\n\t}\n\n\tif (signal.aborted) {\n\t\treturn ABORTED;\n\t}\n\n\tconst observer = abortObserver(signal);\n\ttry {\n\t\treturn await Promise.race([promise, observer.promise]);\n\t} finally {\n\t\tobserver.cleanup();\n\t}\n}\n\nasync function sleepWithAbort(options: SleepWithAbortOptions): Promise<boolean> {\n\tconst { ms, signal, sleep } = options;\n\tconst raced = await raceWithAbort(sleep(ms), signal);\n\treturn raced === ABORTED;\n}\n\nfunction makeTimeout(\n\ttask: LuauExecutionTask | undefined,\n\ttimeoutMs: number,\n): PollTimeoutError<LuauExecutionTask> {\n\treturn new PollTimeoutError(`Polling timed out after ${timeoutMs} ms`, {\n\t\tlastObservedTask: task,\n\t\ttimeoutMs,\n\t});\n}\n\nfunction isTerminal(task: LuauExecutionTask): boolean {\n\treturn task.state === \"COMPLETE\" || task.state === \"FAILED\" || task.state === \"CANCELLED\";\n}\n\n/**\n * A failed poll is worth re-polling only when it is a `NetworkError` carrying a\n * known transient transport code. A self-aborted request timeout has no\n * `code`, and an API response (4xx/5xx) is authoritative, so both abort the\n * loop rather than being re-polled. Transient-ness is classified against the\n * canonical `TRANSIENT_TRANSPORT_CODES` set; this is the loop's own tolerance\n * dimension, distinct from the per-request `retryableTransportCodes` override\n * (which governs request-level retries inside each poll). Loop tolerance is\n * bounded separately by `maxConsecutivePollFailures`.\n *\n * @param error - The error returned by a failed poll.\n * @returns `true` when the loop should tolerate and re-poll.\n */\nfunction isTransientTransport(error: OpenCloudError): error is NetworkError {\n\tif (!(error instanceof NetworkError)) {\n\t\treturn false;\n\t}\n\n\tconst code = findErrorCode(error);\n\treturn code !== undefined && TRANSIENT_TRANSPORT_CODES.includes(code);\n}\n\nasync function fetchOnce(deps: PollDeps, signal: AbortSignal | undefined): Promise<FetchOutcome> {\n\tconst fetchResult = await raceWithAbort(deps.fetch(), signal);\n\tif (fetchResult === ABORTED) {\n\t\treturn { kind: \"aborted\" };\n\t}\n\n\tif (!fetchResult.success) {\n\t\treturn isTransientTransport(fetchResult.err)\n\t\t\t? { error: fetchResult.err, kind: \"transient\" }\n\t\t\t: { error: fetchResult.err, kind: \"failed\" };\n\t}\n\n\treturn isTerminal(fetchResult.data)\n\t\t? { kind: \"terminal\", task: fetchResult.data }\n\t\t: { kind: \"pending\", task: fetchResult.data };\n}\n","import {\n\tGET_SPEC,\n\tSUBMIT_HEAD_SPEC,\n\tSUBMIT_VERSION_SPEC,\n} from \"../../domains/cloud-v2/luau-execution-tasks/specs.ts\";\nimport type {\n\tLuauExecutionTask,\n\tLuauExecutionTaskRef,\n\tSubmitAtHeadParameters,\n\tSubmitAtVersionParameters,\n} from \"../../domains/cloud-v2/luau-execution-tasks/types.ts\";\nimport type { OpenCloudError } from \"../../errors/base.ts\";\nimport type { ResourceClient } from \"../../internal/resource-client.ts\";\nimport type { Result } from \"../../types.ts\";\nimport { type PollDeps, pollUntilDoneCore, type PollUntilDoneOptions } from \"./polling.ts\";\n\n/**\n * Builds the {@link PollDeps} bundle used by {@link pollUntilDoneCore},\n * closing over the supplied {@link ResourceClient}, task ref, and\n * per-request options so the core loop stays narrow.\n *\n * @param inner - The {@link ResourceClient} that issues each `tasks.get` call.\n * @param args - The polling options and the task ref to fetch on every iteration.\n * @returns A {@link PollDeps} bundle wiring `fetch`, `now`, and `sleep`.\n */\nexport function buildPollDeps(\n\tinner: ResourceClient,\n\targs: { options: PollUntilDoneOptions; ref: LuauExecutionTaskRef },\n): PollDeps {\n\treturn {\n\t\tfetch: async () => {\n\t\t\treturn inner.execute({\n\t\t\t\toptions: args.options,\n\t\t\t\tparameters: { ref: args.ref, view: \"BASIC\" },\n\t\t\t\tspec: GET_SPEC,\n\t\t\t});\n\t\t},\n\t\tnow: Date.now,\n\t\tsleep: inner.sleep,\n\t};\n}\n\n/**\n * Submits a Luau execution task and polls it to a terminal state.\n * Dispatches to the head-version or specific-version submit spec based on\n * the presence of `versionId`, then delegates to {@link pollUntilDoneCore}.\n *\n * @param inner - The {@link ResourceClient} that issues submit and poll calls.\n * @param args - The polling options and submit parameters.\n * @returns A {@link Result} wrapping the terminal {@link LuauExecutionTask}, or\n * the {@link OpenCloudError} that caused submit or polling to fail.\n */\nexport async function submitAndPoll(\n\tinner: ResourceClient,\n\targs: {\n\t\toptions: PollUntilDoneOptions;\n\t\tparameters: SubmitAtHeadParameters | SubmitAtVersionParameters;\n\t},\n): Promise<Result<LuauExecutionTask, OpenCloudError>> {\n\tconst { options, parameters } = args;\n\tconst submitResult = await (\"versionId\" in parameters\n\t\t? inner.execute({ options, parameters, spec: SUBMIT_VERSION_SPEC })\n\t\t: inner.execute({ options, parameters, spec: SUBMIT_HEAD_SPEC }));\n\tif (!submitResult.success) {\n\t\treturn submitResult;\n\t}\n\n\treturn pollUntilDoneCore(\n\t\tbuildPollDeps(inner, { options, ref: submitResult.data.ref }),\n\t\toptions,\n\t);\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"resource-client-lE7Tg3BK.mjs","names":["#window","#lastAllowedAt","#chains","#sleep","#trackers","#gateOnce","#tracker","#hooks","#intervalMs","#maxBucketLevel","#sleep","#chain","#waitForToken","#lastCheck","#bucketLevel","#budgets","#config","#hooks","#httpClient","#queues","#sleep","#getQueue","#gatedSend"],"sources":["../src/internal/utils/is-date-time-string.ts","../src/internal/utils/is-record.ts","../src/internal/http/budget-tracker.ts","../src/internal/http/budget-gate.ts","../src/internal/http/execute.ts","../src/internal/http/rate-limit-sample.ts","../src/internal/http/rate-limit-observation.ts","../src/internal/http/rate-limit-queue.ts","../src/internal/utils/try-catch.ts","../src/internal/http/fetch-client.ts","../src/internal/http/resolve-dependencies.ts","../src/internal/http/upload-request.ts","../src/internal/resource-client.ts"],"sourcesContent":["/**\n * Narrows `value` to a string that parses to a real {@link Date} via the\n * `Date(string)` constructor. Used by resource parsers to gate\n * `format: date-time` wire fields before handing them to `new Date(...)`,\n * which silently produces an `Invalid Date` for invalid input.\n *\n * @param value - The unknown wire value to validate.\n * @returns `true` when `value` is a string and `new Date(value).getTime()`\n * is not `NaN`.\n */\nexport function isDateTimeString(value: unknown): value is string {\n\tif (typeof value !== \"string\") {\n\t\treturn false;\n\t}\n\n\treturn !Number.isNaN(new Date(value).getTime());\n}\n","/**\n * Narrows `value` to a plain JSON-style record. Excludes arrays, class\n * instances, primitives, and `null`/`undefined`. Used by resource\n * parsers to gate property access on wire bodies whose shape isn't\n * known at compile time.\n *\n * @param value - The unknown value to narrow.\n * @returns `true` when `value` is a plain `[object Object]`.\n */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n\treturn Object.prototype.toString.call(value) === \"[object Object]\";\n}\n","import type { RateLimitSample } from \"./rate-limit-sample.ts\";\n\nconst MS_PER_SECOND = 1000;\n\n/** Live window state for one scope: budget left and when it resets. */\ninterface WindowState {\n\t/** Best estimate of requests still allowed before the window resets. */\n\treadonly predictedRemaining: number;\n\t/** Absolute time (ms) the window resets to full. */\n\treadonly resetAt: number;\n}\n\n/**\n * Tracks the live rate-limit budget for a single scope. Primed by `observe`\n * from response headers and drawn down by `reserve` as requests leave, so\n * `waitMs` can pace requests across the window.\n *\n * Pacing has two regimes. While budget remains, requests are spread evenly over\n * the time left in the window (`timeLeft / remaining`), so a burst does not\n * spend the whole window's budget up front and then stall. Once the budget is\n * spent, requests hold until the window resets. Budget and reset time move\n * together as one window, so the tracker is either unprimed or fully primed,\n * never half-known.\n */\nexport class BudgetTracker {\n\t/** Time (ms) the most recent request was allowed out, for spacing. */\n\t#lastAllowedAt: number | undefined = undefined;\n\t#window: undefined | WindowState = undefined;\n\n\t/**\n\t * Folds a fresh server reading in, replacing any prior window. The latest\n\t * reading wins: observe time is monotonic, so the most recently resolved\n\t * response is the best current estimate. The spacing reference is left\n\t * untouched so a window refresh does not reset pacing mid-stream.\n\t *\n\t * @param sample - Parsed `remaining`/`resetSeconds` from a response.\n\t * @param now - The current time in ms.\n\t */\n\tpublic observe(sample: RateLimitSample, now: number): void {\n\t\tthis.#window = {\n\t\t\tpredictedRemaining: sample.remaining,\n\t\t\tresetAt: now + sample.resetSeconds * MS_PER_SECOND,\n\t\t};\n\t}\n\n\t/**\n\t * Accounts for one request leaving at `now`: records the spacing reference\n\t * and decrements the prediction. A no-op on the prediction while unprimed.\n\t *\n\t * @param now - The time the request was allowed out, in ms.\n\t */\n\tpublic reserve(now: number): void {\n\t\tthis.#lastAllowedAt = now;\n\t\tif (this.#window !== undefined) {\n\t\t\tthis.#window = {\n\t\t\t\t...this.#window,\n\t\t\t\tpredictedRemaining: this.#window.predictedRemaining - 1,\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Milliseconds to wait before the next request is allowed.\n\t *\n\t * @param now - The current time in ms.\n\t * @returns `0` when a request may go now (unprimed, or the first paced send);\n\t * the time until reset when the budget is spent; otherwise the time until\n\t * this request's evenly-spaced slot.\n\t */\n\tpublic waitMs(now: number): number {\n\t\tif (this.#window === undefined) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tconst { predictedRemaining, resetAt } = this.#window;\n\t\tif (predictedRemaining <= 0) {\n\t\t\treturn Math.max(0, resetAt - now);\n\t\t}\n\n\t\tif (this.#lastAllowedAt === undefined) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tconst interval = (resetAt - now) / predictedRemaining;\n\t\treturn Math.max(0, this.#lastAllowedAt + interval - now);\n\t}\n}\n","import type { SleepFunc } from \"../utils/sleep.ts\";\nimport { BudgetTracker } from \"./budget-tracker.ts\";\nimport type { RateLimitSample } from \"./rate-limit-sample.ts\";\n\n/**\n * Header-primed rate-limit gate shared across a client. Holds one\n * {@link BudgetTracker} per API key, since the tightest Roblox window is the\n * per-key one shared across every operation. Before each request the caller\n * gates on the request's key (sleeping if its budget is spent), and after each\n * response folds the parsed sample back in, so a sibling operation on the same\n * key can head off a 429 the static per-operation token bucket cannot foresee.\n * A per-operation tracker is deliberately not kept: every operation reports the\n * same most-constrained `remaining`, so a per-key tracker (drawn down by all\n * operations) is always the binding constraint.\n *\n * Gating is serialized per scope through a promise chain so concurrent\n * requests on one key cannot read the same budget and reserve the same slot;\n * each waits for the prior gate's reserve before computing its own.\n */\nexport class BudgetGate {\n\treadonly #chains = new Map<string, Promise<void>>();\n\treadonly #sleep: SleepFunc;\n\treadonly #trackers = new Map<string, BudgetTracker>();\n\n\t/**\n\t * Creates a gate bound to an injectable sleep.\n\t *\n\t * @param sleep - Injectable sleep (tests pass a fake clock).\n\t */\n\tconstructor(sleep: SleepFunc) {\n\t\tthis.#sleep = sleep;\n\t}\n\n\t/**\n\t * Holds until the scope's budget permits a send, then reserves one slot.\n\t * Runs after the prior gate on the same scope settles, whether it resolved\n\t * or rejected, so one failed attempt cannot poison later gates on the key.\n\t *\n\t * @param scope - The scope key to gate on (the effective API key).\n\t */\n\tpublic async gate(scope: string): Promise<void> {\n\t\tconst previous = this.#chains.get(scope) ?? Promise.resolve();\n\t\tconst runGate = async (): Promise<void> => this.#gateOnce(scope);\n\t\tconst mine = previous.then(runGate, runGate);\n\t\tthis.#chains.set(scope, mine);\n\t\tawait mine;\n\t}\n\n\t/**\n\t * Folds a response's parsed budget back onto the scope. A `undefined`\n\t * sample (headers absent or non-numeric) is ignored, leaving the scope on\n\t * static pacing.\n\t *\n\t * @param scope - The same scope key passed to {@link gate}.\n\t * @param sample - Parsed sample, or `undefined` when none was reported.\n\t */\n\tpublic observe(scope: string, sample: RateLimitSample | undefined): void {\n\t\tif (sample === undefined) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#tracker(scope).observe(sample, Date.now());\n\t}\n\n\tasync #gateOnce(scope: string): Promise<void> {\n\t\tconst tracker = this.#tracker(scope);\n\t\tconst waitMs = tracker.waitMs(Date.now());\n\t\tif (waitMs > 0) {\n\t\t\tawait this.#sleep(waitMs);\n\t\t}\n\n\t\ttracker.reserve(Date.now());\n\t}\n\n\t#tracker(scope: string): BudgetTracker {\n\t\tconst existing = this.#trackers.get(scope);\n\t\tif (existing !== undefined) {\n\t\t\treturn existing;\n\t\t}\n\n\t\tconst tracker = new BudgetTracker();\n\t\tthis.#trackers.set(scope, tracker);\n\t\treturn tracker;\n\t}\n}\n","import type { OpenCloudError } from \"../../errors/base.ts\";\nimport type { Result } from \"../../types.ts\";\nimport type { SleepFunc } from \"../utils/sleep.ts\";\nimport { computeRetryWaitMs, type RetryResolvable, shouldRetry } from \"./retry.ts\";\nimport type { HttpRequest, HttpResponse, OpenCloudHooks } from \"./types.ts\";\n\n/** A transport callback: takes a request, returns a classified Result. */\ntype SendFunc = (request: HttpRequest) => Promise<Result<HttpResponse, OpenCloudError>>;\n\n/**\n * Inputs to {@link executeWithRetry} bundled as an options object to keep the\n * function signature narrow.\n */\ninterface ExecuteOptions {\n\t/** Fully-resolved retry config (post-merge). */\n\treadonly config: RetryResolvable;\n\t/** Client-level observability hooks. */\n\treadonly hooks: OpenCloudHooks;\n\t/** Transport callback. May be pre-wrapped by a rate-limit queue. */\n\treadonly send: SendFunc;\n\t/** Injectable sleep (tests pass a fake). */\n\treadonly sleep: SleepFunc;\n}\n\n/**\n * Retry-aware orchestration loop. Coordinates a single logical request,\n * looping over `options.send` until it succeeds, the error is non-retryable,\n * or `options.config.maxRetries` is exhausted. Fires observability hooks\n * at each transition. Domain- and queue-agnostic: `send` may be any\n * callback, including one wrapped by a rate-limit queue.\n *\n * @param request - The immutable request to send.\n * @param options - The transport callback, resolved config, hooks, and sleep.\n * @returns The first success, or the final error after retries are exhausted.\n */\nexport async function executeWithRetry(\n\trequest: HttpRequest,\n\toptions: ExecuteOptions,\n): Promise<Result<HttpResponse, OpenCloudError>> {\n\tconst { config, hooks, send, sleep } = options;\n\n\tasync function attempt(): Promise<Result<HttpResponse, OpenCloudError>> {\n\t\thooks.onRequest?.(request);\n\t\treturn send(request);\n\t}\n\n\tlet result = await attempt();\n\n\tfor (let retry = 0; retry < config.maxRetries; retry++) {\n\t\tif (result.success || !shouldRetry(result.err, config)) {\n\t\t\treturn result;\n\t\t}\n\n\t\tconst { err } = result;\n\t\thooks.onRetry?.(retry + 1, err);\n\t\tconst waitMs = computeRetryWaitMs(err, { attempt: retry, retryDelay: config.retryDelay });\n\t\thooks.onRateLimit?.(waitMs);\n\t\tawait sleep(waitMs);\n\n\t\tresult = await attempt();\n\t}\n\n\treturn result;\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/** Requests still allowed in the current window (the most-constrained one). */\n\treadonly remaining: number;\n\t/** Seconds until the most-constrained window resets to full. */\n\treadonly resetSeconds: number;\n}\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 and\n * non-finite tokens (`\"\"`, `\"Infinity\"`, `\"abc\"`) are dropped so a stray value\n * cannot corrupt the result. Returns `undefined` when the header is absent or\n * has no finite 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, floored, clamped 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) => part !== \"\")\n\t\t.map((part) => Number(part))\n\t\t.filter((value) => Number.isFinite(value));\n\tif (tokens.length === 0) {\n\t\treturn undefined;\n\t}\n\n\treturn Math.max(0, Math.floor(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), symmetric to how a 429's\n * retry delay is reduced. Returns `undefined` when either header is missing or\n * has no finite numeric tokens, so a 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\tMath.min(a, b),\n\t);\n\tconst resetSeconds = reduceRateLimitTokens(headers[\"x-ratelimit-reset\"], (a, b) =>\n\t\tMath.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 type { OpenCloudError } from \"../../errors/base.ts\";\nimport { RateLimitError } from \"../../errors/rate-limit.ts\";\nimport type { Result } from \"../../types.ts\";\nimport type { RateLimitSample } from \"./rate-limit-sample.ts\";\nimport { parseRateLimitHeaders } from \"./rate-limit-sample.ts\";\nimport type { HttpResponse } from \"./types.ts\";\n\n/**\n * Extracts a {@link RateLimitSample} from a transport result so the budget gate\n * can be fed from every attempt. A 2xx carries the budget in its headers; a 429\n * carries it on the {@link RateLimitError} (the raw headers are dropped before\n * this point). Any other error, or a response that reported no budget, yields\n * `undefined` and leaves the gate on static pacing.\n *\n * @param result - The classified transport result for one attempt.\n * @returns The parsed sample, or `undefined` when none was reported.\n */\nexport function rateLimitSampleFromResult(\n\tresult: Result<HttpResponse, OpenCloudError>,\n): RateLimitSample | undefined {\n\tif (result.success) {\n\t\treturn parseRateLimitHeaders(result.data.headers);\n\t}\n\n\tconst { err } = result;\n\tif (err instanceof RateLimitError && err.remaining !== undefined) {\n\t\treturn { remaining: err.remaining, resetSeconds: err.retryAfterSeconds };\n\t}\n\n\treturn undefined;\n}\n","import type { SleepFunc } from \"../utils/sleep.ts\";\nimport type { OpenCloudHooks } from \"./types.ts\";\n\n/**\n * Identifies and bounds a single Roblox Open Cloud operation for rate\n * limiting, e.g. `{ operationKey: \"game-passes.create\", maxPerSecond: 5 }`.\n */\nexport interface OperationLimit {\n\t/** Maximum sustained request rate in requests per second. */\n\treadonly maxPerSecond: number;\n\t/**\n\t * Stable identifier for the operation (e.g. \"game-passes.create\"). Not\n\t * consumed by the queue itself; callers use it to key per-operation\n\t * queues in a registry (see GamePassesClient).\n\t */\n\treadonly operationKey: string;\n}\n\n/**\n * Token-bucket rate limiter for a single `(apiKey, operation)` pair. Every\n * call to `acquire` consumes one token; when the bucket is empty the call\n * waits until a token regenerates before invoking the task. Burst capacity\n * equals `maxPerSecond`, refilling at `maxPerSecond` tokens per second.\n *\n * Implemented as a leaky bucket tracking drain debt in ms. `#lastCheck`\n * advances by `waitMs` after every sleep so the algorithm stays correct\n * whether or not the injected sleep moves `Date.now()` forward.\n */\nexport class RateLimitQueue {\n\treadonly #hooks: OpenCloudHooks;\n\treadonly #intervalMs: number;\n\treadonly #maxBucketLevel: number;\n\treadonly #sleep: SleepFunc;\n\n\t#bucketLevel = 0;\n\t#chain: Promise<void> = Promise.resolve();\n\t#lastCheck: number = Date.now();\n\n\t/**\n\t * Creates a rate-limit queue bound to a single operation.\n\t *\n\t * @param limit - The operation key and its per-second request ceiling.\n\t * @param hooks - Observability callbacks; `onRateLimit` fires when the\n\t * bucket is empty and a sleep is about to start.\n\t * @param sleep - Injectable sleep (tests pass a fake).\n\t */\n\tconstructor(limit: OperationLimit, hooks: OpenCloudHooks, sleep: SleepFunc) {\n\t\tthis.#intervalMs = 1000 / limit.maxPerSecond;\n\t\tthis.#maxBucketLevel = limit.maxPerSecond * this.#intervalMs;\n\t\tthis.#hooks = hooks;\n\t\tthis.#sleep = sleep;\n\t}\n\n\t/**\n\t * Waits for a token — sleeping and firing `hooks.onRateLimit` if the\n\t * bucket is empty — then executes `task`. Concurrent callers are\n\t * serialized at token acquisition; tasks themselves run independently\n\t * once their token is secured.\n\t *\n\t * @param task - The request to run once a token is available.\n\t * @returns The value produced by `task`.\n\t */\n\tpublic async acquire<T>(task: () => Promise<T>): Promise<T> {\n\t\tconst myTurn = this.#chain.then(async () => this.#waitForToken());\n\t\tthis.#chain = myTurn;\n\t\tawait myTurn;\n\t\treturn task();\n\t}\n\n\tasync #waitForToken(): Promise<void> {\n\t\tconst now = Math.max(Date.now(), this.#lastCheck);\n\t\tconst drained = Math.max(0, this.#bucketLevel - (now - this.#lastCheck));\n\t\tthis.#lastCheck = now;\n\n\t\tif (drained + this.#intervalMs <= this.#maxBucketLevel) {\n\t\t\tthis.#bucketLevel = drained + this.#intervalMs;\n\t\t\treturn;\n\t\t}\n\n\t\tconst waitMs = drained + this.#intervalMs - this.#maxBucketLevel;\n\t\tthis.#hooks.onRateLimit?.(waitMs);\n\t\tawait this.#sleep(waitMs);\n\t\tthis.#bucketLevel = this.#maxBucketLevel;\n\t\tthis.#lastCheck = now + waitMs;\n\t}\n}\n","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 tryCatch<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 { OpenCloudError } from \"../../errors/base.ts\";\nimport { NetworkError } from \"../../errors/network-error.ts\";\nimport { RateLimitError } from \"../../errors/rate-limit.ts\";\nimport type { Result } from \"../../types.ts\";\nimport { tryCatch } from \"../utils/try-catch.ts\";\nimport { reduceRateLimitTokens } from \"./rate-limit-sample.ts\";\nimport type { HttpClient, HttpRequest, HttpResponse, RequestConfig } 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\ninterface ParseFailureArgs {\n\treadonly cause: Error;\n\treadonly response: Response;\n\treadonly text: string;\n}\n\ninterface ApiErrorMessageParts {\n\treadonly code: string | undefined;\n\treadonly message: string | undefined;\n\treadonly status: number;\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 * Permissively extracts a machine-readable error code from a response body.\n *\n * Modern Open Cloud responses use `{ errorCode: string, message: string }`;\n * the legacy game-internationalization endpoints use\n * `{ errors: [{ code: number, message: string }, ...] }`. Both shapes are\n * checked; numeric legacy codes are returned as strings so callers see one\n * consistent type.\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\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 * Parses the `x-ratelimit-reset` header value into seconds. On a 429 the header\n * is a comma-separated list of per-window reset times (e.g. `\"22, 0\"`, one entry\n * per rate-limit window); the largest value is the longest-resetting window and\n * the only safe wait that won't retry into a still-exhausted window. A single\n * value is treated as a one-element list.\n *\n * @param headerValue - The raw header value, or `undefined` if missing.\n * @returns The number of seconds to wait, or 0 if missing/invalid.\n */\nexport function parseRetryAfterSeconds(headerValue: string | undefined): number {\n\treturn reduceRateLimitTokens(headerValue, (a, b) => Math.max(a, b)) ?? 0;\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): RequestInit {\n\tconst headers = new Headers({\n\t\t\"x-api-key\": config.apiKey,\n\t});\n\n\tconst options: RequestInit = {\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\tif (request.headers !== undefined) {\n\t\tfor (const [name, value] of Object.entries(request.headers)) {\n\t\t\tif (name.toLowerCase() === \"x-api-key\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\theaders.set(name, value);\n\t\t}\n\t}\n\n\tif (config.timeout !== undefined) {\n\t\toptions.signal = AbortSignal.timeout(config.timeout);\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 * @returns An HttpClient that classifies responses into typed Results.\n */\nexport function createFetchHttpClient(\n\tfetchFunc: (url: string, init: RequestInit) => Promise<Response> = globalThis.fetch,\n): HttpClient {\n\treturn {\n\t\tasync request(\n\t\t\thttpRequest: HttpRequest,\n\t\t\tconfig: RequestConfig,\n\t\t): Promise<Result<HttpResponse, OpenCloudError>> {\n\t\t\tconst url = buildUrl(httpRequest, config);\n\t\t\tconst options = buildFetchOptions(httpRequest, config);\n\n\t\t\tconst fetchResult = await tryCatch(fetchFunc(url, options));\n\t\t\tif (!fetchResult.success) {\n\t\t\t\treturn {\n\t\t\t\t\terr: new NetworkError(\"Network request failed\", {\n\t\t\t\t\t\tcause: fetchResult.err,\n\t\t\t\t\t\tmethod: httpRequest.method,\n\t\t\t\t\t\turl,\n\t\t\t\t\t}),\n\t\t\t\t\tsuccess: false,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn classifyResponse(fetchResult.data);\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\nfunction formatApiErrorMessage(parts: ApiErrorMessageParts): string {\n\tconst { code, message, status } = parts;\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(status: number, body: JSONValue | undefined): ApiError {\n\tconst code = extractErrorCode(body);\n\tconst message = extractErrorMessage(body);\n\treturn new ApiError(formatApiErrorMessage({ code, message, status }), {\n\t\tcode,\n\t\tdetails: body,\n\t\tstatusCode: status,\n\t});\n}\n\nfunction createRateLimitError(response: Response): RateLimitError {\n\tconst headers = headersToRecord(response.headers);\n\treturn new RateLimitError(\"Rate limited\", {\n\t\tremaining: reduceRateLimitTokens(headers[\"x-ratelimit-remaining\"], (a, b) =>\n\t\t\tMath.min(a, b),\n\t\t),\n\t\tretryAfterSeconds: parseRetryAfterSeconds(headers[\"x-ratelimit-reset\"]),\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 tryCatch}.\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 * Builds the error for a 2xx response whose body could not be parsed as JSON,\n * preserving the parse `cause`, the (truncated) raw body, and the declared\n * content-type so the failure can be diagnosed after the fact.\n *\n * @param args - The Response, raw body text, and underlying parse error.\n * @returns An ApiError carrying the diagnostic context.\n */\nfunction parseFailureError({ cause, response, text }: ParseFailureArgs): ApiError {\n\tconst contentType = response.headers.get(CONTENT_TYPE_HEADER) ?? \"unknown\";\n\treturn new ApiError(`Failed to parse response body (content-type: ${contentType})`, {\n\t\tcause,\n\t\tdetails: text.slice(0, MAX_DETAIL_LENGTH),\n\t\tstatusCode: response.status,\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 (for example\n * an HTML gateway page) degrades to a status-based {@link ApiError} carrying\n * the raw text. A parse failure is only fatal on a 2xx, where a parseable body is part\n * of the contract.\n *\n * @param response - The raw fetch Response to classify.\n * @returns A Result containing an HttpResponse on success or an OpenCloudError on failure.\n */\nasync function classifyResponse(response: Response): Promise<Result<HttpResponse, OpenCloudError>> {\n\tif (response.status === 429) {\n\t\treturn { err: createRateLimitError(response), success: false };\n\t}\n\n\tconst text = await response.text();\n\tconst parsed: Result<JSONValue | undefined> =\n\t\ttext === \"\" ? { data: undefined, success: true } : parseJson(text);\n\n\tif (response.status >= 300) {\n\t\tconst body = parsed.success ? parsed.data : text.slice(0, MAX_DETAIL_LENGTH);\n\t\treturn { err: createApiError(response.status, body), success: false };\n\t}\n\n\tif (!parsed.success) {\n\t\treturn { err: parseFailureError({ cause: parsed.err, response, text }), success: false };\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","import { setTimeout } from \"node:timers/promises\";\n\nimport type { HttpClient, SleepFunc } from \"../../client/types.ts\";\nimport { createFetchHttpClient } from \"./fetch-client.ts\";\n\n/**\n * Options accepted by {@link resolveDependencies}. Mirrors the test-seam\n * subset of the public client options.\n */\ninterface ResolveDependenciesOptions {\n\t/** Test seam: custom {@link HttpClient}. Defaults to a fetch-backed client. */\n\treadonly httpClient?: HttpClient | undefined;\n\t/** Test seam: custom {@link SleepFunc}. Defaults to a `setTimeout`-backed sleep. */\n\treadonly sleep?: SleepFunc | undefined;\n}\n\n/**\n * Fully-populated dependency set consumed by resource client constructors.\n */\ninterface ResolvedDependencies {\n\t/** Concrete {@link HttpClient} implementation. */\n\treadonly httpClient: HttpClient;\n\t/** Concrete {@link SleepFunc} implementation. */\n\treadonly sleep: SleepFunc;\n}\n\n/**\n * Resolves the concrete HTTP client and sleep implementation a resource\n * client should use. Falls back to the fetch-backed HTTP client and the\n * default `setTimeout`-based sleep when the caller omits the test seams.\n *\n * Extracted so resource client constructors can keep their dependency\n * resolution logic in a single, unit-testable place; this makes the\n * default branches easy to cover without stubbing globals like `fetch`.\n *\n * @param options - Optional {@link HttpClient} and {@link SleepFunc} test seams.\n * @returns A {@link ResolvedDependencies} with defaults applied.\n */\nexport function resolveDependencies(options: ResolveDependenciesOptions): ResolvedDependencies {\n\treturn {\n\t\thttpClient: options.httpClient ?? createFetchHttpClient(),\n\t\tsleep: options.sleep ?? setTimeout,\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 type { Except } from \"type-fest\";\n\nimport type {\n\tHttpClient,\n\tHttpRequest,\n\tHttpResponse,\n\tOpenCloudClientOptions,\n\tOpenCloudHooks,\n\tRequestConfig,\n\tRequestOptions,\n\tSleepFunc,\n} from \"../client/types.ts\";\nimport { ApiError } from \"../errors/api-error.ts\";\nimport type { OpenCloudError } from \"../errors/base.ts\";\nimport { PermissionError } from \"../errors/permission-error.ts\";\nimport type { Result } from \"../types.ts\";\nimport { BudgetGate } from \"./http/budget-gate.ts\";\nimport { executeWithRetry } from \"./http/execute.ts\";\nimport { rateLimitSampleFromResult } from \"./http/rate-limit-observation.ts\";\nimport { type OperationLimit, RateLimitQueue } from \"./http/rate-limit-queue.ts\";\nimport { resolveDependencies } from \"./http/resolve-dependencies.ts\";\nimport {\n\tdefaultRetryDelay,\n\tIDEMPOTENT_METHOD_DEFAULTS,\n\tmergeConfig,\n\ttype MethodKind,\n\ttype RetryResolvable,\n} from \"./http/retry.ts\";\nimport { isUploadRequest } from \"./http/upload-request.ts\";\n\n/**\n * Describes a single resource method's shape for dispatch through\n * `ResourceClient.execute`. Each resource client declares one module-level\n * constant per public method; that constant binds the four resource-specific\n * values (request builder, response parser, retry-policy method kind,\n * operation-level rate limit) and flows through `execute` uniformly.\n *\n * @template P - The resource-specific parameter shape the builder\n * accepts.\n * @template T - The resource-specific parsed success type the parser\n * produces.\n */\nexport interface ResourceMethodSpec<P, T> {\n\t/**\n\t * Builds the pure {@link HttpRequest} for a single call. Returns a\n\t * {@link Result} so a builder can short-circuit with a local error\n\t * (typically a {@link OpenCloudError} subclass such as `ValidationError`)\n\t * before any HTTP, queue, or retry work happens. Builders that cannot\n\t * fail wrap their return as `{ data: request, success: true }`.\n\t */\n\treadonly buildRequest: (parameters: P) => Result<HttpRequest, OpenCloudError>;\n\t/** Method-level retry defaults merged into the resolved config. */\n\treadonly methodDefaults: Partial<RetryResolvable>;\n\t/**\n\t * Method kind, controlling merge precedence: `\"create\"` lets method\n\t * defaults win over client config so create safety cannot be relaxed\n\t * silently; `\"idempotent\"` lets client config win over method defaults\n\t * so consumers can loosen retry globally.\n\t */\n\treadonly methodKind: MethodKind;\n\t/** Operation-level rate limit, keyed into the client's per-key queue map. */\n\treadonly operationLimit: OperationLimit;\n\t/**\n\t * Converts the full {@link HttpResponse} into the resource-specific\n\t * parsed shape. Takes the whole response (body, status, headers) so\n\t * future parsers can read headers without widening the signature.\n\t */\n\treadonly parse: (response: HttpResponse) => Result<T, OpenCloudError>;\n\t/**\n\t * Open Cloud scopes the API key or OAuth token must carry for this\n\t * method, sourced from the vendored OpenAPI schema's `x-roblox-scopes`.\n\t * When set, a 401 or 403 ApiError from the upstream call is upgraded to\n\t * a {@link PermissionError} carrying these scopes alongside\n\t * {@link OperationLimit.operationKey}, so callers can name the missing\n\t * scope instead of just the HTTP status. Optional so test specs and\n\t * not-yet-wired resources can opt out.\n\t */\n\treadonly requiredScopes?: ReadonlyArray<string>;\n}\n\n/**\n * Single-argument bundle consumed by `ResourceClient.execute`: the per-method\n * spec, the resource-specific parameters, and optional per-request config\n * overrides.\n *\n * @template P - The resource-specific parameter shape the builder accepts.\n * @template T - The resource-specific parsed success type the parser produces.\n */\ninterface ExecuteCall<P, T> {\n\t/** Optional per-request config overrides. */\n\treadonly options?: RequestOptions | undefined;\n\t/** Resource-specific request parameters. */\n\treadonly parameters: P;\n\t/** Per-method binding of builder, parser, method kind, and operation limit. */\n\treadonly spec: ResourceMethodSpec<P, T>;\n}\n\n/**\n * Wraps an infallible request build as a {@link Result}-returning\n * `buildRequest` callback compatible with {@link ResourceMethodSpec}.\n * Use from a resource client whose builder cannot fail; resource clients\n * with local validation should construct the {@link Result} directly.\n *\n * @param request - The pre-built {@link HttpRequest}.\n * @returns A success Result wrapping the request.\n */\nexport function okRequest(request: HttpRequest): Result<HttpRequest, OpenCloudError> {\n\treturn { data: request, success: true };\n}\n\n/**\n * A {@link ResourceMethodSpec.parse} implementation for endpoints that return\n * no business payload on success (such as `DELETE` and reorder operations).\n * Surfaces `undefined` data and never inspects the response body.\n *\n * @returns A success Result with `undefined` data.\n */\nexport function parseEmptyResponse(): Result<undefined, OpenCloudError> {\n\treturn { data: undefined, success: true };\n}\n\nconst CLIENT_DEFAULTS = Object.freeze({\n\tbaseUrl: \"https://apis.roblox.com\",\n\tmaxRetries: 3,\n\tretryableStatuses: IDEMPOTENT_METHOD_DEFAULTS.retryableStatuses,\n\tretryableTransportCodes: IDEMPOTENT_METHOD_DEFAULTS.retryableTransportCodes,\n\tretryDelay: defaultRetryDelay,\n\ttimeout: 30_000,\n} satisfies Except<RetryResolvable, \"apiKey\">);\n\n/**\n * Inputs to {@link buildRequestConfig}, bundled to keep the signature narrow.\n */\ninterface RequestConfigInputs {\n\t/** The resolved config for this call. */\n\treadonly merged: RetryResolvable;\n\t/** The caller's per-request overrides, if any. */\n\treadonly options: RequestOptions | undefined;\n\t/** The built request, inspected for an upload body. */\n\treadonly request: HttpRequest;\n}\n\n/**\n * Internal orchestrator shared by every Open Cloud resource client. Holds\n * the frozen client config, observability hooks, injected HTTP client and\n * sleep, and the per-effective-key rate-limit queue registry. Resource\n * classes compose one instance and dispatch every public method through\n * {@link ResourceClient.execute} with a per-method {@link ResourceMethodSpec}.\n * Not exported from any package subpath; reachable only via sibling\n * `src/resources/**` modules in this package.\n */\nexport class ResourceClient {\n\treadonly #budgets: BudgetGate;\n\treadonly #config: Readonly<RetryResolvable>;\n\treadonly #hooks: OpenCloudHooks;\n\treadonly #httpClient: HttpClient;\n\treadonly #queues = new Map<string, RateLimitQueue>();\n\treadonly #sleep: SleepFunc;\n\n\t/**\n\t * Creates a new {@link ResourceClient}. Resolves the injected HTTP\n\t * client and sleep (defaulting to fetch + `setTimeout`) and freezes the\n\t * merged client config so subsequent calls cannot mutate it.\n\t *\n\t * @param options - Client-level configuration including the API key\n\t * and optional construction-time test seams.\n\t */\n\tconstructor(options: OpenCloudClientOptions) {\n\t\tconst { apiKey, hooks, httpClient, sleep, ...overrides } = options;\n\t\tconst resolved = resolveDependencies({ httpClient, sleep });\n\t\tthis.#httpClient = resolved.httpClient;\n\t\tthis.#sleep = resolved.sleep;\n\t\tthis.#budgets = new BudgetGate(this.#sleep);\n\t\tthis.#hooks = hooks ?? {};\n\t\tthis.#config = Object.freeze({\n\t\t\t...CLIENT_DEFAULTS,\n\t\t\tapiKey,\n\t\t\t...overrides,\n\t\t});\n\t}\n\n\t/**\n\t * Dispatches a single resource-method call. Merges the frozen client\n\t * config with the method's `methodDefaults` and the caller's optional\n\t * per-request `options`, routes through the effective-apiKey rate-limit\n\t * queue, runs the retry loop, and finally parses the response with the\n\t * spec's parser.\n\t *\n\t * @param call - The per-method spec, resource-specific parameters, and\n\t * optional per-request overrides.\n\t * @returns The parsed success payload or the {@link OpenCloudError} that\n\t * caused the request to fail. Never throws.\n\t */\n\tpublic async execute<P, T>(call: ExecuteCall<P, T>): Promise<Result<T, OpenCloudError>> {\n\t\tconst { options, parameters, spec } = call;\n\t\tconst merged = mergeConfig(this.#config, {\n\t\t\tmethodDefaults: spec.methodDefaults,\n\t\t\tmethodKind: spec.methodKind,\n\t\t\trequestOptions: options ?? {},\n\t\t});\n\t\tconst requestResult = spec.buildRequest(parameters);\n\t\tif (!requestResult.success) {\n\t\t\treturn requestResult;\n\t\t}\n\n\t\tconst requestConfig = buildRequestConfig({ merged, options, request: requestResult.data });\n\t\tconst queue = this.#getQueue(merged.apiKey, spec.operationLimit);\n\t\tconst httpResult = await queue.acquire(async () => {\n\t\t\treturn executeWithRetry(requestResult.data, {\n\t\t\t\tconfig: merged,\n\t\t\t\thooks: this.#hooks,\n\t\t\t\tsend: this.#gatedSend(merged.apiKey, requestConfig),\n\t\t\t\tsleep: this.#sleep,\n\t\t\t});\n\t\t});\n\t\tif (!httpResult.success) {\n\t\t\treturn { err: enrichPermissionError(httpResult.err, spec), success: false };\n\t\t}\n\n\t\treturn spec.parse(httpResult.data);\n\t}\n\n\t/**\n\t * Returns the sleep function used by this client instance.\n\t *\n\t * @returns The sleep function injected at construction time.\n\t */\n\tpublic get sleep(): SleepFunc {\n\t\treturn this.#sleep;\n\t}\n\n\t/**\n\t * Builds the transport callback for one logical call, wrapping the HTTP\n\t * client with the budget gate: each attempt waits on the API key's budget\n\t * before sending, then folds the response's reported budget back in so the\n\t * next attempt (or a sibling operation on the same key) can head off a 429.\n\t *\n\t * @param apiKey - The effective API key to gate on.\n\t * @param requestConfig - The resolved per-request transport config.\n\t * @returns A send callback for {@link executeWithRetry}.\n\t */\n\t#gatedSend(\n\t\tapiKey: string,\n\t\trequestConfig: RequestConfig,\n\t): (request: HttpRequest) => Promise<Result<HttpResponse, OpenCloudError>> {\n\t\treturn async (toSend) => {\n\t\t\tawait this.#budgets.gate(apiKey);\n\t\t\tconst sendResult = await this.#httpClient.request(toSend, requestConfig);\n\t\t\tthis.#budgets.observe(apiKey, rateLimitSampleFromResult(sendResult));\n\t\t\treturn sendResult;\n\t\t};\n\t}\n\n\t#getQueue(apiKey: string, limit: OperationLimit): RateLimitQueue {\n\t\tconst key = `${apiKey}::${limit.operationKey}`;\n\t\tconst existing = this.#queues.get(key);\n\t\tif (existing !== undefined) {\n\t\t\treturn existing;\n\t\t}\n\n\t\tconst queue = new RateLimitQueue(limit, this.#hooks, this.#sleep);\n\t\tthis.#queues.set(key, queue);\n\t\treturn queue;\n\t}\n}\n\n/**\n * Resolves the per-request {@link RequestConfig}. Upload requests\n * ({@link isUploadRequest}) carry no default timeout: a multi-megabyte place\n * file over a slow link is bandwidth-bound, so a client-side deadline only\n * fires spuriously. An explicit `options.timeout` still applies to any\n * request; every non-upload request keeps the merged default.\n *\n * @param inputs - The merged config, the built request, and per-request overrides.\n * @returns The config to hand to the transport, with `timeout` omitted when\n * no client-side deadline should apply.\n */\nfunction buildRequestConfig(inputs: RequestConfigInputs): RequestConfig {\n\tconst { merged, options, request } = inputs;\n\tconst shouldOmitDefaultTimeout = options?.timeout === undefined && isUploadRequest(request);\n\treturn {\n\t\tapiKey: merged.apiKey,\n\t\tbaseUrl: merged.baseUrl,\n\t\t...(shouldOmitDefaultTimeout ? {} : { timeout: merged.timeout }),\n\t};\n}\n\nfunction enrichPermissionError<P, T>(\n\terr: OpenCloudError,\n\tspec: ResourceMethodSpec<P, T>,\n): OpenCloudError {\n\tif (spec.requiredScopes === undefined) {\n\t\treturn err;\n\t}\n\n\tif (err instanceof PermissionError) {\n\t\treturn err;\n\t}\n\n\tif (!(err instanceof ApiError)) {\n\t\treturn err;\n\t}\n\n\tif (err.statusCode !== 401 && err.statusCode !== 403) {\n\t\treturn err;\n\t}\n\n\treturn new PermissionError(err.message, {\n\t\tcause: err.cause,\n\t\tcode: err.code,\n\t\toperationKey: spec.operationLimit.operationKey,\n\t\trequiredScopes: spec.requiredScopes,\n\t\tstatusCode: err.statusCode,\n\t});\n}\n"],"mappings":"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"}