@bedrock-rbx/ocale 0.1.0-beta.1

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.
@@ -0,0 +1,705 @@
1
+ import { i as ApiError } from "./rate-limit-BBU_4xnZ.mjs";
2
+ import { t as ValidationError } from "./validation-CTZzJhmd.mjs";
3
+ import { t as toBlob } from "./to-blob-1BtHsDGK.mjs";
4
+ 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-CaS_j3yg.mjs";
5
+ //#region src/domains/cloud-v2/universes/builders.ts
6
+ /**
7
+ * Dodges `unicorn/no-null` while still emitting a literal `null` onto
8
+ * the wire, which the Open Cloud `Cloud_UpdateUniverse` endpoint
9
+ * requires to clear a nullable field (for example disabling private
10
+ * servers or removing a social link).
11
+ */
12
+ const NULL_SENTINEL = JSON.parse("null");
13
+ /**
14
+ * Builds a `GET` request for the Open Cloud "get universe" endpoint.
15
+ *
16
+ * @param parameters - The universe identifier.
17
+ * @returns A success result wrapping the request; the builder cannot fail.
18
+ */
19
+ function buildGetRequest(parameters) {
20
+ return okRequest({
21
+ method: "GET",
22
+ url: `/cloud/v2/universes/${parameters.universeId}`
23
+ });
24
+ }
25
+ /**
26
+ * Builds a `PATCH` request for the Open Cloud "update universe"
27
+ * endpoint. Derives the `updateMask` query string from the keys
28
+ * present on `parameters` and emits a JSON body containing those same
29
+ * fields, translating `undefined` values to JSON `null` so Roblox
30
+ * clears the corresponding server-side value.
31
+ *
32
+ * @param parameters - The universe identifier plus the fields to update.
33
+ * @returns A success result wrapping the request, or a
34
+ * {@link ValidationError} when no updatable fields were supplied.
35
+ */
36
+ function buildUpdateRequest(parameters) {
37
+ const fieldKeys = extractUpdateFieldKeys(parameters);
38
+ if (fieldKeys.length === 0) return {
39
+ err: new ValidationError("Update must include at least one field", { code: "empty_update" }),
40
+ success: false
41
+ };
42
+ const body = {};
43
+ for (const key of fieldKeys) body[key] = bodyValueFor(parameters, key);
44
+ const updateMask = fieldKeys.join(",");
45
+ return {
46
+ data: {
47
+ body,
48
+ headers: { "content-type": "application/json" },
49
+ method: "PATCH",
50
+ url: `/cloud/v2/universes/${parameters.universeId}?updateMask=${updateMask}`
51
+ },
52
+ success: true
53
+ };
54
+ }
55
+ function extractUpdateFieldKeys(parameters) {
56
+ return Object.keys(parameters).filter((key) => key !== "universeId");
57
+ }
58
+ function bodyValueFor(parameters, key) {
59
+ const value = Reflect.get(parameters, key);
60
+ return value === void 0 ? NULL_SENTINEL : value;
61
+ }
62
+ //#endregion
63
+ //#region src/domains/cloud-v2/universes/operations.ts
64
+ const PER_MINUTE = 100;
65
+ const SECONDS_PER_MINUTE = 60;
66
+ /**
67
+ * Per-second request ceiling for reading a universe, from the Open
68
+ * Cloud OpenAPI schema (100 requests per minute per API key owner).
69
+ */
70
+ const GET_OPERATION_LIMIT = Object.freeze({
71
+ maxPerSecond: PER_MINUTE / SECONDS_PER_MINUTE,
72
+ operationKey: "universes.get"
73
+ });
74
+ /**
75
+ * Per-second request ceiling for updating a universe, from the Open
76
+ * Cloud OpenAPI schema (100 requests per minute per API key owner).
77
+ * Keyed independently from {@link GET_OPERATION_LIMIT} so reads and
78
+ * updates do not share a queue; upstream quota accounting is not
79
+ * documented as shared and the conservative default is fewer
80
+ * cross-method contention surprises.
81
+ */
82
+ const UPDATE_OPERATION_LIMIT = Object.freeze({
83
+ maxPerSecond: PER_MINUTE / SECONDS_PER_MINUTE,
84
+ operationKey: "universes.update"
85
+ });
86
+ /**
87
+ * Scopes required to update a universe, sourced from `x-roblox-scopes`
88
+ * on the `Cloud_UpdateUniverse` operation in the vendored OpenAPI schema.
89
+ * `Cloud_GetUniverse` declares no scope, so the GET method intentionally
90
+ * does not declare `requiredScopes` and a 401/403 there surfaces as a
91
+ * generic ApiError.
92
+ */
93
+ const UPDATE_REQUIRED_SCOPES = Object.freeze(["universe:write"]);
94
+ //#endregion
95
+ //#region src/domains/cloud-v2/universes/parsers.ts
96
+ const VISIBILITY_MAP = {
97
+ PRIVATE: "private",
98
+ PUBLIC: "public",
99
+ VISIBILITY_UNSPECIFIED: "unspecified"
100
+ };
101
+ const AGE_RATING_MAP = {
102
+ AGE_RATING_9_PLUS: "9Plus",
103
+ AGE_RATING_13_PLUS: "13Plus",
104
+ AGE_RATING_17_PLUS: "17Plus",
105
+ AGE_RATING_ALL: "all",
106
+ AGE_RATING_UNSPECIFIED: "unspecified"
107
+ };
108
+ const MALFORMED_MESSAGE = "Malformed universe response";
109
+ /**
110
+ * Parses a successful Open Cloud `Universe` response body into the
111
+ * public {@link Universe} shape.
112
+ *
113
+ * @param response - The full {@link HttpResponse} from the Open Cloud API.
114
+ * @returns A success result wrapping the parsed {@link Universe}, or
115
+ * an {@link ApiError} when the body does not match the wire schema.
116
+ */
117
+ function parseUniverseResponse(response) {
118
+ const { body, status: statusCode } = response;
119
+ if (!isUniverseWire(body)) return malformed(statusCode);
120
+ const ownerResult = resolveOwner(body);
121
+ if (!ownerResult.success) return malformed(statusCode);
122
+ const id = /^universes\/(\d+)$/.exec(body.path)?.[1];
123
+ if (id === void 0) return malformed(statusCode);
124
+ return {
125
+ data: toUniverse({
126
+ id,
127
+ body,
128
+ owner: ownerResult.data
129
+ }),
130
+ success: true
131
+ };
132
+ }
133
+ function malformed(statusCode) {
134
+ return {
135
+ err: new ApiError(MALFORMED_MESSAGE, { statusCode }),
136
+ success: false
137
+ };
138
+ }
139
+ function extractRootPlaceId(rootPlace) {
140
+ if (rootPlace === void 0) return;
141
+ return /\/places\/(\d+)$/.exec(rootPlace)?.[1];
142
+ }
143
+ function toSocialLink(wire) {
144
+ if (wire === void 0) return;
145
+ return {
146
+ title: wire.title,
147
+ uri: wire.uri
148
+ };
149
+ }
150
+ function toUniverse(args) {
151
+ const { id, body, owner } = args;
152
+ return {
153
+ id,
154
+ ageRating: AGE_RATING_MAP[body.ageRating],
155
+ consoleEnabled: body.consoleEnabled ?? false,
156
+ createdAt: new Date(body.createTime),
157
+ description: body.description,
158
+ desktopEnabled: body.desktopEnabled ?? false,
159
+ discordSocialLink: toSocialLink(body.discordSocialLink),
160
+ displayName: body.displayName,
161
+ facebookSocialLink: toSocialLink(body.facebookSocialLink),
162
+ guildedSocialLink: toSocialLink(body.guildedSocialLink),
163
+ mobileEnabled: body.mobileEnabled ?? false,
164
+ owner,
165
+ privateServerPriceRobux: body.privateServerPriceRobux ?? void 0,
166
+ robloxGroupSocialLink: toSocialLink(body.robloxGroupSocialLink),
167
+ rootPlaceId: extractRootPlaceId(body.rootPlace),
168
+ tabletEnabled: body.tabletEnabled ?? false,
169
+ twitchSocialLink: toSocialLink(body.twitchSocialLink),
170
+ twitterSocialLink: toSocialLink(body.twitterSocialLink),
171
+ updatedAt: new Date(body.updateTime),
172
+ visibility: VISIBILITY_MAP[body.visibility],
173
+ voiceChatEnabled: body.voiceChatEnabled ?? false,
174
+ vrEnabled: body.vrEnabled ?? false,
175
+ youtubeSocialLink: toSocialLink(body.youtubeSocialLink)
176
+ };
177
+ }
178
+ function isVisibilityWire(value) {
179
+ return value === "PRIVATE" || value === "PUBLIC" || value === "VISIBILITY_UNSPECIFIED";
180
+ }
181
+ function isAgeRatingWire(value) {
182
+ 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
+ function hasValidRequiredFields(body) {
185
+ return typeof body["path"] === "string" && typeof body["createTime"] === "string" && typeof body["updateTime"] === "string" && typeof body["displayName"] === "string" && typeof body["description"] === "string" && isVisibilityWire(body["visibility"]) && isAgeRatingWire(body["ageRating"]);
186
+ }
187
+ function isSocialLinkWire(value) {
188
+ if (!isRecord(value)) return false;
189
+ return typeof value["title"] === "string" && typeof value["uri"] === "string";
190
+ }
191
+ function isOptionalSocialLink(value) {
192
+ return value === void 0 || value === null || isSocialLinkWire(value);
193
+ }
194
+ function isOptionalBoolean(value) {
195
+ return value === void 0 || value === null || typeof value === "boolean";
196
+ }
197
+ function hasValidOptionalFields(body) {
198
+ const priceField = body["privateServerPriceRobux"] ?? void 0;
199
+ if (priceField !== void 0 && typeof priceField !== "number") return false;
200
+ const rootPlace = body["rootPlace"] ?? void 0;
201
+ if (rootPlace !== void 0 && typeof rootPlace !== "string") return false;
202
+ return isOptionalBoolean(body["voiceChatEnabled"]) && isOptionalBoolean(body["desktopEnabled"]) && isOptionalBoolean(body["mobileEnabled"]) && isOptionalBoolean(body["tabletEnabled"]) && isOptionalBoolean(body["consoleEnabled"]) && isOptionalBoolean(body["vrEnabled"]) && isOptionalSocialLink(body["facebookSocialLink"]) && isOptionalSocialLink(body["twitterSocialLink"]) && isOptionalSocialLink(body["youtubeSocialLink"]) && isOptionalSocialLink(body["twitchSocialLink"]) && isOptionalSocialLink(body["discordSocialLink"]) && isOptionalSocialLink(body["robloxGroupSocialLink"]) && isOptionalSocialLink(body["guildedSocialLink"]);
203
+ }
204
+ function isUniverseWire(body) {
205
+ if (!isRecord(body)) return false;
206
+ return hasValidRequiredFields(body) && hasValidOptionalFields(body);
207
+ }
208
+ function extractOwnerId(resourcePath) {
209
+ return /^(?:users|groups)\/(\d+)$/.exec(resourcePath)?.[1];
210
+ }
211
+ function resolveOwner(body) {
212
+ if (typeof body.user === "string") {
213
+ const id = extractOwnerId(body.user);
214
+ if (id !== void 0) return {
215
+ data: {
216
+ id,
217
+ kind: "user"
218
+ },
219
+ success: true
220
+ };
221
+ }
222
+ if (typeof body.group === "string") {
223
+ const id = extractOwnerId(body.group);
224
+ if (id !== void 0) return {
225
+ data: {
226
+ id,
227
+ kind: "group"
228
+ },
229
+ success: true
230
+ };
231
+ }
232
+ return {
233
+ err: void 0,
234
+ success: false
235
+ };
236
+ }
237
+ //#endregion
238
+ //#region src/domains/game-internationalization/game-icon/builders.ts
239
+ /**
240
+ * Builds a `POST` request for the localized "upload experience icon"
241
+ * endpoint. A successful upload replaces any existing icon for the same
242
+ * `(universeId, languageCode)` pair.
243
+ *
244
+ * @param parameters - Universe and language identifiers plus the image
245
+ * bytes to upload.
246
+ * @returns A pure {@link HttpRequest} describing the upload call.
247
+ */
248
+ function buildUploadIconRequest(parameters) {
249
+ const body = new FormData();
250
+ body.append("request.files", toBlob(parameters.image));
251
+ return {
252
+ body,
253
+ method: "POST",
254
+ url: `/legacy-game-internationalization/v1/game-icon/games/${parameters.universeId}/language-codes/${parameters.languageCode}`
255
+ };
256
+ }
257
+ /**
258
+ * Builds a `DELETE` request for the localized "delete experience icon"
259
+ * endpoint. Removing the source-language icon is rejected server-side;
260
+ * deleting the icon for a non-source language clears that translation.
261
+ *
262
+ * @param parameters - Universe and language identifiers of the icon to
263
+ * delete.
264
+ * @returns A pure {@link HttpRequest} describing the delete call.
265
+ */
266
+ function buildDeleteIconRequest(parameters) {
267
+ return {
268
+ method: "DELETE",
269
+ url: `/legacy-game-internationalization/v1/game-icon/games/${parameters.universeId}/language-codes/${parameters.languageCode}`
270
+ };
271
+ }
272
+ /**
273
+ * Builds a `GET` request for the "list experience icons" endpoint. The
274
+ * server returns one entry per locale that has an icon registered.
275
+ *
276
+ * @param parameters - Universe identifier whose icons to list.
277
+ * @returns A pure {@link HttpRequest} describing the list call.
278
+ */
279
+ function buildListIconsRequest(parameters) {
280
+ return {
281
+ method: "GET",
282
+ url: `/legacy-game-internationalization/v1/game-icon/games/${parameters.universeId}`
283
+ };
284
+ }
285
+ //#endregion
286
+ //#region src/domains/game-internationalization/game-icon/operations.ts
287
+ /**
288
+ * Per-second request ceiling for every game-icon Operation bound on
289
+ * `UniversesClient.icon`. The legacy `gameinternationalization` service caps
290
+ * each API key at 100 requests per minute *shared across the entire service*
291
+ * (see the `x-roblox-rate-limits` extension on every operation in the
292
+ * vendored Open Cloud spec), so all methods queue against the same operation
293
+ * key.
294
+ */
295
+ const ICON_OPERATION_LIMIT = Object.freeze({
296
+ maxPerSecond: 100 / 60,
297
+ operationKey: "experience-icon"
298
+ });
299
+ /**
300
+ * Scopes required for every game-icon operation, sourced from
301
+ * `x-roblox-scopes` on the legacy `gameinternationalization` icon
302
+ * endpoints in the vendored OpenAPI schema.
303
+ */
304
+ const ICON_REQUIRED_SCOPES = Object.freeze(["legacy-universe:manage"]);
305
+ //#endregion
306
+ //#region src/domains/game-internationalization/game-icon/parsers.ts
307
+ /**
308
+ * Parses a successful icon-list response into a public array of
309
+ * {@link ExperienceIcon} entries.
310
+ *
311
+ * @param response - The full {@link HttpResponse} from the Open Cloud API.
312
+ * @returns A success result wrapping the converted icon list, or an
313
+ * `ApiError` when the body does not match the wire schema.
314
+ */
315
+ function parseIconListResponse(response) {
316
+ const { body, status: statusCode } = response;
317
+ if (!isGameIconListWire(body)) return {
318
+ err: new ApiError("Malformed icon list response", { statusCode }),
319
+ success: false
320
+ };
321
+ return {
322
+ data: body.data.map(toExperienceIcon),
323
+ success: true
324
+ };
325
+ }
326
+ function isGameIconState(value) {
327
+ return value === "Approved" || value === "Error" || value === "PendingReview" || value === "Rejected" || value === "UnAvailable";
328
+ }
329
+ function isGetGameIconResponseWire(value) {
330
+ if (!isRecord(value)) return false;
331
+ return typeof value["imageId"] === "string" && typeof value["imageUrl"] === "string" && typeof value["languageCode"] === "string" && isGameIconState(value["state"]);
332
+ }
333
+ function isGameIconListWire(body) {
334
+ if (!isRecord(body)) return false;
335
+ const { data } = body;
336
+ if (!Array.isArray(data)) return false;
337
+ return data.every(isGetGameIconResponseWire);
338
+ }
339
+ function toExperienceIcon(wire) {
340
+ return {
341
+ imageId: wire.imageId,
342
+ imageUrl: wire.imageUrl,
343
+ languageCode: wire.languageCode,
344
+ state: wire.state
345
+ };
346
+ }
347
+ //#endregion
348
+ //#region src/domains/game-internationalization/game-thumbnails/builders.ts
349
+ /**
350
+ * Builds a `POST` request for the localized "upload experience thumbnail"
351
+ * endpoint. Each successful upload appends a new entry to the carousel.
352
+ *
353
+ * @param parameters - Universe and language identifiers plus the image
354
+ * bytes to upload.
355
+ * @returns A pure {@link HttpRequest} describing the upload call.
356
+ */
357
+ function buildUploadThumbnailRequest(parameters) {
358
+ const body = new FormData();
359
+ body.append("gameThumbnailRequest.files", toBlob(parameters.image));
360
+ return {
361
+ body,
362
+ method: "POST",
363
+ url: `/legacy-game-internationalization/v1/game-thumbnails/games/${parameters.universeId}/language-codes/${parameters.languageCode}/image`
364
+ };
365
+ }
366
+ /**
367
+ * Builds a `DELETE` request for the "delete experience thumbnail" endpoint.
368
+ *
369
+ * @param parameters - Universe, language, and image identifiers of the
370
+ * thumbnail to delete.
371
+ * @returns A pure {@link HttpRequest} describing the delete call.
372
+ */
373
+ function buildDeleteThumbnailRequest(parameters) {
374
+ return {
375
+ method: "DELETE",
376
+ url: `/legacy-game-internationalization/v1/game-thumbnails/games/${parameters.universeId}/language-codes/${parameters.languageCode}/images/${parameters.imageId}`
377
+ };
378
+ }
379
+ /**
380
+ * Builds a `POST` request for the "reorder experience thumbnails" endpoint.
381
+ * Validates each supplied image ID at the wire boundary so a typo cannot
382
+ * silently serialize as JSON `null` and corrupt the request.
383
+ *
384
+ * @param parameters - Universe, language, and the desired display order.
385
+ * @returns A success result wrapping the request, or a
386
+ * {@link ValidationError} when `orderedImageIds` is empty or any ID is not
387
+ * a positive integer within the safe-integer range.
388
+ */
389
+ function buildReorderThumbnailsRequest(parameters) {
390
+ const { languageCode, orderedImageIds, universeId } = parameters;
391
+ const idsResult = parseOrderedImageIds(orderedImageIds);
392
+ if (!idsResult.success) return idsResult;
393
+ return {
394
+ data: {
395
+ body: { mediaAssetIds: idsResult.data },
396
+ method: "POST",
397
+ url: `/legacy-game-internationalization/v1/game-thumbnails/games/${universeId}/language-codes/${languageCode}/images/order`
398
+ },
399
+ success: true
400
+ };
401
+ }
402
+ function parseImageId(value) {
403
+ if (!/^[1-9]\d*$/.test(value)) return;
404
+ const parsed = Number(value);
405
+ if (!Number.isSafeInteger(parsed)) return;
406
+ return parsed;
407
+ }
408
+ function appendParsedId(accumulator, id) {
409
+ if (!accumulator.success) return accumulator;
410
+ const parsed = parseImageId(id);
411
+ if (parsed === void 0) return {
412
+ err: new ValidationError(`orderedImageIds entry ${JSON.stringify(id)} is not a positive integer ID`, { code: "invalid_image_id" }),
413
+ success: false
414
+ };
415
+ return {
416
+ data: [...accumulator.data, parsed],
417
+ success: true
418
+ };
419
+ }
420
+ function parseOrderedImageIds(orderedImageIds) {
421
+ if (orderedImageIds.length === 0) return {
422
+ err: new ValidationError("orderedImageIds must contain at least one image ID", { code: "empty_image_ids" }),
423
+ success: false
424
+ };
425
+ return orderedImageIds.reduce(appendParsedId, {
426
+ data: [],
427
+ success: true
428
+ });
429
+ }
430
+ //#endregion
431
+ //#region src/domains/game-internationalization/game-thumbnails/operations.ts
432
+ /**
433
+ * Per-second request ceiling for every game-thumbnails Operation bound on
434
+ * `UniversesClient.thumbnails`. The legacy `gameinternationalization`
435
+ * service caps each API key at 100 requests per minute *shared across the
436
+ * entire service* (see the `x-roblox-rate-limits` extension on every
437
+ * operation in the vendored Open Cloud spec), so all methods queue against
438
+ * the same operation key.
439
+ */
440
+ const THUMBNAILS_OPERATION_LIMIT = Object.freeze({
441
+ maxPerSecond: 100 / 60,
442
+ operationKey: "experience-thumbnails"
443
+ });
444
+ /**
445
+ * Scopes required for every game-thumbnails operation, sourced from
446
+ * `x-roblox-scopes` on the legacy `gameinternationalization` thumbnail
447
+ * endpoints in the vendored OpenAPI schema.
448
+ */
449
+ const THUMBNAILS_REQUIRED_SCOPES = Object.freeze(["legacy-universe:manage"]);
450
+ //#endregion
451
+ //#region src/domains/game-internationalization/game-thumbnails/parsers.ts
452
+ /**
453
+ * Parses a successful thumbnail-upload response into the public
454
+ * {@link UploadedExperienceThumbnail} shape, returning a {@link Result}
455
+ * so callers can handle malformed payloads without exceptions.
456
+ *
457
+ * @param response - The full {@link HttpResponse} from the Open Cloud API.
458
+ * @returns A success result wrapping the converted upload, or an
459
+ * `ApiError` when the body does not match the wire schema.
460
+ */
461
+ function parseThumbnailUploadResponse(response) {
462
+ const { body, status: statusCode } = response;
463
+ if (!isGameThumbnailUploadWire(body)) return {
464
+ err: new ApiError("Malformed thumbnail upload response", { statusCode }),
465
+ success: false
466
+ };
467
+ return {
468
+ data: { mediaAssetId: body.mediaAssetId },
469
+ success: true
470
+ };
471
+ }
472
+ function isGameThumbnailUploadWire(body) {
473
+ if (!isRecord(body)) return false;
474
+ return typeof body["mediaAssetId"] === "string";
475
+ }
476
+ //#endregion
477
+ //#region src/resources/universes/client.ts
478
+ const GET_SPEC = Object.freeze({
479
+ buildRequest: buildGetRequest,
480
+ methodDefaults: {},
481
+ methodKind: "idempotent",
482
+ operationLimit: GET_OPERATION_LIMIT,
483
+ parse: parseUniverseResponse
484
+ });
485
+ const UPDATE_SPEC = Object.freeze({
486
+ buildRequest: buildUpdateRequest,
487
+ methodDefaults: {},
488
+ methodKind: "idempotent",
489
+ operationLimit: UPDATE_OPERATION_LIMIT,
490
+ parse: parseUniverseResponse,
491
+ requiredScopes: UPDATE_REQUIRED_SCOPES
492
+ });
493
+ function buildIconUploadOkRequest(parameters) {
494
+ return okRequest(buildUploadIconRequest(parameters));
495
+ }
496
+ function buildIconDeleteOkRequest(parameters) {
497
+ return okRequest(buildDeleteIconRequest(parameters));
498
+ }
499
+ function buildIconListOkRequest(parameters) {
500
+ return okRequest(buildListIconsRequest(parameters));
501
+ }
502
+ const ICON_UPLOAD_SPEC = Object.freeze({
503
+ buildRequest: buildIconUploadOkRequest,
504
+ methodDefaults: CREATE_METHOD_DEFAULTS,
505
+ methodKind: "create",
506
+ operationLimit: ICON_OPERATION_LIMIT,
507
+ parse: parseEmptyResponse,
508
+ requiredScopes: ICON_REQUIRED_SCOPES
509
+ });
510
+ const ICON_DELETE_SPEC = Object.freeze({
511
+ buildRequest: buildIconDeleteOkRequest,
512
+ methodDefaults: IDEMPOTENT_METHOD_DEFAULTS,
513
+ methodKind: "idempotent",
514
+ operationLimit: ICON_OPERATION_LIMIT,
515
+ parse: parseEmptyResponse,
516
+ requiredScopes: ICON_REQUIRED_SCOPES
517
+ });
518
+ const ICON_LIST_SPEC = Object.freeze({
519
+ buildRequest: buildIconListOkRequest,
520
+ methodDefaults: IDEMPOTENT_METHOD_DEFAULTS,
521
+ methodKind: "idempotent",
522
+ operationLimit: ICON_OPERATION_LIMIT,
523
+ parse: parseIconListResponse,
524
+ requiredScopes: ICON_REQUIRED_SCOPES
525
+ });
526
+ function buildThumbnailUploadOkRequest(parameters) {
527
+ return okRequest(buildUploadThumbnailRequest(parameters));
528
+ }
529
+ function buildThumbnailDeleteOkRequest(parameters) {
530
+ return okRequest(buildDeleteThumbnailRequest(parameters));
531
+ }
532
+ const THUMBNAIL_UPLOAD_SPEC = Object.freeze({
533
+ buildRequest: buildThumbnailUploadOkRequest,
534
+ methodDefaults: CREATE_METHOD_DEFAULTS,
535
+ methodKind: "create",
536
+ operationLimit: THUMBNAILS_OPERATION_LIMIT,
537
+ parse: parseThumbnailUploadResponse,
538
+ requiredScopes: THUMBNAILS_REQUIRED_SCOPES
539
+ });
540
+ const THUMBNAIL_DELETE_SPEC = Object.freeze({
541
+ buildRequest: buildThumbnailDeleteOkRequest,
542
+ methodDefaults: IDEMPOTENT_METHOD_DEFAULTS,
543
+ methodKind: "idempotent",
544
+ operationLimit: THUMBNAILS_OPERATION_LIMIT,
545
+ parse: parseEmptyResponse,
546
+ requiredScopes: THUMBNAILS_REQUIRED_SCOPES
547
+ });
548
+ const THUMBNAIL_REORDER_SPEC = Object.freeze({
549
+ buildRequest: buildReorderThumbnailsRequest,
550
+ methodDefaults: IDEMPOTENT_METHOD_DEFAULTS,
551
+ methodKind: "idempotent",
552
+ operationLimit: THUMBNAILS_OPERATION_LIMIT,
553
+ parse: parseEmptyResponse,
554
+ requiredScopes: THUMBNAILS_REQUIRED_SCOPES
555
+ });
556
+ /**
557
+ * Public client for the Roblox Open Cloud `Universe` resource. Wires
558
+ * the request builders, the injected
559
+ * {@link OpenCloudClientOptions.httpClient}, and the response parser
560
+ * into a single ergonomic surface. Every method returns a
561
+ * {@link Result} so callers handle failure explicitly; no thrown
562
+ * {@link OpenCloudError} ever escapes the client.
563
+ *
564
+ * Partial updates use a Google-style `updateMask` query string derived
565
+ * from the keys present on the update parameters. Setting a clearable
566
+ * field (`privateServerPriceRobux` or any social link) to `undefined`
567
+ * sends JSON `null` for that field so the server clears the
568
+ * corresponding value.
569
+ *
570
+ * Localized experience-icon and experience-thumbnail Operations are
571
+ * bound on the {@link UniversesClient.icon} and
572
+ * {@link UniversesClient.thumbnails} Operation Groups so callers reach
573
+ * for one client per universe.
574
+ *
575
+ * @example
576
+ *
577
+ * ```ts
578
+ * import { UniversesClient } from "@bedrock-rbx/ocale/universes";
579
+ *
580
+ * const client = new UniversesClient({ apiKey: "your-key" });
581
+ * expect(client).toBeInstanceOf(UniversesClient);
582
+ * ```
583
+ */
584
+ var UniversesClient = class {
585
+ #inner;
586
+ /**
587
+ * Operation Group exposing the localized experience-icon
588
+ * Operations (`upload`, `delete`, `list`) backed by the
589
+ * `legacy-game-internationalization` domain. Shares the parent
590
+ * client's HTTP, rate-limit, and retry plumbing.
591
+ */
592
+ icon;
593
+ /**
594
+ * Operation Group exposing the localized experience-thumbnail
595
+ * Operations (`upload`, `delete`, `reorder`) backed by the
596
+ * `legacy-game-internationalization` domain. No list-thumbnails
597
+ * endpoint is bridged; consumers must track uploaded
598
+ * `mediaAssetId`s in their own state store to reconcile against
599
+ * the existing carousel. Shares the parent client's HTTP,
600
+ * rate-limit, and retry plumbing.
601
+ */
602
+ thumbnails;
603
+ /**
604
+ * Creates a new {@link UniversesClient}. Configuration is frozen
605
+ * on construction; per-request overrides are accepted on each
606
+ * method.
607
+ *
608
+ * @param options - Client-level configuration including the API key.
609
+ */
610
+ constructor(options) {
611
+ this.#inner = new ResourceClient(options);
612
+ this.icon = createIconHandle(this.#inner);
613
+ this.thumbnails = createThumbnailsHandle(this.#inner);
614
+ }
615
+ /**
616
+ * Fetches the current configuration of a universe.
617
+ *
618
+ * @param parameters - The universe identifier.
619
+ * @param options - Optional per-request overrides (e.g. A different
620
+ * {@link OpenCloudClientOptions.apiKey} for this call only).
621
+ * @returns A {@link Result} wrapping the parsed {@link Universe}
622
+ * or the {@link OpenCloudError} that caused the request to fail.
623
+ */
624
+ async get(parameters, options) {
625
+ return this.#inner.execute({
626
+ options,
627
+ parameters,
628
+ spec: GET_SPEC
629
+ });
630
+ }
631
+ /**
632
+ * Partially updates a universe's configuration. The fields
633
+ * supplied on `parameters` (excluding `universeId`) are forwarded
634
+ * to the server via a Google-style `updateMask`; unmentioned
635
+ * fields are left untouched.
636
+ *
637
+ * @param parameters - The universe identifier and the fields to
638
+ * update. At least one updatable field must be supplied.
639
+ * @param options - Optional per-request overrides (e.g. A different
640
+ * {@link OpenCloudClientOptions.apiKey} for this call only).
641
+ * @returns A {@link Result} wrapping the parsed {@link Universe}
642
+ * or the {@link OpenCloudError} that caused the request to fail.
643
+ */
644
+ async update(parameters, options) {
645
+ return this.#inner.execute({
646
+ options,
647
+ parameters,
648
+ spec: UPDATE_SPEC
649
+ });
650
+ }
651
+ };
652
+ function createIconHandle(inner) {
653
+ return {
654
+ async delete(parameters, options) {
655
+ return inner.execute({
656
+ options,
657
+ parameters,
658
+ spec: ICON_DELETE_SPEC
659
+ });
660
+ },
661
+ async list(parameters, options) {
662
+ return inner.execute({
663
+ options,
664
+ parameters,
665
+ spec: ICON_LIST_SPEC
666
+ });
667
+ },
668
+ async upload(parameters, options) {
669
+ return inner.execute({
670
+ options,
671
+ parameters,
672
+ spec: ICON_UPLOAD_SPEC
673
+ });
674
+ }
675
+ };
676
+ }
677
+ function createThumbnailsHandle(inner) {
678
+ return {
679
+ async delete(parameters, options) {
680
+ return inner.execute({
681
+ options,
682
+ parameters,
683
+ spec: THUMBNAIL_DELETE_SPEC
684
+ });
685
+ },
686
+ async reorder(parameters, options) {
687
+ return inner.execute({
688
+ options,
689
+ parameters,
690
+ spec: THUMBNAIL_REORDER_SPEC
691
+ });
692
+ },
693
+ async upload(parameters, options) {
694
+ return inner.execute({
695
+ options,
696
+ parameters,
697
+ spec: THUMBNAIL_UPLOAD_SPEC
698
+ });
699
+ }
700
+ };
701
+ }
702
+ //#endregion
703
+ export { UniversesClient };
704
+
705
+ //# sourceMappingURL=universes.mjs.map