@bedrock-rbx/ocale 0.1.0-beta.1 → 0.1.0-beta.2
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.
- package/dist/badges.d.mts +86 -3
- package/dist/badges.d.mts.map +1 -1
- package/dist/badges.mjs +113 -5
- package/dist/badges.mjs.map +1 -1
- package/dist/data.generated-BtkDGH8C.d.mts +485 -0
- package/dist/data.generated-BtkDGH8C.d.mts.map +1 -0
- package/dist/developer-products.d.mts +14 -5
- package/dist/developer-products.d.mts.map +1 -1
- package/dist/developer-products.mjs +5 -4
- package/dist/developer-products.mjs.map +1 -1
- package/dist/game-passes.d.mts +83 -2
- package/dist/game-passes.d.mts.map +1 -1
- package/dist/game-passes.mjs +111 -4
- package/dist/game-passes.mjs.map +1 -1
- package/dist/index.d.mts +3 -81
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/is-date-time-string-Cuf1TaSC.mjs +19 -0
- package/dist/is-date-time-string-Cuf1TaSC.mjs.map +1 -0
- package/dist/locales.d.mts +2 -0
- package/dist/locales.mjs +512 -0
- package/dist/locales.mjs.map +1 -0
- package/dist/luau-execution.d.mts +62 -0
- package/dist/luau-execution.d.mts.map +1 -0
- package/dist/luau-execution.mjs +52 -0
- package/dist/luau-execution.mjs.map +1 -0
- package/dist/places.d.mts +47 -8
- package/dist/places.d.mts.map +1 -1
- package/dist/places.mjs +39 -13
- package/dist/places.mjs.map +1 -1
- package/dist/{price-information-CmpscMc4.mjs → price-information-s7DY0GV2.mjs} +2 -2
- package/dist/{price-information-CmpscMc4.mjs.map → price-information-s7DY0GV2.mjs.map} +1 -1
- package/dist/{rate-limit-BBU_4xnZ.mjs → rate-limit-CKfuhxT1.mjs} +11 -3
- package/dist/rate-limit-CKfuhxT1.mjs.map +1 -0
- package/dist/rate-limit-DzHBFwps.d.mts +92 -0
- package/dist/rate-limit-DzHBFwps.d.mts.map +1 -0
- package/dist/{resource-client-CaS_j3yg.mjs → resource-client-Wi4Mwqy5.mjs} +69 -14
- package/dist/resource-client-Wi4Mwqy5.mjs.map +1 -0
- package/dist/specs-Co6qYp_E.mjs +309 -0
- package/dist/specs-Co6qYp_E.mjs.map +1 -0
- package/dist/storage.d.mts +374 -0
- package/dist/storage.d.mts.map +1 -0
- package/dist/storage.mjs +371 -0
- package/dist/storage.mjs.map +1 -0
- package/dist/types-BZ0959rh.d.mts +149 -0
- package/dist/types-BZ0959rh.d.mts.map +1 -0
- package/dist/{types-YCTsM8Qd.d.mts → types-Cp8w8uwA.d.mts} +1 -1
- package/dist/{types-YCTsM8Qd.d.mts.map → types-Cp8w8uwA.d.mts.map} +1 -1
- package/dist/universes.d.mts +37 -12
- package/dist/universes.d.mts.map +1 -1
- package/dist/universes.mjs +5 -4
- package/dist/universes.mjs.map +1 -1
- package/dist/{validation-CTZzJhmd.mjs → validation-b7KAoEio.mjs} +2 -2
- package/dist/validation-b7KAoEio.mjs.map +1 -0
- package/package.json +7 -3
- package/dist/rate-limit-BBU_4xnZ.mjs.map +0 -1
- package/dist/resource-client-CaS_j3yg.mjs.map +0 -1
- package/dist/validation-CTZzJhmd.mjs.map +0 -1
package/dist/universes.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { d as OpenCloudError, i as OpenCloudClientOptions, l as Result, s as RequestOptions } from "./types-
|
|
1
|
+
import { d as OpenCloudError, i as OpenCloudClientOptions, l as Result, s as RequestOptions } from "./types-Cp8w8uwA.mjs";
|
|
2
|
+
import { i as RobloxLocale, r as RobloxLanguageCode } from "./data.generated-BtkDGH8C.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/domains/cloud-v2/universes/types.d.ts
|
|
4
5
|
/**
|
|
@@ -141,7 +142,11 @@ interface ExperienceIcon {
|
|
|
141
142
|
readonly imageId: string;
|
|
142
143
|
/** CDN URL the icon can be loaded from. */
|
|
143
144
|
readonly imageUrl: string;
|
|
144
|
-
/**
|
|
145
|
+
/**
|
|
146
|
+
* Roblox wire form the icon is registered against. Either the
|
|
147
|
+
* Language form (e.g. `en`, `fil`, `zh-hans`) or the Locale form
|
|
148
|
+
* (e.g. `en_us`, `pt_br`, `ar_001`).
|
|
149
|
+
*/
|
|
145
150
|
readonly languageCode: string;
|
|
146
151
|
/** Moderation state of the icon. */
|
|
147
152
|
readonly state: GameIconState;
|
|
@@ -154,8 +159,12 @@ interface ExperienceIcon {
|
|
|
154
159
|
interface UploadExperienceIconParameters {
|
|
155
160
|
/** Image bytes to upload. PNG and JPEG are accepted by the server. */
|
|
156
161
|
readonly image: Blob | Uint8Array;
|
|
157
|
-
/**
|
|
158
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Roblox wire form the icon is being uploaded for. Either the
|
|
164
|
+
* Language form (e.g. `en`, `fil`, `zh-hans`) or the Locale form
|
|
165
|
+
* (e.g. `en_us`, `pt_br`, `ar_001`).
|
|
166
|
+
*/
|
|
167
|
+
readonly languageCode: RobloxLanguageCode | RobloxLocale;
|
|
159
168
|
/** Stringified ID of the universe whose icon is being uploaded. */
|
|
160
169
|
readonly universeId: string;
|
|
161
170
|
}
|
|
@@ -164,8 +173,12 @@ interface UploadExperienceIconParameters {
|
|
|
164
173
|
* for a given language.
|
|
165
174
|
*/
|
|
166
175
|
interface DeleteExperienceIconParameters {
|
|
167
|
-
/**
|
|
168
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Roblox wire form of the icon to delete. Either the Language form
|
|
178
|
+
* (e.g. `en`, `fil`, `zh-hans`) or the Locale form (e.g. `en_us`,
|
|
179
|
+
* `pt_br`, `ar_001`).
|
|
180
|
+
*/
|
|
181
|
+
readonly languageCode: RobloxLanguageCode | RobloxLocale;
|
|
169
182
|
/** Stringified ID of the universe whose icon is being deleted. */
|
|
170
183
|
readonly universeId: string;
|
|
171
184
|
}
|
|
@@ -193,8 +206,12 @@ interface UploadedExperienceThumbnail {
|
|
|
193
206
|
interface UploadExperienceThumbnailParameters {
|
|
194
207
|
/** Image bytes to upload. PNG and JPEG are accepted by the server. */
|
|
195
208
|
readonly image: Blob | Uint8Array;
|
|
196
|
-
/**
|
|
197
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Roblox wire form the thumbnail is being uploaded for. Either the
|
|
211
|
+
* Language form (e.g. `en`, `fil`, `zh-hans`) or the Locale form
|
|
212
|
+
* (e.g. `en_us`, `pt_br`, `ar_001`).
|
|
213
|
+
*/
|
|
214
|
+
readonly languageCode: RobloxLanguageCode | RobloxLocale;
|
|
198
215
|
/** Stringified ID of the universe whose carousel is being appended to. */
|
|
199
216
|
readonly universeId: string;
|
|
200
217
|
}
|
|
@@ -204,8 +221,12 @@ interface UploadExperienceThumbnailParameters {
|
|
|
204
221
|
interface DeleteExperienceThumbnailParameters {
|
|
205
222
|
/** Stringified media asset ID of the thumbnail to delete. */
|
|
206
223
|
readonly imageId: string;
|
|
207
|
-
/**
|
|
208
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Roblox wire form of the thumbnail to delete. Either the Language
|
|
226
|
+
* form (e.g. `en`, `fil`, `zh-hans`) or the Locale form (e.g.
|
|
227
|
+
* `en_us`, `pt_br`, `ar_001`).
|
|
228
|
+
*/
|
|
229
|
+
readonly languageCode: RobloxLanguageCode | RobloxLocale;
|
|
209
230
|
/** Stringified ID of the universe whose carousel is being modified. */
|
|
210
231
|
readonly universeId: string;
|
|
211
232
|
}
|
|
@@ -214,8 +235,12 @@ interface DeleteExperienceThumbnailParameters {
|
|
|
214
235
|
* `orderedImageIds` describes the new display order from first to last.
|
|
215
236
|
*/
|
|
216
237
|
interface ReorderExperienceThumbnailsParameters {
|
|
217
|
-
/**
|
|
218
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Roblox wire form of the carousel being reordered. Either the
|
|
240
|
+
* Language form (e.g. `en`, `fil`, `zh-hans`) or the Locale form
|
|
241
|
+
* (e.g. `en_us`, `pt_br`, `ar_001`).
|
|
242
|
+
*/
|
|
243
|
+
readonly languageCode: RobloxLanguageCode | RobloxLocale;
|
|
219
244
|
/** Stringified media asset IDs in the desired display order. */
|
|
220
245
|
readonly orderedImageIds: ReadonlyArray<string>;
|
|
221
246
|
/** Stringified ID of the universe whose carousel is being reordered. */
|
package/dist/universes.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"universes.d.mts","names":[],"sources":["../src/domains/cloud-v2/universes/types.ts","../src/domains/game-internationalization/game-icon/wire.ts","../src/domains/game-internationalization/game-icon/types.ts","../src/domains/game-internationalization/game-thumbnails/types.ts","../src/resources/universes/client.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"universes.d.mts","names":[],"sources":["../src/domains/cloud-v2/universes/types.ts","../src/domains/game-internationalization/game-icon/wire.ts","../src/domains/game-internationalization/game-icon/types.ts","../src/domains/game-internationalization/game-thumbnails/types.ts","../src/resources/universes/client.ts"],"mappings":";;;;;;;UAGiB,qBAAA;EAAjB;EAAA,SAEU,UAAA;AAAA;;;AAMV;UAAiB,UAAA;;WAEP,KAAA;EAEA;EAAA,SAAA,GAAA;AAAA;;KAIE,kBAAA;;AAWZ;;;;;;;;UAAiB,wBAAA;;WAEP,cAAA;EA4BoB;EAAA,SA1BpB,cAAA;;WAEA,iBAAA,GAAoB,UAAA;;WAEpB,kBAAA,GAAqB,UAAA;;WAErB,iBAAA,GAAoB,UAAA;;WAEpB,aAAA;;WAEA,uBAAA;;WAEA,qBAAA,GAAwB,UAAA;;WAExB,aAAA;;WAEA,gBAAA,GAAmB,UAAA;;WAEnB,iBAAA,GAAoB,UAAA;;WAEpB,UAAA;;WAEA,gBAAA;EAIoB;EAAA,SAFpB,SAAA;EAQO;EAAA,SANP,iBAAA,GAAoB,UAAA;AAAA;;AAiB9B;;UAXiB,aAAA;EAWL;;AAKZ;;EALY,SANF,EAAA;;WAEA,IAAA;AAAA;;KAIE,iBAAA;;;;UAKK,QAAA;;WAEP,EAAA;;WAEA,SAAA,EAAW,iBAAA;EA0CQ;EAAA,SAxCnB,cAAA;;WAEA,SAAA,EAAW,IAAA;;WAEX,WAAA;;WAEA,cAAA;;WAEA,iBAAA,EAAmB,UAAA;;WAEnB,WAAA;;WAEA,kBAAA,EAAoB,UAAA;;WAEpB,iBAAA,EAAmB,UAAA;;WAEnB,aAAA;;WAEA,KAAA,EAAO,aAAA;;WAEP,uBAAA;;WAEA,qBAAA,EAAuB,UAAA;;WAEvB,WAAA;;WAEA,aAAA;;WAEA,gBAAA,EAAkB,UAAA;;WAElB,iBAAA,EAAmB,UAAA;;WAEnB,SAAA,EAAW,IAAA;;WAEX,UAAA,EAAY,kBAAA;;WAEZ,gBAAA;;WAEA,SAAA;;WAEA,iBAAA,EAAmB,UAAA;AAAA;;;;;;KC7HjB,aAAA;;;;ADAZ;;UEMiB,cAAA;EFNA;EAAA,SEQP,OAAA;EFAV;EAAA,SEEU,QAAA;;;;AFMV;;WEAU,YAAA;EFAE;EAAA,SEEF,KAAA,EAAO,aAAA;AAAA;;;;;;UAQA,8BAAA;;WAEP,KAAA,EAAO,IAAA,GAAO,UAAA;;;;;;WAMd,YAAA,EAAc,kBAAA,GAAqB,YAAA;;WAEnC,UAAA;AAAA;;;;;UAOO,8BAAA;;;;;;WAMP,YAAA,EAAc,kBAAA,GAAqB,YAAA;;WAEnC,UAAA;AAAA;;;;UAMO,6BAAA;EFMjB;EAAA,SEJU,UAAA;AAAA;;;;;;UCzDO,2BAAA;EHFA;EAAA,SGIP,YAAA;AAAA;;AHIV;;;;UGIiB,mCAAA;EHIjB;EAAA,SGFU,KAAA,EAAO,IAAA,GAAO,UAAA;;;;AHaxB;;WGPU,YAAA,EAAc,kBAAA,GAAqB,YAAA;;WAEnC,UAAA;AAAA;;;;UAMO,mCAAA;;WAEP,OAAA;;;;;;WAMA,YAAA,EAAc,kBAAA,GAAqB,YAAA;;WAEnC,UAAA;AAAA;;;;;UAOO,qCAAA;;;;;;WAMP,YAAA,EAAc,kBAAA,GAAqB,YAAA;;WAEnC,eAAA,EAAiB,aAAA;;WAEjB,UAAA;AAAA;;;UC6GA,kBAAA;EJpKO;;AAQjB;;;;;AAQA;;;;;EIiKC,MAAA,GACC,UAAA,EAAY,8BAAA,EACZ,OAAA,GAAU,cAAA,KACN,OAAA,CAAQ,MAAA,YAAkB,cAAA;EJzJf;;;;;;;;;;EIoKhB,IAAA,GACC,UAAA,EAAY,6BAAA,EACZ,OAAA,GAAU,cAAA,KACN,OAAA,CAAQ,MAAA,CAAO,aAAA,CAAc,cAAA,GAAiB,cAAA;;;;;;;;;;;;;EAanD,MAAA,GACC,UAAA,EAAY,8BAAA,EACZ,OAAA,GAAU,cAAA,KACN,OAAA,CAAQ,MAAA,YAAkB,cAAA;AAAA;AAAA,UAGtB,wBAAA;;;;;;;;;;AJtJV;EIiKC,MAAA,GACC,UAAA,EAAY,mCAAA,EACZ,OAAA,GAAU,cAAA,KACN,OAAA,CAAQ,MAAA,YAAkB,cAAA;;;;AJzJhC;;;;;AAKA;;;;EIiKC,OAAA,GACC,UAAA,EAAY,qCAAA,EACZ,OAAA,GAAU,cAAA,KACN,OAAA,CAAQ,MAAA,YAAkB,cAAA;;;;;;;;;;;;;;EAc/B,MAAA,GACC,UAAA,EAAY,mCAAA,EACZ,OAAA,GAAU,cAAA,KACN,OAAA,CAAQ,MAAA,CAAO,2BAAA,EAA6B,cAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA+BrC,eAAA;EAAA;;;;;;;WASI,IAAA,EAAM,kBAAA;EH5SvB;;;;;;;;ACMA;EDNA,SGsTiB,UAAA,EAAY,wBAAA;;;;;;;;EAS5B,WAAA,CAAY,OAAA,EAAS,sBAAA;EF7SL;AAQjB;;;;;;;;EEoTC,GAAA,CACC,UAAA,EAAY,qBAAA,EACZ,OAAA,GAAU,cAAA,GACR,OAAA,CAAQ,MAAA,CAAO,QAAA,EAAU,cAAA;;;;;;;;;;AFtS7B;;;;EEuTC,MAAA,CACC,UAAA,EAAY,wBAAA,EACZ,OAAA,GAAU,cAAA,GACR,OAAA,CAAQ,MAAA,CAAO,QAAA,EAAU,cAAA;AAAA"}
|
package/dist/universes.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { i as ApiError } from "./rate-limit-
|
|
2
|
-
import { t as ValidationError } from "./validation-
|
|
1
|
+
import { i as ApiError } from "./rate-limit-CKfuhxT1.mjs";
|
|
2
|
+
import { t as ValidationError } from "./validation-b7KAoEio.mjs";
|
|
3
3
|
import { t as toBlob } from "./to-blob-1BtHsDGK.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { t as isDateTimeString } from "./is-date-time-string-Cuf1TaSC.mjs";
|
|
5
|
+
import { a as IDEMPOTENT_METHOD_DEFAULTS, i as CREATE_METHOD_DEFAULTS, n as okRequest, o as isRecord, r as parseEmptyResponse, t as ResourceClient } from "./resource-client-Wi4Mwqy5.mjs";
|
|
5
6
|
//#region src/domains/cloud-v2/universes/builders.ts
|
|
6
7
|
/**
|
|
7
8
|
* Dodges `unicorn/no-null` while still emitting a literal `null` onto
|
|
@@ -182,7 +183,7 @@ function isAgeRatingWire(value) {
|
|
|
182
183
|
return value === "AGE_RATING_13_PLUS" || value === "AGE_RATING_17_PLUS" || value === "AGE_RATING_9_PLUS" || value === "AGE_RATING_ALL" || value === "AGE_RATING_UNSPECIFIED";
|
|
183
184
|
}
|
|
184
185
|
function hasValidRequiredFields(body) {
|
|
185
|
-
return typeof body["path"] === "string" &&
|
|
186
|
+
return typeof body["path"] === "string" && isDateTimeString(body["createTime"]) && isDateTimeString(body["updateTime"]) && typeof body["displayName"] === "string" && typeof body["description"] === "string" && isVisibilityWire(body["visibility"]) && isAgeRatingWire(body["ageRating"]);
|
|
186
187
|
}
|
|
187
188
|
function isSocialLinkWire(value) {
|
|
188
189
|
if (!isRecord(value)) return false;
|
package/dist/universes.mjs.map
CHANGED
|
@@ -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 { 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\ttypeof body[\"createTime\"] === \"string\" &&\n\t\ttypeof body[\"updateTime\"] === \"string\" &&\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 * @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 * @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 * @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\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 * @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 * @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 * @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,4 +1,4 @@
|
|
|
1
|
-
import { a as OpenCloudError } from "./rate-limit-
|
|
1
|
+
import { a as OpenCloudError } from "./rate-limit-CKfuhxT1.mjs";
|
|
2
2
|
//#region src/errors/validation.ts
|
|
3
3
|
/**
|
|
4
4
|
* Thrown locally when caller-supplied input is rejected before any HTTP
|
|
@@ -35,4 +35,4 @@ var ValidationError = class extends OpenCloudError {
|
|
|
35
35
|
//#endregion
|
|
36
36
|
export { ValidationError as t };
|
|
37
37
|
|
|
38
|
-
//# sourceMappingURL=validation-
|
|
38
|
+
//# sourceMappingURL=validation-b7KAoEio.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation-b7KAoEio.mjs","names":[],"sources":["../src/errors/validation.ts"],"sourcesContent":["import { OpenCloudError } from \"./base.ts\";\n\n/**\n * Closed discriminator for a {@link ValidationError}. Consumers can\n * exhaustively `switch` over this union so TypeScript will refuse to compile\n * if a new variant is added without a handler.\n */\nexport type ValidationErrorCode =\n\t| \"empty_body\"\n\t| \"empty_image_ids\"\n\t| \"empty_update\"\n\t| \"format_mismatch\"\n\t| \"incomplete_ref\"\n\t| \"invalid_image_id\";\n\n/**\n * Options for constructing a {@link ValidationError}.\n */\nexport interface ValidationErrorOptions extends ErrorOptions {\n\t/** Machine-readable discriminator identifying the validation failure. */\n\tcode: ValidationErrorCode;\n}\n\n/**\n * Thrown locally when caller-supplied input is rejected before any HTTP\n * round-trip. The `code` discriminator lets consumers branch on local-input\n * errors separately from server-side errors.\n *\n * @example\n *\n * ```ts\n * import { ValidationError } from \"@bedrock-rbx/ocale\";\n *\n * const error = new ValidationError(\"Place body is empty\", {\n * code: \"empty_body\",\n * });\n *\n * expect(error).toBeInstanceOf(ValidationError);\n * expect(error.code).toBe(\"empty_body\");\n * ```\n */\nexport class ValidationError extends OpenCloudError {\n\tpublic readonly code: ValidationErrorCode;\n\tpublic override readonly name: string = \"ValidationError\";\n\n\t/**\n\t * Creates a new ValidationError.\n\t *\n\t * @param message - Human-readable error description.\n\t * @param options - Error options including the validation failure code.\n\t */\n\tconstructor(message: string, options: ValidationErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.code = options.code;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyCA,IAAa,kBAAb,cAAqC,eAAe;CACnD;CACA,OAAwC;;;;;;;CAQxC,YAAY,SAAiB,SAAiC;AAC7D,QAAM,SAAS,QAAQ;AACvB,OAAK,OAAO,QAAQ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bedrock-rbx/ocale",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.2",
|
|
4
4
|
"description": "Roblox Open Cloud API client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -29,8 +29,11 @@
|
|
|
29
29
|
"./badges": "./dist/badges.mjs",
|
|
30
30
|
"./developer-products": "./dist/developer-products.mjs",
|
|
31
31
|
"./game-passes": "./dist/game-passes.mjs",
|
|
32
|
+
"./locales": "./dist/locales.mjs",
|
|
33
|
+
"./luau-execution": "./dist/luau-execution.mjs",
|
|
32
34
|
"./package.json": "./package.json",
|
|
33
35
|
"./places": "./dist/places.mjs",
|
|
36
|
+
"./storage": "./dist/storage.mjs",
|
|
34
37
|
"./universes": "./dist/universes.mjs"
|
|
35
38
|
},
|
|
36
39
|
"types": "./dist/index.d.mts",
|
|
@@ -49,9 +52,9 @@
|
|
|
49
52
|
"type-fest": "5.6.0",
|
|
50
53
|
"typescript": "npm:@typescript/typescript6@6.0.1",
|
|
51
54
|
"vitest": "4.1.5",
|
|
52
|
-
"@bedrock-rbx/testing": "0.0.0",
|
|
53
55
|
"@bedrock-rbx/typescript-config": "0.0.0",
|
|
54
|
-
"@bedrock-rbx/vite-config": "0.0.0"
|
|
56
|
+
"@bedrock-rbx/vite-config": "0.0.0",
|
|
57
|
+
"@bedrock-rbx/testing": "0.0.0"
|
|
55
58
|
},
|
|
56
59
|
"engines": {
|
|
57
60
|
"node": ">=24.12.0",
|
|
@@ -63,6 +66,7 @@
|
|
|
63
66
|
"scripts": {
|
|
64
67
|
"build": "vp pack",
|
|
65
68
|
"dev": "vp pack --watch",
|
|
69
|
+
"refresh-locales": "bun scripts/fetch-locales.ts",
|
|
66
70
|
"refresh-openapi": "bun scripts/fetch-openapi.ts",
|
|
67
71
|
"test": "vp test",
|
|
68
72
|
"typecheck": "tsgo --noEmit"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit-BBU_4xnZ.mjs","names":[],"sources":["../src/errors/base.ts","../src/errors/api-error.ts","../src/errors/network-error.ts","../src/errors/permission-error.ts","../src/errors/rate-limit.ts"],"sourcesContent":["/**\n * Base error class for all Open Cloud SDK errors.\n *\n * All specific error types (RateLimitError, ApiError, NetworkError)\n * extend this class, enabling `instanceof OpenCloudError` checks.\n */\nexport class OpenCloudError extends Error {\n\tpublic override readonly name: string = \"OpenCloudError\";\n}\n","import { OpenCloudError } from \"./base.ts\";\n\n/**\n * Options for constructing an {@link ApiError}.\n */\nexport interface ApiErrorOptions extends ErrorOptions {\n\t/** Optional machine-readable error code from the API. */\n\tcode?: string | undefined;\n\t/** HTTP status code from the API response. */\n\tstatusCode: number;\n}\n\n/**\n * Thrown when the Roblox Open Cloud API returns a non-2xx response\n * that is not a rate limit (429).\n *\n * @example\n *\n * ```ts\n * import { ApiError } from \"@bedrock-rbx/ocale\";\n *\n * const error = new ApiError(\"Game pass not found\", {\n * code: \"NotFound\",\n * statusCode: 404,\n * });\n *\n * expect(error).toBeInstanceOf(ApiError);\n * expect(error.statusCode).toBe(404);\n * expect(error.code).toBe(\"NotFound\");\n * ```\n */\nexport class ApiError extends OpenCloudError {\n\tpublic readonly code: string | undefined;\n\tpublic override readonly name: string = \"ApiError\";\n\tpublic readonly statusCode: number;\n\n\t/**\n\t * Creates a new ApiError.\n\t *\n\t * @param message - Human-readable error description.\n\t * @param options - Error options including status code and optional error code.\n\t */\n\tconstructor(message: string, options: ApiErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.statusCode = options.statusCode;\n\t\tthis.code = options.code;\n\t}\n}\n","import { OpenCloudError } from \"./base.ts\";\n\n/**\n * Thrown when a network-level failure prevents the request from reaching\n * the Roblox Open Cloud API (e.g., DNS resolution failure, connection timeout).\n */\nexport class NetworkError extends OpenCloudError {\n\tpublic override readonly name: string = \"NetworkError\";\n}\n","import { ApiError, type ApiErrorOptions } from \"./api-error.ts\";\n\n/**\n * Options for constructing a {@link PermissionError}.\n */\nexport interface PermissionErrorOptions extends ApiErrorOptions {\n\t/**\n\t * Stable identifier of the Open Cloud operation that returned the\n\t * permission failure (matches `OperationLimit.operationKey`, e.g.\n\t * `\"developer-products.create\"`).\n\t */\n\toperationKey: string;\n\t/**\n\t * Scope strings the API key or OAuth token must carry for the failing\n\t * operation, sourced from the vendored OpenAPI schema's `x-roblox-scopes`\n\t * for that operationId.\n\t */\n\trequiredScopes: ReadonlyArray<string>;\n}\n\n/**\n * Thrown when the Roblox Open Cloud API returns a 401 or 403 for an operation\n * whose required scopes are known. Subclass of {@link ApiError} carrying the\n * scope strings the caller's credential is missing plus the operation key, so\n * a CLI consumer can tell the user exactly which scope to grant on their API\n * key.\n *\n * @example\n *\n * ```ts\n * import { PermissionError } from \"@bedrock-rbx/ocale\";\n *\n * const error = new PermissionError(\"HTTP 403\", {\n * operationKey: \"developer-products.create\",\n * requiredScopes: [\"creator-store-product:write\"],\n * statusCode: 403,\n * });\n *\n * expect(error).toBeInstanceOf(PermissionError);\n * expect(error.requiredScopes).toStrictEqual([\"creator-store-product:write\"]);\n * expect(error.operationKey).toBe(\"developer-products.create\");\n * ```\n */\nexport class PermissionError extends ApiError {\n\tpublic override readonly name: string = \"PermissionError\";\n\tpublic readonly operationKey: string;\n\tpublic readonly requiredScopes: ReadonlyArray<string>;\n\n\t/**\n\t * Creates a new PermissionError.\n\t *\n\t * @param message - Human-readable error description.\n\t * @param options - Error options including status code, the operation key,\n\t * and the scopes the caller's credential must carry.\n\t */\n\tconstructor(message: string, options: PermissionErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.operationKey = options.operationKey;\n\t\tthis.requiredScopes = options.requiredScopes;\n\t}\n}\n","import { OpenCloudError } from \"./base.ts\";\n\n/**\n * Options for constructing a {@link RateLimitError}.\n */\nexport interface RateLimitErrorOptions extends ErrorOptions {\n\t/** Seconds to wait before retrying the request. */\n\tretryAfterSeconds: number;\n}\n\n/**\n * Thrown when the Roblox Open Cloud API returns a 429 Too Many Requests response.\n * Contains the server-suggested retry delay.\n *\n * @example\n *\n * ```ts\n * import { RateLimitError } from \"@bedrock-rbx/ocale\";\n *\n * const error = new RateLimitError(\"Too many requests\", {\n * retryAfterSeconds: 30,\n * });\n *\n * expect(error).toBeInstanceOf(RateLimitError);\n * expect(error.retryAfterSeconds).toBe(30);\n * ```\n */\nexport class RateLimitError extends OpenCloudError {\n\tpublic override readonly name = \"RateLimitError\";\n\tpublic readonly retryAfterSeconds: number;\n\n\t/**\n\t * Creates a new RateLimitError.\n\t *\n\t * @param message - Human-readable error description.\n\t * @param options - Error options including the retry delay.\n\t */\n\tconstructor(message: string, options: RateLimitErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.retryAfterSeconds = options.retryAfterSeconds;\n\t}\n}\n"],"mappings":";;;;;;;AAMA,IAAa,iBAAb,cAAoC,MAAM;CACzC,OAAwC;;;;;;;;;;;;;;;;;;;;;;;ACwBzC,IAAa,WAAb,cAA8B,eAAe;CAC5C;CACA,OAAwC;CACxC;;;;;;;CAQA,YAAY,SAAiB,SAA0B;AACtD,QAAM,SAAS,QAAQ;AACvB,OAAK,aAAa,QAAQ;AAC1B,OAAK,OAAO,QAAQ;;;;;;;;;ACvCtB,IAAa,eAAb,cAAkC,eAAe;CAChD,OAAwC;;;;;;;;;;;;;;;;;;;;;;;;;;;ACoCzC,IAAa,kBAAb,cAAqC,SAAS;CAC7C,OAAwC;CACxC;CACA;;;;;;;;CASA,YAAY,SAAiB,SAAiC;AAC7D,QAAM,SAAS,QAAQ;AACvB,OAAK,eAAe,QAAQ;AAC5B,OAAK,iBAAiB,QAAQ;;;;;;;;;;;;;;;;;;;;;;AC/BhC,IAAa,iBAAb,cAAoC,eAAe;CAClD,OAAgC;CAChC;;;;;;;CAQA,YAAY,SAAiB,SAAgC;AAC5D,QAAM,SAAS,QAAQ;AACvB,OAAK,oBAAoB,QAAQ"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"resource-client-CaS_j3yg.mjs","names":["#hooks","#intervalMs","#maxBucketLevel","#sleep","#chain","#waitForToken","#lastCheck","#bucketLevel","#config","#hooks","#httpClient","#queues","#sleep","#getQueue"],"sources":["../src/internal/utils/is-record.ts","../src/internal/http/retry.ts","../src/internal/http/execute.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/resource-client.ts"],"sourcesContent":["/**\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 { ApiError } from \"../../errors/api-error.ts\";\nimport { RateLimitError } from \"../../errors/rate-limit.ts\";\n\n/**\n * Fully-resolved retry config shape that {@link mergeConfig} and\n * {@link shouldRetry} operate on. Fields are required because this represents\n * the post-defaulting, internal view — callers should supply every field (or\n * resolve them via a test factory / client constructor). The partial,\n * user-facing type lives on client construction options; method defaults and\n * per-request overrides use `Partial<RetryResolvable>`.\n */\nexport interface RetryResolvable {\n\t/** Roblox Open Cloud API key. */\n\treadonly apiKey: string;\n\t/** Base URL for the Open Cloud API. */\n\treadonly baseUrl: string;\n\t/** Maximum retry attempts before giving up. */\n\treadonly maxRetries: number;\n\t/** Status codes that are eligible for retry. */\n\treadonly retryableStatuses: ReadonlyArray<number>;\n\t/** Fallback delay function when no server hint is available. */\n\treadonly retryDelay: (attempt: number) => number;\n\t/** Per-request timeout in milliseconds. */\n\treadonly timeout: number;\n}\n\n/**\n * Default retry status codes for idempotent operations (read, list, update,\n * delete). Safe to retry on both rate limits and transient server errors.\n */\nexport const IDEMPOTENT_METHOD_DEFAULTS: Readonly<Pick<RetryResolvable, \"retryableStatuses\">> =\n\tObject.freeze({\n\t\tretryableStatuses: Object.freeze([429, 500, 502, 503, 504] as const),\n\t});\n\n/**\n * Default retry status codes for create operations. Retries rate limits only,\n * to prevent duplicate resources on 5xx (Roblox Open Cloud has no\n * idempotency-key support).\n */\nexport const CREATE_METHOD_DEFAULTS: Readonly<Pick<RetryResolvable, \"retryableStatuses\">> =\n\tObject.freeze({\n\t\tretryableStatuses: Object.freeze([429] as const),\n\t});\n\n/** Kind of HTTP method the merge is being performed for. */\nexport type MethodKind = \"create\" | \"idempotent\";\n\n/**\n * Options for {@link mergeConfig}.\n *\n * @template T - Concrete `RetryResolvable` subtype being merged.\n */\ninterface MergeConfigOptions<T> {\n\t/** Method-level defaults (e.g. {@link CREATE_METHOD_DEFAULTS}). */\n\treadonly methodDefaults: Partial<T>;\n\t/** Whether the method is a create or idempotent operation. */\n\treadonly methodKind: MethodKind;\n\t/** Optional per-request overrides; always win when provided. */\n\treadonly requestOptions?: Partial<T>;\n}\n\n/**\n * Options for {@link computeRetryWaitMs}.\n */\ninterface ComputeRetryWaitMsOptions {\n\t/** Zero-indexed retry attempt number. */\n\treadonly attempt: number;\n\t/** Fallback delay function when no server hint is available. */\n\treadonly retryDelay: (attempt: number) => number;\n}\n\n/**\n * Default exponential backoff: 1s → 2s → 4s → 8s → 16s → 30s (capped).\n *\n * @example\n *\n * ```ts\n * import { defaultRetryDelay } from \"./retry\";\n *\n * expect(defaultRetryDelay(0)).toBe(1000);\n * expect(defaultRetryDelay(4)).toBe(16_000);\n * expect(defaultRetryDelay(10)).toBe(30_000);\n * ```\n *\n * @param attempt - Zero-indexed retry attempt number.\n * @returns Wait duration in milliseconds.\n */\nexport function defaultRetryDelay(attempt: number): number {\n\treturn Math.min(1000 * 2 ** attempt, 30_000);\n}\n\n/**\n * Computes how long to wait before the next retry. Prefers the server's\n * suggested delay when the error is a {@link RateLimitError} with a positive\n * `retryAfterSeconds`; otherwise falls through to `retryDelay(attempt)`.\n *\n * @example\n *\n * ```ts\n * import { RateLimitError } from \"../../errors/rate-limit.ts\";\n * import { computeRetryWaitMs, defaultRetryDelay } from \"./retry\";\n *\n * const error = new RateLimitError(\"slow down\", { retryAfterSeconds: 3 });\n *\n * expect(computeRetryWaitMs(error, { attempt: 0, retryDelay: defaultRetryDelay })).toBe(\n * 3000,\n * );\n * ```\n *\n * @example\n *\n * ```ts\n * import { ApiError } from \"../../errors/api-error.ts\";\n * import { computeRetryWaitMs, defaultRetryDelay } from \"./retry\";\n *\n * const error = new ApiError(\"server error\", { statusCode: 503 });\n *\n * expect(computeRetryWaitMs(error, { attempt: 2, retryDelay: defaultRetryDelay })).toBe(\n * 4000,\n * );\n * ```\n *\n * @param error - The error returned by the failing request.\n * @param options - Retry attempt index and fallback delay function.\n * @returns Wait duration in milliseconds before the next attempt.\n */\nexport function computeRetryWaitMs(\n\terror: ApiError | RateLimitError,\n\toptions: ComputeRetryWaitMsOptions,\n): number {\n\tif (error instanceof RateLimitError && error.retryAfterSeconds > 0) {\n\t\treturn error.retryAfterSeconds * 1000;\n\t}\n\n\treturn options.retryDelay(options.attempt);\n}\n\n/**\n * Decides whether a failed request is eligible for retry under the given\n * `retryableStatuses`. Only {@link RateLimitError} (checked against 429) and\n * {@link ApiError} (checked against its `statusCode`) are retryable — network\n * errors and other failures always return `false`.\n *\n * @example\n *\n * ```ts\n * import { RateLimitError } from \"../../errors/rate-limit.ts\";\n * import { shouldRetry } from \"./retry\";\n *\n * const error = new RateLimitError(\"\", { retryAfterSeconds: 1 });\n *\n * expect(shouldRetry(error, { retryableStatuses: [429] })).toBe(true);\n * ```\n *\n * @example\n *\n * ```ts\n * import { ApiError } from \"../../errors/api-error.ts\";\n * import { shouldRetry } from \"./retry\";\n *\n * const error = new ApiError(\"\", { statusCode: 503 });\n *\n * expect(shouldRetry(error, { retryableStatuses: [429, 500, 502, 503, 504] })).toBe(\n * true,\n * );\n * ```\n *\n * @example\n *\n * ```ts\n * import { NetworkError } from \"../../errors/network-error.ts\";\n * import { shouldRetry } from \"./retry\";\n *\n * const error = new NetworkError(\"offline\");\n *\n * expect(shouldRetry(error, { retryableStatuses: [429] })).toBe(false);\n * ```\n *\n * @param error - The error returned by the failing request.\n * @param config - Object carrying the retry-eligible status list.\n * @returns `true` if the error should be retried, `false` otherwise.\n */\nexport function shouldRetry(\n\terror: unknown,\n\tconfig: { readonly retryableStatuses: ReadonlyArray<number> },\n): error is ApiError | RateLimitError {\n\tif (error instanceof RateLimitError) {\n\t\treturn config.retryableStatuses.includes(429);\n\t}\n\n\tif (error instanceof ApiError) {\n\t\treturn config.retryableStatuses.includes(error.statusCode);\n\t}\n\n\treturn false;\n}\n\n/**\n * Resolves the effective config for a single request by shallow-merging the\n * client config, method defaults, and per-request options. Precedence depends\n * on `methodKind`:\n *\n * - `\"create\"`: method defaults override client config, so client-level\n * settings cannot silently relax create-method safety. Only explicit\n * per-request `requestOptions` can.\n * - `\"idempotent\"`: client config overrides method defaults, so consumers\n * can loosen or tighten retry policy globally. `requestOptions` still wins\n * when provided.\n *\n * Array-valued fields like `retryableStatuses` are *replaced*, not extended.\n *\n * @template T - Concrete `RetryResolvable` subtype being merged.\n *\n * @example\n *\n * ```ts\n * import {\n * CREATE_METHOD_DEFAULTS,\n * defaultRetryDelay,\n * mergeConfig,\n * type RetryResolvable,\n * } from \"./retry\";\n *\n * const clientConfig: RetryResolvable = {\n * apiKey: \"k\",\n * baseUrl: \"https://apis.roblox.com\",\n * maxRetries: 3,\n * retryableStatuses: [429, 500],\n * retryDelay: defaultRetryDelay,\n * timeout: 30_000,\n * };\n *\n * const merged = mergeConfig(clientConfig, {\n * methodDefaults: CREATE_METHOD_DEFAULTS,\n * methodKind: \"create\",\n * });\n *\n * expect(merged.retryableStatuses).toStrictEqual([429]);\n * ```\n *\n * @example\n *\n * ```ts\n * import {\n * defaultRetryDelay,\n * IDEMPOTENT_METHOD_DEFAULTS,\n * mergeConfig,\n * type RetryResolvable,\n * } from \"./retry\";\n *\n * const clientConfig: RetryResolvable = {\n * apiKey: \"k\",\n * baseUrl: \"https://apis.roblox.com\",\n * maxRetries: 3,\n * retryableStatuses: [429],\n * retryDelay: defaultRetryDelay,\n * timeout: 30_000,\n * };\n *\n * const merged = mergeConfig(clientConfig, {\n * methodDefaults: IDEMPOTENT_METHOD_DEFAULTS,\n * methodKind: \"idempotent\",\n * requestOptions: { timeout: 10_000 },\n * });\n *\n * expect(merged.retryableStatuses).toStrictEqual([429]);\n * expect(merged.timeout).toBe(10_000);\n * ```\n *\n * @param clientConfig - Config frozen at client construction.\n * @param options - Method defaults, method kind, and optional per-request overrides.\n * @returns A new merged config object. Inputs are not mutated.\n */\nexport function mergeConfig<T extends RetryResolvable>(\n\tclientConfig: T,\n\toptions: MergeConfigOptions<T>,\n): T {\n\tconst { methodDefaults, methodKind, requestOptions } = options;\n\n\tswitch (methodKind) {\n\t\tcase \"create\": {\n\t\t\treturn { ...clientConfig, ...methodDefaults, ...requestOptions };\n\t\t}\n\t\tcase \"idempotent\": {\n\t\t\treturn { ...methodDefaults, ...clientConfig, ...requestOptions };\n\t\t}\n\t\tdefault: {\n\t\t\tconst exhaustive: never = methodKind;\n\t\t\tthrow new Error(`Unexpected methodKind: ${String(exhaustive)}`);\n\t\t}\n\t}\n}\n","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","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 type { HttpClient, HttpRequest, HttpResponse, RequestConfig } from \"./types.ts\";\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 top-level `errorCode` string field from a\n * response body.\n *\n * @param body - The parsed response body (unknown shape).\n * @returns The `errorCode` string 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: unknown = Reflect.get(body, \"errorCode\");\n\treturn typeof errorCode === \"string\" ? errorCode : undefined;\n}\n\n/**\n * Parses the `x-ratelimit-reset` header value into seconds.\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\tconst parsed = Number(headerValue);\n\tif (Number.isNaN(parsed)) {\n\t\treturn 0;\n\t}\n\n\treturn Math.max(0, Math.floor(parsed));\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\", \"application/octet-stream\");\n\t\toptions.body = request.body;\n\t} else if (request.body !== undefined) {\n\t\theaders.set(\"content-type\", \"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\", { cause: fetchResult.err }),\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 createApiError(status: number, body: unknown): ApiError {\n\treturn new ApiError(`HTTP ${status}`, {\n\t\tcode: extractErrorCode(body),\n\t\tstatusCode: status,\n\t});\n}\n\n/**\n * Classifies a fetch `Response` into a typed `Result`.\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 */\nfunction createRateLimitError(response: Response): RateLimitError {\n\treturn new RateLimitError(\"Rate limited\", {\n\t\tretryAfterSeconds: parseRetryAfterSeconds(\n\t\t\tresponse.headers.get(\"x-ratelimit-reset\") ?? undefined,\n\t\t),\n\t});\n}\n\nasync function readResponseBody(response: Response): Promise<Result<unknown, OpenCloudError>> {\n\ttry {\n\t\tconst text = await response.text();\n\t\treturn { data: text === \"\" ? undefined : JSON.parse(text), success: true };\n\t} catch {\n\t\treturn {\n\t\t\terr: new ApiError(\"Failed to parse response body\", { statusCode: response.status }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n}\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 bodyResult = await readResponseBody(response);\n\tif (!bodyResult.success) {\n\t\treturn bodyResult;\n\t}\n\n\tif (response.status >= 300) {\n\t\treturn { err: createApiError(response.status, bodyResult.data), success: false };\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tbody: bodyResult.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 { Except } from \"type-fest\";\n\nimport type {\n\tHttpClient,\n\tHttpRequest,\n\tHttpResponse,\n\tOpenCloudClientOptions,\n\tOpenCloudHooks,\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 { executeWithRetry } from \"./http/execute.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\";\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\tretryDelay: defaultRetryDelay,\n\ttimeout: 30_000,\n} satisfies Except<RetryResolvable, \"apiKey\">);\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 #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.#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 = {\n\t\t\tapiKey: merged.apiKey,\n\t\t\tbaseUrl: merged.baseUrl,\n\t\t\ttimeout: merged.timeout,\n\t\t};\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: async (toSend) => this.#httpClient.request(toSend, 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#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\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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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validation-CTZzJhmd.mjs","names":[],"sources":["../src/errors/validation.ts"],"sourcesContent":["import { OpenCloudError } from \"./base.ts\";\n\n/**\n * Closed discriminator for a {@link ValidationError}. Consumers can\n * exhaustively `switch` over this union so TypeScript will refuse to compile\n * if a new variant is added without a handler.\n */\nexport type ValidationErrorCode =\n\t| \"empty_body\"\n\t| \"empty_image_ids\"\n\t| \"empty_update\"\n\t| \"format_mismatch\"\n\t| \"invalid_image_id\";\n\n/**\n * Options for constructing a {@link ValidationError}.\n */\nexport interface ValidationErrorOptions extends ErrorOptions {\n\t/** Machine-readable discriminator identifying the validation failure. */\n\tcode: ValidationErrorCode;\n}\n\n/**\n * Thrown locally when caller-supplied input is rejected before any HTTP\n * round-trip. The `code` discriminator lets consumers branch on local-input\n * errors separately from server-side errors.\n *\n * @example\n *\n * ```ts\n * import { ValidationError } from \"@bedrock-rbx/ocale\";\n *\n * const error = new ValidationError(\"Place body is empty\", {\n * code: \"empty_body\",\n * });\n *\n * expect(error).toBeInstanceOf(ValidationError);\n * expect(error.code).toBe(\"empty_body\");\n * ```\n */\nexport class ValidationError extends OpenCloudError {\n\tpublic readonly code: ValidationErrorCode;\n\tpublic override readonly name: string = \"ValidationError\";\n\n\t/**\n\t * Creates a new ValidationError.\n\t *\n\t * @param message - Human-readable error description.\n\t * @param options - Error options including the validation failure code.\n\t */\n\tconstructor(message: string, options: ValidationErrorOptions) {\n\t\tsuper(message, options);\n\t\tthis.code = options.code;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwCA,IAAa,kBAAb,cAAqC,eAAe;CACnD;CACA,OAAwC;;;;;;;CAQxC,YAAY,SAAiB,SAAiC;AAC7D,QAAM,SAAS,QAAQ;AACvB,OAAK,OAAO,QAAQ"}
|