@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.
- package/LICENSE +21 -0
- package/dist/badges.d.mts +186 -0
- package/dist/badges.d.mts.map +1 -0
- package/dist/badges.mjs +309 -0
- package/dist/badges.mjs.map +1 -0
- package/dist/developer-products.d.mts +245 -0
- package/dist/developer-products.d.mts.map +1 -0
- package/dist/developer-products.mjs +388 -0
- package/dist/developer-products.mjs.map +1 -0
- package/dist/game-passes.d.mts +210 -0
- package/dist/game-passes.d.mts.map +1 -0
- package/dist/game-passes.mjs +397 -0
- package/dist/game-passes.mjs.map +1 -0
- package/dist/index.d.mts +191 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/places.d.mts +161 -0
- package/dist/places.d.mts.map +1 -0
- package/dist/places.mjs +403 -0
- package/dist/places.mjs.map +1 -0
- package/dist/price-information-CmpscMc4.mjs +42 -0
- package/dist/price-information-CmpscMc4.mjs.map +1 -0
- package/dist/rate-limit-BBU_4xnZ.mjs +135 -0
- package/dist/rate-limit-BBU_4xnZ.mjs.map +1 -0
- package/dist/resource-client-CaS_j3yg.mjs +652 -0
- package/dist/resource-client-CaS_j3yg.mjs.map +1 -0
- package/dist/to-blob-1BtHsDGK.mjs +18 -0
- package/dist/to-blob-1BtHsDGK.mjs.map +1 -0
- package/dist/types-YCTsM8Qd.d.mts +214 -0
- package/dist/types-YCTsM8Qd.d.mts.map +1 -0
- package/dist/universes.d.mts +387 -0
- package/dist/universes.d.mts.map +1 -0
- package/dist/universes.mjs +705 -0
- package/dist/universes.mjs.map +1 -0
- package/dist/validation-CTZzJhmd.mjs +38 -0
- package/dist/validation-CTZzJhmd.mjs.map +1 -0
- package/package.json +70 -0
package/dist/places.mjs
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import { i as ApiError } from "./rate-limit-BBU_4xnZ.mjs";
|
|
2
|
+
import { t as ValidationError } from "./validation-CTZzJhmd.mjs";
|
|
3
|
+
import { i as CREATE_METHOD_DEFAULTS, o as isRecord, t as ResourceClient } from "./resource-client-CaS_j3yg.mjs";
|
|
4
|
+
//#region src/domains/cloud-v2/places/builders.ts
|
|
5
|
+
const NON_UPDATABLE_KEYS = new Set(["placeId", "universeId"]);
|
|
6
|
+
/**
|
|
7
|
+
* Builds a `PATCH` request for the Open Cloud "update place" endpoint.
|
|
8
|
+
* Derives the `updateMask` query string from the keys present on
|
|
9
|
+
* `parameters` (excluding the identifiers) and emits a JSON body
|
|
10
|
+
* containing those same fields.
|
|
11
|
+
*
|
|
12
|
+
* @param parameters - The universe and place identifiers plus the fields
|
|
13
|
+
* to update.
|
|
14
|
+
* @returns A success result wrapping the request, or a
|
|
15
|
+
* {@link ValidationError} when no updatable fields were supplied.
|
|
16
|
+
*/
|
|
17
|
+
function buildUpdateRequest(parameters) {
|
|
18
|
+
const fieldKeys = extractUpdateFieldKeys(parameters);
|
|
19
|
+
if (fieldKeys.length === 0) return {
|
|
20
|
+
err: new ValidationError("Update must include at least one field", { code: "empty_update" }),
|
|
21
|
+
success: false
|
|
22
|
+
};
|
|
23
|
+
const body = Object.fromEntries(fieldKeys.map((key) => [key, Reflect.get(parameters, key)]));
|
|
24
|
+
const updateMask = fieldKeys.join(",");
|
|
25
|
+
const { placeId, universeId } = parameters;
|
|
26
|
+
return {
|
|
27
|
+
data: {
|
|
28
|
+
body,
|
|
29
|
+
headers: { "content-type": "application/json" },
|
|
30
|
+
method: "PATCH",
|
|
31
|
+
url: `/cloud/v2/universes/${universeId}/places/${placeId}?updateMask=${updateMask}`
|
|
32
|
+
},
|
|
33
|
+
success: true
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function extractUpdateFieldKeys(parameters) {
|
|
37
|
+
return Object.keys(parameters).filter((key) => !NON_UPDATABLE_KEYS.has(key));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Per-second request ceiling for updating a place, from the Open Cloud
|
|
41
|
+
* OpenAPI schema (100 requests per minute per API key owner). Keyed
|
|
42
|
+
* independently from the publish operation so publish and update do
|
|
43
|
+
* not share a queue; upstream quota accounting is not documented as
|
|
44
|
+
* shared and the conservative default is fewer cross-method
|
|
45
|
+
* contention surprises.
|
|
46
|
+
*/
|
|
47
|
+
const UPDATE_OPERATION_LIMIT = Object.freeze({
|
|
48
|
+
maxPerSecond: 100 / 60,
|
|
49
|
+
operationKey: "places.update"
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Scopes required to update a place's metadata, sourced from
|
|
53
|
+
* `x-roblox-scopes` on the `Cloud_UpdatePlace` operation in the vendored
|
|
54
|
+
* OpenAPI schema.
|
|
55
|
+
*/
|
|
56
|
+
const UPDATE_REQUIRED_SCOPES = Object.freeze(["universe.place:write"]);
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/domains/cloud-v2/places/parsers.ts
|
|
59
|
+
const MALFORMED_PLACE_MESSAGE = "Malformed place response";
|
|
60
|
+
/**
|
|
61
|
+
* Parses a successful Open Cloud `Place` response body into the public
|
|
62
|
+
* {@link Place} shape.
|
|
63
|
+
*
|
|
64
|
+
* @param response - The full {@link HttpResponse} from the Open Cloud API.
|
|
65
|
+
* @returns A success result wrapping the parsed {@link Place}, or an
|
|
66
|
+
* {@link ApiError} when the body does not match the wire schema.
|
|
67
|
+
*/
|
|
68
|
+
function parsePlaceResponse(response) {
|
|
69
|
+
const { body, status: statusCode } = response;
|
|
70
|
+
if (!isPlaceWire(body)) return malformedPlace(statusCode);
|
|
71
|
+
const match = /^universes\/(\d+)\/places\/(\d+)$/.exec(body.path);
|
|
72
|
+
const universeId = match?.[1];
|
|
73
|
+
const id = match?.[2];
|
|
74
|
+
if (id === void 0 || universeId === void 0) return malformedPlace(statusCode);
|
|
75
|
+
return {
|
|
76
|
+
data: toPlace({
|
|
77
|
+
id,
|
|
78
|
+
body,
|
|
79
|
+
universeId
|
|
80
|
+
}),
|
|
81
|
+
success: true
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function malformedPlace(statusCode) {
|
|
85
|
+
return {
|
|
86
|
+
err: new ApiError(MALFORMED_PLACE_MESSAGE, { statusCode }),
|
|
87
|
+
success: false
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function toPlace(args) {
|
|
91
|
+
const { id, body, universeId } = args;
|
|
92
|
+
return {
|
|
93
|
+
id,
|
|
94
|
+
createdAt: new Date(body.createTime),
|
|
95
|
+
description: body.description,
|
|
96
|
+
displayName: body.displayName,
|
|
97
|
+
root: body.root ?? false,
|
|
98
|
+
serverSize: body.serverSize ?? void 0,
|
|
99
|
+
universeId,
|
|
100
|
+
universeRuntimeCreation: body.universeRuntimeCreation ?? false,
|
|
101
|
+
updatedAt: new Date(body.updateTime)
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function hasValidPlaceRequired(body) {
|
|
105
|
+
return typeof body["path"] === "string" && typeof body["createTime"] === "string" && typeof body["updateTime"] === "string" && typeof body["displayName"] === "string" && typeof body["description"] === "string";
|
|
106
|
+
}
|
|
107
|
+
function isOptionalBoolean(value) {
|
|
108
|
+
return value === void 0 || value === null || typeof value === "boolean";
|
|
109
|
+
}
|
|
110
|
+
function hasValidPlaceOptional(body) {
|
|
111
|
+
const serverSize = body["serverSize"] ?? void 0;
|
|
112
|
+
return (serverSize === void 0 || typeof serverSize === "number") && isOptionalBoolean(body["root"]) && isOptionalBoolean(body["universeRuntimeCreation"]);
|
|
113
|
+
}
|
|
114
|
+
function isPlaceWire(body) {
|
|
115
|
+
return isRecord(body) && hasValidPlaceRequired(body) && hasValidPlaceOptional(body);
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/domains/universes/places/signatures.ts
|
|
119
|
+
/**
|
|
120
|
+
* Magic-byte prefix every Roblox binary place file (`.rbxl`) starts with.
|
|
121
|
+
* The first 8 bytes spell out `<roblox!` in ASCII; the remaining 6 bytes
|
|
122
|
+
* (`\x89\xff\r\n\x1a\n`) are the binary-format marker that distinguishes a
|
|
123
|
+
* binary place file from the XML form (`.rbxlx`), whose ASCII-only header
|
|
124
|
+
* begins with `<roblox `.
|
|
125
|
+
*/
|
|
126
|
+
const RBXL_SIGNATURE = new Uint8Array([
|
|
127
|
+
60,
|
|
128
|
+
114,
|
|
129
|
+
111,
|
|
130
|
+
98,
|
|
131
|
+
108,
|
|
132
|
+
111,
|
|
133
|
+
120,
|
|
134
|
+
33,
|
|
135
|
+
137,
|
|
136
|
+
255,
|
|
137
|
+
13,
|
|
138
|
+
10,
|
|
139
|
+
26,
|
|
140
|
+
10
|
|
141
|
+
]);
|
|
142
|
+
/**
|
|
143
|
+
* Magic-byte prefix every Roblox XML place file (`.rbxlx`) starts with.
|
|
144
|
+
* Equivalent to the ASCII string `<roblox ` (note the trailing space): a
|
|
145
|
+
* well-formed rbxlx file opens with `<roblox` followed by attributes, while
|
|
146
|
+
* an rbxl file uses `<roblox!` (exclamation mark) as the eighth byte. The
|
|
147
|
+
* trailing space is what proves the file is the XML variant rather than
|
|
148
|
+
* the binary one.
|
|
149
|
+
*/
|
|
150
|
+
const RBXLX_SIGNATURE = new Uint8Array([
|
|
151
|
+
60,
|
|
152
|
+
114,
|
|
153
|
+
111,
|
|
154
|
+
98,
|
|
155
|
+
108,
|
|
156
|
+
111,
|
|
157
|
+
120,
|
|
158
|
+
32
|
|
159
|
+
]);
|
|
160
|
+
/**
|
|
161
|
+
* Reports whether `body` begins with `signature`. A pure byte-prefix check
|
|
162
|
+
* with no allocation; used by the place builder to disambiguate `.rbxl` and
|
|
163
|
+
* `.rbxlx` payloads against their declared `format`.
|
|
164
|
+
*
|
|
165
|
+
* @param body - The caller-supplied place file bytes.
|
|
166
|
+
* @param signature - One of the frozen signature constants from this module.
|
|
167
|
+
* @returns `true` if every byte of `signature` matches `body[0..signature.length]`.
|
|
168
|
+
*/
|
|
169
|
+
function matchesSignature(body, signature) {
|
|
170
|
+
for (const [index, expected] of signature.entries()) if (body[index] !== expected) return false;
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/domains/universes/places/builders.ts
|
|
175
|
+
const CONTENT_TYPE_BY_FORMAT = {
|
|
176
|
+
rbxl: "application/octet-stream",
|
|
177
|
+
rbxlx: "application/xml"
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Builds a `POST` request for the Open Cloud "publish place version"
|
|
181
|
+
* endpoint. Performs two local validations before producing any
|
|
182
|
+
* {@link HttpRequest}: a non-empty body check and a magic-byte check
|
|
183
|
+
* that the bytes' actual format matches `parameters.format`.
|
|
184
|
+
*
|
|
185
|
+
* @param parameters - Universe and place identifiers, the place file
|
|
186
|
+
* bytes, and the declared `format` of those bytes.
|
|
187
|
+
* @param versionType - `"Published"` for `publish()`, `"Saved"` for
|
|
188
|
+
* `save()`; baked into the `?versionType=` query string.
|
|
189
|
+
* @returns A success result wrapping the request on success, or a
|
|
190
|
+
* {@link ValidationError} when the body is empty or its magic bytes
|
|
191
|
+
* disagree with `parameters.format`.
|
|
192
|
+
*/
|
|
193
|
+
function buildPublishRequest(parameters, versionType) {
|
|
194
|
+
const { body, format, placeId, universeId } = parameters;
|
|
195
|
+
if (body.length === 0) return {
|
|
196
|
+
err: new ValidationError("Place body is empty", { code: "empty_body" }),
|
|
197
|
+
success: false
|
|
198
|
+
};
|
|
199
|
+
if (!matchesSignature(body, format === "rbxl" ? RBXL_SIGNATURE : RBXLX_SIGNATURE)) return {
|
|
200
|
+
err: new ValidationError(`Place body does not match the declared "${format}" format`, { code: "format_mismatch" }),
|
|
201
|
+
success: false
|
|
202
|
+
};
|
|
203
|
+
return {
|
|
204
|
+
data: {
|
|
205
|
+
body,
|
|
206
|
+
headers: { "content-type": CONTENT_TYPE_BY_FORMAT[format] },
|
|
207
|
+
method: "POST",
|
|
208
|
+
url: `/universes/v1/${universeId}/places/${placeId}/versions?versionType=${versionType}`
|
|
209
|
+
},
|
|
210
|
+
success: true
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/domains/universes/places/operations.ts
|
|
215
|
+
/**
|
|
216
|
+
* Per-second request ceiling for publishing or saving a place version,
|
|
217
|
+
* from the Open Cloud OpenAPI schema (30 requests per minute, expressed
|
|
218
|
+
* here as `0.5` per second). The publish and save methods both reference
|
|
219
|
+
* this constant so that a single per-API-key queue serves both, matching
|
|
220
|
+
* Roblox's server-side accounting which counts both call types against
|
|
221
|
+
* the same per-minute quota.
|
|
222
|
+
*/
|
|
223
|
+
const PUBLISH_OPERATION_LIMIT = Object.freeze({
|
|
224
|
+
maxPerSecond: .5,
|
|
225
|
+
operationKey: "places.publishVersion"
|
|
226
|
+
});
|
|
227
|
+
/**
|
|
228
|
+
* Scopes required to publish or save a place version, sourced from
|
|
229
|
+
* `x-roblox-scopes` on the `Places_CreatePlaceVersionApiKey` operation
|
|
230
|
+
* in the vendored OpenAPI schema.
|
|
231
|
+
*/
|
|
232
|
+
const PUBLISH_REQUIRED_SCOPES = Object.freeze(["universe-places:write"]);
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/domains/universes/places/parsers.ts
|
|
235
|
+
/**
|
|
236
|
+
* Parses a successful publish-version response into the public
|
|
237
|
+
* {@link PlaceVersion} shape. The Roblox endpoint sometimes returns the
|
|
238
|
+
* JSON-shaped body under a `text/plain` `Content-Type`, so the body may
|
|
239
|
+
* arrive either pre-decoded as a JSON object or still in its raw string
|
|
240
|
+
* form; both are accepted here.
|
|
241
|
+
*
|
|
242
|
+
* @param response - The full {@link HttpResponse} from the Open Cloud API.
|
|
243
|
+
* @returns A success result wrapping the parsed {@link PlaceVersion}, or
|
|
244
|
+
* an {@link ApiError} when the body is malformed or its `versionNumber`
|
|
245
|
+
* field is missing/wrong-typed.
|
|
246
|
+
*/
|
|
247
|
+
function parsePublishResponse(response) {
|
|
248
|
+
const { body, status: statusCode } = response;
|
|
249
|
+
const decodeResult = decodeBody(body, statusCode);
|
|
250
|
+
if (!decodeResult.success) return decodeResult;
|
|
251
|
+
if (!isPlaceVersionWire(decodeResult.data)) return {
|
|
252
|
+
err: new ApiError("Malformed publish response", { statusCode }),
|
|
253
|
+
success: false
|
|
254
|
+
};
|
|
255
|
+
return {
|
|
256
|
+
data: { versionNumber: decodeResult.data.versionNumber },
|
|
257
|
+
success: true
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function decodeBody(body, statusCode) {
|
|
261
|
+
if (typeof body !== "string") return {
|
|
262
|
+
data: body,
|
|
263
|
+
success: true
|
|
264
|
+
};
|
|
265
|
+
try {
|
|
266
|
+
return {
|
|
267
|
+
data: JSON.parse(body),
|
|
268
|
+
success: true
|
|
269
|
+
};
|
|
270
|
+
} catch {
|
|
271
|
+
return {
|
|
272
|
+
err: new ApiError("Malformed publish response", { statusCode }),
|
|
273
|
+
success: false
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function isPlaceVersionWire(value) {
|
|
278
|
+
if (!isRecord(value)) return false;
|
|
279
|
+
return typeof value["versionNumber"] === "number";
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/resources/places/client.ts
|
|
283
|
+
function makeSpec(versionType) {
|
|
284
|
+
return Object.freeze({
|
|
285
|
+
buildRequest: (parameters) => buildPublishRequest(parameters, versionType),
|
|
286
|
+
methodDefaults: CREATE_METHOD_DEFAULTS,
|
|
287
|
+
methodKind: "create",
|
|
288
|
+
operationLimit: PUBLISH_OPERATION_LIMIT,
|
|
289
|
+
parse: parsePublishResponse,
|
|
290
|
+
requiredScopes: PUBLISH_REQUIRED_SCOPES
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
const PUBLISH_SPEC = makeSpec("Published");
|
|
294
|
+
const SAVE_SPEC = makeSpec("Saved");
|
|
295
|
+
const UPDATE_SPEC = Object.freeze({
|
|
296
|
+
buildRequest: buildUpdateRequest,
|
|
297
|
+
methodDefaults: {},
|
|
298
|
+
methodKind: "idempotent",
|
|
299
|
+
operationLimit: UPDATE_OPERATION_LIMIT,
|
|
300
|
+
parse: parsePlaceResponse,
|
|
301
|
+
requiredScopes: UPDATE_REQUIRED_SCOPES
|
|
302
|
+
});
|
|
303
|
+
/**
|
|
304
|
+
* Public client for the Roblox Open Cloud `Place` resource. Covers
|
|
305
|
+
* place-version publishing (`publish`, `save`) and place-configuration
|
|
306
|
+
* updates (`update`). Wires the request builders, the injected
|
|
307
|
+
* {@link OpenCloudClientOptions.httpClient}, and the response parsers
|
|
308
|
+
* into a single ergonomic surface. Every method returns a {@link Result}
|
|
309
|
+
* so callers handle failure explicitly; no thrown {@link OpenCloudError}
|
|
310
|
+
* ever escapes the client.
|
|
311
|
+
*
|
|
312
|
+
* Publishing or saving a 5xx-failed place version is not retried
|
|
313
|
+
* automatically: Roblox does not support idempotency keys, so a retry
|
|
314
|
+
* could publish a duplicate version unnoticed. Callers that *can* detect
|
|
315
|
+
* duplicates externally may opt back into 5xx retry per-call by passing
|
|
316
|
+
* `retryableStatuses` on the second argument. The `update` method, by
|
|
317
|
+
* contrast, is idempotent and retries both 429 and 5xx automatically.
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
*
|
|
321
|
+
* ```ts
|
|
322
|
+
* import { PlacesClient } from "@bedrock-rbx/ocale/places";
|
|
323
|
+
*
|
|
324
|
+
* const client = new PlacesClient({ apiKey: "your-key" });
|
|
325
|
+
* expect(client).toBeInstanceOf(PlacesClient);
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
var PlacesClient = class {
|
|
329
|
+
#inner;
|
|
330
|
+
/**
|
|
331
|
+
* Creates a new {@link PlacesClient}. Configuration is frozen on
|
|
332
|
+
* construction; per-request overrides are accepted on each method.
|
|
333
|
+
*
|
|
334
|
+
* @param options - Client-level configuration including the API key.
|
|
335
|
+
*/
|
|
336
|
+
constructor(options) {
|
|
337
|
+
this.#inner = new ResourceClient(options);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Publishes a new live version of a place.
|
|
341
|
+
*
|
|
342
|
+
* @param parameters - Universe and place identifiers, the place file
|
|
343
|
+
* bytes, and their declared `format`.
|
|
344
|
+
* @param options - Optional per-request overrides (e.g. A different
|
|
345
|
+
* {@link OpenCloudClientOptions.apiKey} for this call only).
|
|
346
|
+
* @returns A {@link Result} wrapping the parsed {@link PlaceVersion}
|
|
347
|
+
* or the {@link OpenCloudError} that caused the request to fail.
|
|
348
|
+
*/
|
|
349
|
+
async publish(parameters, options) {
|
|
350
|
+
return this.#inner.execute({
|
|
351
|
+
options,
|
|
352
|
+
parameters,
|
|
353
|
+
spec: PUBLISH_SPEC
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Saves a new draft version of a place. Identical to {@link publish}
|
|
358
|
+
* except the resulting version is not made live; consumers can list or
|
|
359
|
+
* promote it later. Shares a single per-API-key rate-limit queue with
|
|
360
|
+
* `publish` because Roblox attributes both calls to the same per-minute
|
|
361
|
+
* quota.
|
|
362
|
+
*
|
|
363
|
+
* @param parameters - Universe and place identifiers, the place file
|
|
364
|
+
* bytes, and their declared `format`.
|
|
365
|
+
* @param options - Optional per-request overrides (e.g. A different
|
|
366
|
+
* {@link OpenCloudClientOptions.apiKey} for this call only).
|
|
367
|
+
* @returns A {@link Result} wrapping the parsed {@link PlaceVersion}
|
|
368
|
+
* or the {@link OpenCloudError} that caused the request to fail.
|
|
369
|
+
*/
|
|
370
|
+
async save(parameters, options) {
|
|
371
|
+
return this.#inner.execute({
|
|
372
|
+
options,
|
|
373
|
+
parameters,
|
|
374
|
+
spec: SAVE_SPEC
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Partially updates a place's configuration. The fields supplied on
|
|
379
|
+
* `parameters` (excluding the identifiers) are forwarded to the
|
|
380
|
+
* server via a Google-style `updateMask`; unmentioned fields are
|
|
381
|
+
* left untouched. The universe's root place is the canonical place
|
|
382
|
+
* to update when changing a universe's description or display name:
|
|
383
|
+
* both are derived server-side from the root place.
|
|
384
|
+
*
|
|
385
|
+
* @param parameters - The universe and place identifiers and the
|
|
386
|
+
* fields to update. At least one writable field must be supplied.
|
|
387
|
+
* @param options - Optional per-request overrides (e.g. A different
|
|
388
|
+
* {@link OpenCloudClientOptions.apiKey} for this call only).
|
|
389
|
+
* @returns A {@link Result} wrapping the parsed {@link Place} or
|
|
390
|
+
* the {@link OpenCloudError} that caused the request to fail.
|
|
391
|
+
*/
|
|
392
|
+
async update(parameters, options) {
|
|
393
|
+
return this.#inner.execute({
|
|
394
|
+
options,
|
|
395
|
+
parameters,
|
|
396
|
+
spec: UPDATE_SPEC
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
//#endregion
|
|
401
|
+
export { PlacesClient };
|
|
402
|
+
|
|
403
|
+
//# sourceMappingURL=places.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"places.mjs","names":["#inner"],"sources":["../src/domains/cloud-v2/places/builders.ts","../src/domains/cloud-v2/places/operations.ts","../src/domains/cloud-v2/places/parsers.ts","../src/domains/universes/places/signatures.ts","../src/domains/universes/places/builders.ts","../src/domains/universes/places/operations.ts","../src/domains/universes/places/parsers.ts","../src/resources/places/client.ts"],"sourcesContent":["import type { HttpRequest } from \"../../../client/types.ts\";\nimport { ValidationError } from \"../../../errors/validation.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport type { UpdatePlaceParameters } from \"./types.ts\";\n\nconst NON_UPDATABLE_KEYS: ReadonlySet<string> = new Set([\"placeId\", \"universeId\"]);\n\n/**\n * Builds a `PATCH` request for the Open Cloud \"update place\" endpoint.\n * Derives the `updateMask` query string from the keys present on\n * `parameters` (excluding the identifiers) and emits a JSON body\n * containing those same fields.\n *\n * @param parameters - The universe and place identifiers plus the fields\n * 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: UpdatePlaceParameters,\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 = Object.fromEntries(\n\t\tfieldKeys.map((key): readonly [string, unknown] => [key, Reflect.get(parameters, key)]),\n\t);\n\tconst updateMask = fieldKeys.join(\",\");\n\tconst { placeId, universeId } = parameters;\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/${universeId}/places/${placeId}?updateMask=${updateMask}`,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n\nfunction extractUpdateFieldKeys(parameters: UpdatePlaceParameters): ReadonlyArray<string> {\n\treturn Object.keys(parameters).filter((key) => !NON_UPDATABLE_KEYS.has(key));\n}\n","import type { OperationLimit } from \"../../../internal/http/rate-limit-queue.ts\";\n\nconst UPDATE_PER_MINUTE = 100;\nconst SECONDS_PER_MINUTE = 60;\n\n/**\n * Per-second request ceiling for updating a place, from the Open Cloud\n * OpenAPI schema (100 requests per minute per API key owner). Keyed\n * independently from the publish operation so publish and update do\n * not share a queue; upstream quota accounting is not documented as\n * shared and the conservative default is fewer cross-method\n * contention surprises.\n */\nexport const UPDATE_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: UPDATE_PER_MINUTE / SECONDS_PER_MINUTE,\n\toperationKey: \"places.update\",\n});\n\n/**\n * Scopes required to update a place's metadata, sourced from\n * `x-roblox-scopes` on the `Cloud_UpdatePlace` operation in the vendored\n * OpenAPI schema.\n */\nexport const UPDATE_REQUIRED_SCOPES: ReadonlyArray<string> = Object.freeze([\n\t\"universe.place:write\",\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 { Place } from \"./types.ts\";\nimport type { PlaceWire } from \"./wire.ts\";\n\nconst MALFORMED_PLACE_MESSAGE = \"Malformed place response\";\n\ninterface ToPlaceArgs {\n\treadonly id: string;\n\treadonly body: PlaceWire;\n\treadonly universeId: string;\n}\n\n/**\n * Parses a successful Open Cloud `Place` response body into the public\n * {@link Place} shape.\n *\n * @param response - The full {@link HttpResponse} from the Open Cloud API.\n * @returns A success result wrapping the parsed {@link Place}, or an\n * {@link ApiError} when the body does not match the wire schema.\n */\nexport function parsePlaceResponse(response: HttpResponse): Result<Place, ApiError> {\n\tconst { body, status: statusCode } = response;\n\n\tif (!isPlaceWire(body)) {\n\t\treturn malformedPlace(statusCode);\n\t}\n\n\tconst match = /^universes\\/(\\d+)\\/places\\/(\\d+)$/.exec(body.path);\n\tconst universeId = match?.[1];\n\tconst id = match?.[2];\n\tif (id === undefined || universeId === undefined) {\n\t\treturn malformedPlace(statusCode);\n\t}\n\n\treturn { data: toPlace({ id, body, universeId }), success: true };\n}\n\nfunction malformedPlace(statusCode: number): Result<Place, ApiError> {\n\treturn {\n\t\terr: new ApiError(MALFORMED_PLACE_MESSAGE, { statusCode }),\n\t\tsuccess: false,\n\t};\n}\n\nfunction toPlace(args: ToPlaceArgs): Place {\n\tconst { id, body, universeId } = args;\n\treturn {\n\t\tid,\n\t\tcreatedAt: new Date(body.createTime),\n\t\tdescription: body.description,\n\t\tdisplayName: body.displayName,\n\t\troot: body.root ?? false,\n\t\tserverSize: body.serverSize ?? undefined,\n\t\tuniverseId,\n\t\tuniverseRuntimeCreation: body.universeRuntimeCreation ?? false,\n\t\tupdatedAt: new Date(body.updateTime),\n\t};\n}\n\nfunction hasValidPlaceRequired(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);\n}\n\nfunction isOptionalBoolean(value: unknown): boolean {\n\treturn value === undefined || value === null || typeof value === \"boolean\";\n}\n\nfunction hasValidPlaceOptional(body: Record<string, unknown>): boolean {\n\tconst serverSize = body[\"serverSize\"] ?? undefined;\n\treturn (\n\t\t(serverSize === undefined || typeof serverSize === \"number\") &&\n\t\tisOptionalBoolean(body[\"root\"]) &&\n\t\tisOptionalBoolean(body[\"universeRuntimeCreation\"])\n\t);\n}\n\nfunction isPlaceWire(body: unknown): body is PlaceWire {\n\treturn isRecord(body) && hasValidPlaceRequired(body) && hasValidPlaceOptional(body);\n}\n","/**\n * Magic-byte prefix every Roblox binary place file (`.rbxl`) starts with.\n * The first 8 bytes spell out `<roblox!` in ASCII; the remaining 6 bytes\n * (`\\x89\\xff\\r\\n\\x1a\\n`) are the binary-format marker that distinguishes a\n * binary place file from the XML form (`.rbxlx`), whose ASCII-only header\n * begins with `<roblox `.\n */\nexport const RBXL_SIGNATURE: Readonly<Uint8Array<ArrayBuffer>> = new Uint8Array([\n\t0x3c, 0x72, 0x6f, 0x62, 0x6c, 0x6f, 0x78, 0x21, 0x89, 0xff, 0x0d, 0x0a, 0x1a, 0x0a,\n]);\n\n/**\n * Magic-byte prefix every Roblox XML place file (`.rbxlx`) starts with.\n * Equivalent to the ASCII string `<roblox ` (note the trailing space): a\n * well-formed rbxlx file opens with `<roblox` followed by attributes, while\n * an rbxl file uses `<roblox!` (exclamation mark) as the eighth byte. The\n * trailing space is what proves the file is the XML variant rather than\n * the binary one.\n */\nexport const RBXLX_SIGNATURE: Readonly<Uint8Array<ArrayBuffer>> = new Uint8Array([\n\t0x3c, 0x72, 0x6f, 0x62, 0x6c, 0x6f, 0x78, 0x20,\n]);\n\n/**\n * Reports whether `body` begins with `signature`. A pure byte-prefix check\n * with no allocation; used by the place builder to disambiguate `.rbxl` and\n * `.rbxlx` payloads against their declared `format`.\n *\n * @param body - The caller-supplied place file bytes.\n * @param signature - One of the frozen signature constants from this module.\n * @returns `true` if every byte of `signature` matches `body[0..signature.length]`.\n */\nexport function matchesSignature(\n\tbody: Uint8Array,\n\tsignature: Readonly<Uint8Array<ArrayBuffer>>,\n): boolean {\n\tfor (const [index, expected] of signature.entries()) {\n\t\tif (body[index] !== expected) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n","import type { HttpRequest } from \"../../../client/types.ts\";\nimport { ValidationError } from \"../../../errors/validation.ts\";\nimport type { Result } from \"../../../types.ts\";\nimport { matchesSignature, RBXL_SIGNATURE, RBXLX_SIGNATURE } from \"./signatures.ts\";\nimport type { PublishParameters } from \"./types.ts\";\n\n/**\n * Whether a publish call writes a live (`Published`) or draft (`Saved`)\n * version. Surfaces only as the `versionType` query string on the\n * underlying HTTP request.\n */\ntype VersionType = \"Published\" | \"Saved\";\n\nconst CONTENT_TYPE_BY_FORMAT: Readonly<Record<PublishParameters[\"format\"], string>> = {\n\trbxl: \"application/octet-stream\",\n\trbxlx: \"application/xml\",\n};\n\n/**\n * Builds a `POST` request for the Open Cloud \"publish place version\"\n * endpoint. Performs two local validations before producing any\n * {@link HttpRequest}: a non-empty body check and a magic-byte check\n * that the bytes' actual format matches `parameters.format`.\n *\n * @param parameters - Universe and place identifiers, the place file\n * bytes, and the declared `format` of those bytes.\n * @param versionType - `\"Published\"` for `publish()`, `\"Saved\"` for\n * `save()`; baked into the `?versionType=` query string.\n * @returns A success result wrapping the request on success, or a\n * {@link ValidationError} when the body is empty or its magic bytes\n * disagree with `parameters.format`.\n */\nexport function buildPublishRequest(\n\tparameters: PublishParameters,\n\tversionType: VersionType,\n): Result<HttpRequest, ValidationError> {\n\tconst { body, format, placeId, universeId } = parameters;\n\n\tif (body.length === 0) {\n\t\treturn {\n\t\t\terr: new ValidationError(\"Place body is empty\", { code: \"empty_body\" }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\tconst expectedSignature = format === \"rbxl\" ? RBXL_SIGNATURE : RBXLX_SIGNATURE;\n\tif (!matchesSignature(body, expectedSignature)) {\n\t\treturn {\n\t\t\terr: new ValidationError(`Place body does not match the declared \"${format}\" format`, {\n\t\t\t\tcode: \"format_mismatch\",\n\t\t\t}),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tbody,\n\t\t\theaders: { \"content-type\": CONTENT_TYPE_BY_FORMAT[format] },\n\t\t\tmethod: \"POST\",\n\t\t\turl: `/universes/v1/${universeId}/places/${placeId}/versions?versionType=${versionType}`,\n\t\t},\n\t\tsuccess: true,\n\t};\n}\n","import type { OperationLimit } from \"../../../internal/http/rate-limit-queue.ts\";\n\n/**\n * Per-second request ceiling for publishing or saving a place version,\n * from the Open Cloud OpenAPI schema (30 requests per minute, expressed\n * here as `0.5` per second). The publish and save methods both reference\n * this constant so that a single per-API-key queue serves both, matching\n * Roblox's server-side accounting which counts both call types against\n * the same per-minute quota.\n */\nexport const PUBLISH_OPERATION_LIMIT: OperationLimit = Object.freeze({\n\tmaxPerSecond: 0.5,\n\toperationKey: \"places.publishVersion\",\n});\n\n/**\n * Scopes required to publish or save a place version, sourced from\n * `x-roblox-scopes` on the `Places_CreatePlaceVersionApiKey` operation\n * in the vendored OpenAPI schema.\n */\nexport const PUBLISH_REQUIRED_SCOPES: ReadonlyArray<string> = Object.freeze([\n\t\"universe-places:write\",\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 { PlaceVersion } from \"./types.ts\";\nimport type { PlaceVersionWire } from \"./wire.ts\";\n\n/**\n * Parses a successful publish-version response into the public\n * {@link PlaceVersion} shape. The Roblox endpoint sometimes returns the\n * JSON-shaped body under a `text/plain` `Content-Type`, so the body may\n * arrive either pre-decoded as a JSON object or still in its raw string\n * form; both are accepted here.\n *\n * @param response - The full {@link HttpResponse} from the Open Cloud API.\n * @returns A success result wrapping the parsed {@link PlaceVersion}, or\n * an {@link ApiError} when the body is malformed or its `versionNumber`\n * field is missing/wrong-typed.\n */\nexport function parsePublishResponse(response: HttpResponse): Result<PlaceVersion, ApiError> {\n\tconst { body, status: statusCode } = response;\n\n\tconst decodeResult = decodeBody(body, statusCode);\n\tif (!decodeResult.success) {\n\t\treturn decodeResult;\n\t}\n\n\tif (!isPlaceVersionWire(decodeResult.data)) {\n\t\treturn {\n\t\t\terr: new ApiError(\"Malformed publish response\", { statusCode }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: { versionNumber: decodeResult.data.versionNumber },\n\t\tsuccess: true,\n\t};\n}\n\nfunction decodeBody(body: unknown, statusCode: number): Result<unknown, ApiError> {\n\tif (typeof body !== \"string\") {\n\t\treturn { data: body, success: true };\n\t}\n\n\ttry {\n\t\treturn { data: JSON.parse(body), success: true };\n\t} catch {\n\t\treturn {\n\t\t\terr: new ApiError(\"Malformed publish response\", { statusCode }),\n\t\t\tsuccess: false,\n\t\t};\n\t}\n}\n\nfunction isPlaceVersionWire(value: unknown): value is PlaceVersionWire {\n\tif (!isRecord(value)) {\n\t\treturn false;\n\t}\n\n\treturn typeof value[\"versionNumber\"] === \"number\";\n}\n","import type { OpenCloudClientOptions, RequestOptions } from \"../../client/types.ts\";\nimport { buildUpdateRequest } from \"../../domains/cloud-v2/places/builders.ts\";\nimport {\n\tUPDATE_OPERATION_LIMIT,\n\tUPDATE_REQUIRED_SCOPES,\n} from \"../../domains/cloud-v2/places/operations.ts\";\nimport { parsePlaceResponse } from \"../../domains/cloud-v2/places/parsers.ts\";\nimport type { Place, UpdatePlaceParameters } from \"../../domains/cloud-v2/places/types.ts\";\nimport { buildPublishRequest } from \"../../domains/universes/places/builders.ts\";\nimport {\n\tPUBLISH_OPERATION_LIMIT,\n\tPUBLISH_REQUIRED_SCOPES,\n} from \"../../domains/universes/places/operations.ts\";\nimport { parsePublishResponse } from \"../../domains/universes/places/parsers.ts\";\nimport type { PlaceVersion, PublishParameters } from \"../../domains/universes/places/types.ts\";\nimport type { OpenCloudError } from \"../../errors/base.ts\";\nimport { CREATE_METHOD_DEFAULTS } from \"../../internal/http/retry.ts\";\nimport { ResourceClient, type ResourceMethodSpec } from \"../../internal/resource-client.ts\";\nimport type { Result } from \"../../types.ts\";\n\nfunction makeSpec(\n\tversionType: \"Published\" | \"Saved\",\n): ResourceMethodSpec<PublishParameters, PlaceVersion> {\n\treturn Object.freeze({\n\t\tbuildRequest: (parameters: PublishParameters) =>\n\t\t\tbuildPublishRequest(parameters, versionType),\n\t\tmethodDefaults: CREATE_METHOD_DEFAULTS,\n\t\tmethodKind: \"create\",\n\t\toperationLimit: PUBLISH_OPERATION_LIMIT,\n\t\tparse: parsePublishResponse,\n\t\trequiredScopes: PUBLISH_REQUIRED_SCOPES,\n\t});\n}\n\nconst PUBLISH_SPEC = makeSpec(\"Published\");\nconst SAVE_SPEC = makeSpec(\"Saved\");\n\nconst UPDATE_SPEC: ResourceMethodSpec<UpdatePlaceParameters, Place> = Object.freeze({\n\tbuildRequest: buildUpdateRequest,\n\tmethodDefaults: {},\n\tmethodKind: \"idempotent\",\n\toperationLimit: UPDATE_OPERATION_LIMIT,\n\tparse: parsePlaceResponse,\n\trequiredScopes: UPDATE_REQUIRED_SCOPES,\n});\n\n/**\n * Public client for the Roblox Open Cloud `Place` resource. Covers\n * place-version publishing (`publish`, `save`) and place-configuration\n * updates (`update`). Wires the request builders, the injected\n * {@link OpenCloudClientOptions.httpClient}, and the response parsers\n * into a single ergonomic surface. Every method returns a {@link Result}\n * so callers handle failure explicitly; no thrown {@link OpenCloudError}\n * ever escapes the client.\n *\n * Publishing or saving a 5xx-failed place version is not retried\n * automatically: Roblox does not support idempotency keys, so a retry\n * could publish a duplicate version unnoticed. Callers that *can* detect\n * duplicates externally may opt back into 5xx retry per-call by passing\n * `retryableStatuses` on the second argument. The `update` method, by\n * contrast, is idempotent and retries both 429 and 5xx automatically.\n *\n * @example\n *\n * ```ts\n * import { PlacesClient } from \"@bedrock-rbx/ocale/places\";\n *\n * const client = new PlacesClient({ apiKey: \"your-key\" });\n * expect(client).toBeInstanceOf(PlacesClient);\n * ```\n */\nexport class PlacesClient {\n\treadonly #inner: ResourceClient;\n\n\t/**\n\t * Creates a new {@link PlacesClient}. Configuration is frozen on\n\t * construction; per-request overrides are accepted on each 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}\n\n\t/**\n\t * Publishes a new live version of a place.\n\t *\n\t * @param parameters - Universe and place identifiers, the place file\n\t * bytes, and their declared `format`.\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 PlaceVersion}\n\t * or the {@link OpenCloudError} that caused the request to fail.\n\t */\n\tpublic async publish(\n\t\tparameters: PublishParameters,\n\t\toptions?: RequestOptions,\n\t): Promise<Result<PlaceVersion, OpenCloudError>> {\n\t\treturn this.#inner.execute({ options, parameters, spec: PUBLISH_SPEC });\n\t}\n\n\t/**\n\t * Saves a new draft version of a place. Identical to {@link publish}\n\t * except the resulting version is not made live; consumers can list or\n\t * promote it later. Shares a single per-API-key rate-limit queue with\n\t * `publish` because Roblox attributes both calls to the same per-minute\n\t * quota.\n\t *\n\t * @param parameters - Universe and place identifiers, the place file\n\t * bytes, and their declared `format`.\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 PlaceVersion}\n\t * or the {@link OpenCloudError} that caused the request to fail.\n\t */\n\tpublic async save(\n\t\tparameters: PublishParameters,\n\t\toptions?: RequestOptions,\n\t): Promise<Result<PlaceVersion, OpenCloudError>> {\n\t\treturn this.#inner.execute({ options, parameters, spec: SAVE_SPEC });\n\t}\n\n\t/**\n\t * Partially updates a place's configuration. The fields supplied on\n\t * `parameters` (excluding the identifiers) are forwarded to the\n\t * server via a Google-style `updateMask`; unmentioned fields are\n\t * left untouched. The universe's root place is the canonical place\n\t * to update when changing a universe's description or display name:\n\t * both are derived server-side from the root place.\n\t *\n\t * @param parameters - The universe and place identifiers and the\n\t * fields to update. At least one writable 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 Place} or\n\t * the {@link OpenCloudError} that caused the request to fail.\n\t */\n\tpublic async update(\n\t\tparameters: UpdatePlaceParameters,\n\t\toptions?: RequestOptions,\n\t): Promise<Result<Place, OpenCloudError>> {\n\t\treturn this.#inner.execute({ options, parameters, spec: UPDATE_SPEC });\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { o as isRecord } from "./resource-client-CaS_j3yg.mjs";
|
|
2
|
+
//#region src/internal/price-information.ts
|
|
3
|
+
/**
|
|
4
|
+
* Narrows `value` to {@link PriceInformationLike} for a given feature literal
|
|
5
|
+
* union by delegating per-element validation to the supplied `isFeature`
|
|
6
|
+
* predicate.
|
|
7
|
+
*
|
|
8
|
+
* @template F - The pricing-feature literal union the caller wants to narrow to.
|
|
9
|
+
* @param value - Unknown wire value to validate.
|
|
10
|
+
* @param isFeature - Type guard for a single `enabledFeatures` element.
|
|
11
|
+
* @returns `true` when `value` is a record whose `defaultPriceInRobux` is a
|
|
12
|
+
* number, `null`, or absent and whose `enabledFeatures` is an array of
|
|
13
|
+
* values that all satisfy `isFeature`.
|
|
14
|
+
*/
|
|
15
|
+
function isPriceInformationLike(value, isFeature) {
|
|
16
|
+
if (!isRecord(value)) return false;
|
|
17
|
+
const defaultPrice = value["defaultPriceInRobux"] ?? void 0;
|
|
18
|
+
if (defaultPrice !== void 0 && typeof defaultPrice !== "number") return false;
|
|
19
|
+
const features = value["enabledFeatures"];
|
|
20
|
+
if (!Array.isArray(features)) return false;
|
|
21
|
+
for (const feature of features) if (!isFeature(feature)) return false;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns a fresh {@link PriceInformationLike} value with a new
|
|
26
|
+
* `enabledFeatures` array, so the caller can hand the result on without
|
|
27
|
+
* exposing the wire object's internal storage.
|
|
28
|
+
*
|
|
29
|
+
* @template F - The pricing-feature literal union of the input.
|
|
30
|
+
* @param wire - Already-validated wire shape.
|
|
31
|
+
* @returns A new record with the same defaults and a copied feature array.
|
|
32
|
+
*/
|
|
33
|
+
function copyPriceInformation(wire) {
|
|
34
|
+
return {
|
|
35
|
+
defaultPriceInRobux: wire.defaultPriceInRobux ?? void 0,
|
|
36
|
+
enabledFeatures: [...wire.enabledFeatures]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { isPriceInformationLike as n, copyPriceInformation as t };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=price-information-CmpscMc4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"price-information-CmpscMc4.mjs","names":[],"sources":["../src/internal/price-information.ts"],"sourcesContent":["import { isRecord } from \"./utils/is-record.ts\";\n\n/**\n * Wire shape shared by every Roblox commerce resource that carries a\n * `priceInformation` block (game passes, developer products, ...). Resources\n * vary in the literal set their `enabledFeatures` may contain, so the feature\n * type is left as a parameter `F`.\n *\n * @template F - The string-literal union for this resource's pricing-feature flags.\n */\nexport interface PriceInformationLike<F extends string> {\n\t/** Default Robux price; `undefined` when the schema returns null. */\n\treadonly defaultPriceInRobux: number | undefined;\n\t/** Enabled pricing feature flags, in the order returned by the API. */\n\treadonly enabledFeatures: ReadonlyArray<F>;\n}\n\n/**\n * Narrows `value` to {@link PriceInformationLike} for a given feature literal\n * union by delegating per-element validation to the supplied `isFeature`\n * predicate.\n *\n * @template F - The pricing-feature literal union the caller wants to narrow to.\n * @param value - Unknown wire value to validate.\n * @param isFeature - Type guard for a single `enabledFeatures` element.\n * @returns `true` when `value` is a record whose `defaultPriceInRobux` is a\n * number, `null`, or absent and whose `enabledFeatures` is an array of\n * values that all satisfy `isFeature`.\n */\nexport function isPriceInformationLike<F extends string>(\n\tvalue: unknown,\n\tisFeature: (candidate: unknown) => candidate is F,\n): value is PriceInformationLike<F> {\n\tif (!isRecord(value)) {\n\t\treturn false;\n\t}\n\n\tconst defaultPrice = value[\"defaultPriceInRobux\"] ?? undefined;\n\tif (defaultPrice !== undefined && typeof defaultPrice !== \"number\") {\n\t\treturn false;\n\t}\n\n\tconst features = value[\"enabledFeatures\"];\n\tif (!Array.isArray(features)) {\n\t\treturn false;\n\t}\n\n\tfor (const feature of features) {\n\t\tif (!isFeature(feature)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/**\n * Returns a fresh {@link PriceInformationLike} value with a new\n * `enabledFeatures` array, so the caller can hand the result on without\n * exposing the wire object's internal storage.\n *\n * @template F - The pricing-feature literal union of the input.\n * @param wire - Already-validated wire shape.\n * @returns A new record with the same defaults and a copied feature array.\n */\nexport function copyPriceInformation<F extends string>(\n\twire: PriceInformationLike<F>,\n): PriceInformationLike<F> {\n\treturn {\n\t\tdefaultPriceInRobux: wire.defaultPriceInRobux ?? undefined,\n\t\tenabledFeatures: [...wire.enabledFeatures],\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;AA6BA,SAAgB,uBACf,OACA,WACmC;AACnC,KAAI,CAAC,SAAS,MAAM,CACnB,QAAO;CAGR,MAAM,eAAe,MAAM,0BAA0B,KAAA;AACrD,KAAI,iBAAiB,KAAA,KAAa,OAAO,iBAAiB,SACzD,QAAO;CAGR,MAAM,WAAW,MAAM;AACvB,KAAI,CAAC,MAAM,QAAQ,SAAS,CAC3B,QAAO;AAGR,MAAK,MAAM,WAAW,SACrB,KAAI,CAAC,UAAU,QAAQ,CACtB,QAAO;AAIT,QAAO;;;;;;;;;;;AAYR,SAAgB,qBACf,MAC0B;AAC1B,QAAO;EACN,qBAAqB,KAAK,uBAAuB,KAAA;EACjD,iBAAiB,CAAC,GAAG,KAAK,gBAAgB;EAC1C"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
//#region src/errors/base.ts
|
|
2
|
+
/**
|
|
3
|
+
* Base error class for all Open Cloud SDK errors.
|
|
4
|
+
*
|
|
5
|
+
* All specific error types (RateLimitError, ApiError, NetworkError)
|
|
6
|
+
* extend this class, enabling `instanceof OpenCloudError` checks.
|
|
7
|
+
*/
|
|
8
|
+
var OpenCloudError = class extends Error {
|
|
9
|
+
name = "OpenCloudError";
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/errors/api-error.ts
|
|
13
|
+
/**
|
|
14
|
+
* Thrown when the Roblox Open Cloud API returns a non-2xx response
|
|
15
|
+
* that is not a rate limit (429).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { ApiError } from "@bedrock-rbx/ocale";
|
|
21
|
+
*
|
|
22
|
+
* const error = new ApiError("Game pass not found", {
|
|
23
|
+
* code: "NotFound",
|
|
24
|
+
* statusCode: 404,
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* expect(error).toBeInstanceOf(ApiError);
|
|
28
|
+
* expect(error.statusCode).toBe(404);
|
|
29
|
+
* expect(error.code).toBe("NotFound");
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
var ApiError = class extends OpenCloudError {
|
|
33
|
+
code;
|
|
34
|
+
name = "ApiError";
|
|
35
|
+
statusCode;
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new ApiError.
|
|
38
|
+
*
|
|
39
|
+
* @param message - Human-readable error description.
|
|
40
|
+
* @param options - Error options including status code and optional error code.
|
|
41
|
+
*/
|
|
42
|
+
constructor(message, options) {
|
|
43
|
+
super(message, options);
|
|
44
|
+
this.statusCode = options.statusCode;
|
|
45
|
+
this.code = options.code;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/errors/network-error.ts
|
|
50
|
+
/**
|
|
51
|
+
* Thrown when a network-level failure prevents the request from reaching
|
|
52
|
+
* the Roblox Open Cloud API (e.g., DNS resolution failure, connection timeout).
|
|
53
|
+
*/
|
|
54
|
+
var NetworkError = class extends OpenCloudError {
|
|
55
|
+
name = "NetworkError";
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/errors/permission-error.ts
|
|
59
|
+
/**
|
|
60
|
+
* Thrown when the Roblox Open Cloud API returns a 401 or 403 for an operation
|
|
61
|
+
* whose required scopes are known. Subclass of {@link ApiError} carrying the
|
|
62
|
+
* scope strings the caller's credential is missing plus the operation key, so
|
|
63
|
+
* a CLI consumer can tell the user exactly which scope to grant on their API
|
|
64
|
+
* key.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
*
|
|
68
|
+
* ```ts
|
|
69
|
+
* import { PermissionError } from "@bedrock-rbx/ocale";
|
|
70
|
+
*
|
|
71
|
+
* const error = new PermissionError("HTTP 403", {
|
|
72
|
+
* operationKey: "developer-products.create",
|
|
73
|
+
* requiredScopes: ["creator-store-product:write"],
|
|
74
|
+
* statusCode: 403,
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* expect(error).toBeInstanceOf(PermissionError);
|
|
78
|
+
* expect(error.requiredScopes).toStrictEqual(["creator-store-product:write"]);
|
|
79
|
+
* expect(error.operationKey).toBe("developer-products.create");
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
var PermissionError = class extends ApiError {
|
|
83
|
+
name = "PermissionError";
|
|
84
|
+
operationKey;
|
|
85
|
+
requiredScopes;
|
|
86
|
+
/**
|
|
87
|
+
* Creates a new PermissionError.
|
|
88
|
+
*
|
|
89
|
+
* @param message - Human-readable error description.
|
|
90
|
+
* @param options - Error options including status code, the operation key,
|
|
91
|
+
* and the scopes the caller's credential must carry.
|
|
92
|
+
*/
|
|
93
|
+
constructor(message, options) {
|
|
94
|
+
super(message, options);
|
|
95
|
+
this.operationKey = options.operationKey;
|
|
96
|
+
this.requiredScopes = options.requiredScopes;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/errors/rate-limit.ts
|
|
101
|
+
/**
|
|
102
|
+
* Thrown when the Roblox Open Cloud API returns a 429 Too Many Requests response.
|
|
103
|
+
* Contains the server-suggested retry delay.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
*
|
|
107
|
+
* ```ts
|
|
108
|
+
* import { RateLimitError } from "@bedrock-rbx/ocale";
|
|
109
|
+
*
|
|
110
|
+
* const error = new RateLimitError("Too many requests", {
|
|
111
|
+
* retryAfterSeconds: 30,
|
|
112
|
+
* });
|
|
113
|
+
*
|
|
114
|
+
* expect(error).toBeInstanceOf(RateLimitError);
|
|
115
|
+
* expect(error.retryAfterSeconds).toBe(30);
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
var RateLimitError = class extends OpenCloudError {
|
|
119
|
+
name = "RateLimitError";
|
|
120
|
+
retryAfterSeconds;
|
|
121
|
+
/**
|
|
122
|
+
* Creates a new RateLimitError.
|
|
123
|
+
*
|
|
124
|
+
* @param message - Human-readable error description.
|
|
125
|
+
* @param options - Error options including the retry delay.
|
|
126
|
+
*/
|
|
127
|
+
constructor(message, options) {
|
|
128
|
+
super(message, options);
|
|
129
|
+
this.retryAfterSeconds = options.retryAfterSeconds;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
//#endregion
|
|
133
|
+
export { OpenCloudError as a, ApiError as i, PermissionError as n, NetworkError as r, RateLimitError as t };
|
|
134
|
+
|
|
135
|
+
//# sourceMappingURL=rate-limit-BBU_4xnZ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|