@bedrock-rbx/ocale 0.1.0-beta.2 → 0.1.0-beta.20

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.
Files changed (81) hide show
  1. package/README.md +129 -0
  2. package/dist/badges.d.mts +33 -2
  3. package/dist/badges.d.mts.map +1 -1
  4. package/dist/badges.mjs +12 -4
  5. package/dist/badges.mjs.map +1 -1
  6. package/dist/{data.generated-BtkDGH8C.d.mts → data.generated-B2ceLfSn.d.mts} +9 -1
  7. package/dist/data.generated-B2ceLfSn.d.mts.map +1 -0
  8. package/dist/developer-products.d.mts +38 -2
  9. package/dist/developer-products.d.mts.map +1 -1
  10. package/dist/developer-products.mjs +22 -5
  11. package/dist/developer-products.mjs.map +1 -1
  12. package/dist/game-passes.d.mts +40 -2
  13. package/dist/game-passes.d.mts.map +1 -1
  14. package/dist/game-passes.mjs +22 -5
  15. package/dist/game-passes.mjs.map +1 -1
  16. package/dist/index.d.mts +110 -12
  17. package/dist/index.d.mts.map +1 -1
  18. package/dist/index.mjs +5 -3
  19. package/dist/locales.d.mts +1 -1
  20. package/dist/locales.mjs +2 -0
  21. package/dist/locales.mjs.map +1 -1
  22. package/dist/luau-execution.d.mts +112 -6
  23. package/dist/luau-execution.d.mts.map +1 -1
  24. package/dist/luau-execution.mjs +129 -4
  25. package/dist/luau-execution.mjs.map +1 -1
  26. package/dist/places.d.mts +64 -6
  27. package/dist/places.d.mts.map +1 -1
  28. package/dist/places.mjs +33 -61
  29. package/dist/places.mjs.map +1 -1
  30. package/dist/poll-timeout-C0nmJzOd.mjs +83 -0
  31. package/dist/poll-timeout-C0nmJzOd.mjs.map +1 -0
  32. package/dist/polling-helpers-Cb4j4cq7.mjs +787 -0
  33. package/dist/polling-helpers-Cb4j4cq7.mjs.map +1 -0
  34. package/dist/polling-rj0dGREg.d.mts +312 -0
  35. package/dist/polling-rj0dGREg.d.mts.map +1 -0
  36. package/dist/{price-information-s7DY0GV2.mjs → price-information-Bu87sPyh.mjs} +2 -2
  37. package/dist/{price-information-s7DY0GV2.mjs.map → price-information-Bu87sPyh.mjs.map} +1 -1
  38. package/dist/{rate-limit-CKfuhxT1.mjs → rate-limit-Co9i28qi.mjs} +24 -38
  39. package/dist/rate-limit-Co9i28qi.mjs.map +1 -0
  40. package/dist/{resource-client-Wi4Mwqy5.mjs → resource-client-lE7Tg3BK.mjs} +349 -241
  41. package/dist/resource-client-lE7Tg3BK.mjs.map +1 -0
  42. package/dist/retry-CbHBw60o.mjs +380 -0
  43. package/dist/retry-CbHBw60o.mjs.map +1 -0
  44. package/dist/retry-r1TXe5Zd.d.mts +189 -0
  45. package/dist/retry-r1TXe5Zd.d.mts.map +1 -0
  46. package/dist/signatures-JF-7Psce.mjs +59 -0
  47. package/dist/signatures-JF-7Psce.mjs.map +1 -0
  48. package/dist/storage.d.mts +322 -31
  49. package/dist/storage.d.mts.map +1 -1
  50. package/dist/storage.mjs +506 -18
  51. package/dist/storage.mjs.map +1 -1
  52. package/dist/testing.d.mts +727 -0
  53. package/dist/testing.d.mts.map +1 -0
  54. package/dist/testing.mjs +457 -0
  55. package/dist/testing.mjs.map +1 -0
  56. package/dist/{to-blob-1BtHsDGK.mjs → to-blob-RPqMBuft.mjs} +1 -1
  57. package/dist/{to-blob-1BtHsDGK.mjs.map → to-blob-RPqMBuft.mjs.map} +1 -1
  58. package/dist/{types-Cp8w8uwA.d.mts → types-CRiJg5EQ.d.mts} +46 -4
  59. package/dist/types-CRiJg5EQ.d.mts.map +1 -0
  60. package/dist/universes.d.mts +47 -10
  61. package/dist/universes.d.mts.map +1 -1
  62. package/dist/universes.mjs +7 -5
  63. package/dist/universes.mjs.map +1 -1
  64. package/dist/{validation-b7KAoEio.mjs → validation-9oU6qNNQ.mjs} +4 -2
  65. package/dist/validation-9oU6qNNQ.mjs.map +1 -0
  66. package/dist/wire-D3K-a-UP.d.mts +37 -0
  67. package/dist/wire-D3K-a-UP.d.mts.map +1 -0
  68. package/package.json +9 -7
  69. package/dist/data.generated-BtkDGH8C.d.mts.map +0 -1
  70. package/dist/is-date-time-string-Cuf1TaSC.mjs +0 -19
  71. package/dist/is-date-time-string-Cuf1TaSC.mjs.map +0 -1
  72. package/dist/rate-limit-CKfuhxT1.mjs.map +0 -1
  73. package/dist/rate-limit-DzHBFwps.d.mts +0 -92
  74. package/dist/rate-limit-DzHBFwps.d.mts.map +0 -1
  75. package/dist/resource-client-Wi4Mwqy5.mjs.map +0 -1
  76. package/dist/specs-Co6qYp_E.mjs +0 -309
  77. package/dist/specs-Co6qYp_E.mjs.map +0 -1
  78. package/dist/types-BZ0959rh.d.mts +0 -149
  79. package/dist/types-BZ0959rh.d.mts.map +0 -1
  80. package/dist/types-Cp8w8uwA.d.mts.map +0 -1
  81. package/dist/validation-b7KAoEio.mjs.map +0 -1
@@ -0,0 +1,787 @@
1
+ import { n as NetworkError, r as ApiError } from "./rate-limit-Co9i28qi.mjs";
2
+ import { c as findErrorCode, n as IDEMPOTENT_METHOD_DEFAULTS, r as TRANSIENT_TRANSPORT_CODES, t as CREATE_METHOD_DEFAULTS } from "./retry-CbHBw60o.mjs";
3
+ import { n as PollAbortedError, t as PollTimeoutError } from "./poll-timeout-C0nmJzOd.mjs";
4
+ import { t as ValidationError } from "./validation-9oU6qNNQ.mjs";
5
+ import { a as isDateTimeString, i as isRecord, n as okRequest } from "./resource-client-lE7Tg3BK.mjs";
6
+ //#region src/domains/cloud-v2/luau-execution-task-logs/builders.ts
7
+ /**
8
+ * Builds a `GET` request for the Open Cloud "list Luau execution session
9
+ * task logs" endpoint. The endpoint requires the maximal x-aep-resource
10
+ * path shape (universe, place, version, session, task), so the supplied
11
+ * ref must include `versionId` and `sessionId`; refs extracted from the
12
+ * narrower path formats are rejected with a {@link ValidationError}.
13
+ *
14
+ * The `view` query parameter is hard-coded to `STRUCTURED` so callers
15
+ * always receive typed structured messages. No public `view` parameter
16
+ * is exposed.
17
+ *
18
+ * @param parameters - Task ref, and optional `pageSize` and `pageToken`
19
+ * pagination controls.
20
+ * @returns A success result wrapping the request, or a
21
+ * {@link ValidationError} when the ref is missing `versionId` or
22
+ * `sessionId`.
23
+ */
24
+ function buildListLogsRequest(parameters) {
25
+ const { pageSize, pageToken, ref } = parameters;
26
+ const { placeId, sessionId, taskId, universeId, versionId } = ref;
27
+ if (versionId === void 0) return {
28
+ err: new ValidationError("Task ref is missing versionId; cannot list logs", { code: "incomplete_ref" }),
29
+ success: false
30
+ };
31
+ if (sessionId === void 0) return {
32
+ err: new ValidationError("Task ref is missing sessionId; cannot list logs", { code: "incomplete_ref" }),
33
+ success: false
34
+ };
35
+ return {
36
+ data: {
37
+ method: "GET",
38
+ url: `${`/cloud/v2/universes/${universeId}/places/${placeId}/versions/${versionId}/luau-execution-sessions/${sessionId}/tasks/${taskId}/logs`}?${buildQuery(pageSize, pageToken).toString()}`
39
+ },
40
+ success: true
41
+ };
42
+ }
43
+ function buildQuery(pageSize, pageToken) {
44
+ const query = new URLSearchParams({ view: "STRUCTURED" });
45
+ if (pageSize !== void 0) query.append("maxPageSize", String(pageSize));
46
+ if (pageToken !== void 0) query.append("pageToken", pageToken);
47
+ return query;
48
+ }
49
+ /**
50
+ * Per-second request ceiling for listing Luau execution task logs,
51
+ * sourced from `x-roblox-rate-limits.perApiKeyOwner` on the
52
+ * `Cloud_ListLuauExecutionSessionTaskLogs` operation (45 requests per
53
+ * minute per API key owner).
54
+ */
55
+ const LIST_LOGS_OPERATION_LIMIT = Object.freeze({
56
+ maxPerSecond: 45 / 60,
57
+ operationKey: "luau-execution-task-logs.list"
58
+ });
59
+ /**
60
+ * Scopes required to list Luau execution task logs, sourced from
61
+ * `x-roblox-scopes` on the list-logs operation in the vendored OpenAPI
62
+ * schema. Surfaced via the `requiredScopes` field of the per-method
63
+ * spec so a 401 or 403 ApiError is upgraded to a `PermissionError`
64
+ * naming the missing scope. Only `:read` is required as the
65
+ * minimum-privilege scope for this read-only operation.
66
+ */
67
+ const LIST_LOGS_REQUIRED_SCOPES = Object.freeze(["universe.place.luau-execution-session:read"]);
68
+ //#endregion
69
+ //#region src/domains/cloud-v2/luau-execution-task-logs/parsers.ts
70
+ const MALFORMED_LOGS_MESSAGE = "Malformed list-luau-execution-task-logs response";
71
+ /**
72
+ * Parses a successful Open Cloud list-luau-execution-task-logs response
73
+ * body into the public {@link LogPage} shape. Chunks are flattened into
74
+ * a single ordered array of {@link LogMessage} values. The
75
+ * `MESSAGE_TYPE_UNSPECIFIED` sentinel is rejected.
76
+ *
77
+ * @param response - The full {@link HttpResponse} from the Open Cloud API.
78
+ * @returns A success result wrapping the parsed {@link LogPage}, or an
79
+ * {@link ApiError} when the body does not match a supported shape.
80
+ */
81
+ function parseListLogsResponse(response) {
82
+ const { body, status: statusCode } = response;
83
+ if (!isRecord(body)) return malformed$1(statusCode);
84
+ const rawChunks = body["luauExecutionSessionTaskLogs"] ?? void 0;
85
+ if (!isOptionalLogChunks(rawChunks)) return malformed$1(statusCode);
86
+ const rawToken = body["nextPageToken"] ?? void 0;
87
+ if (rawToken !== void 0 && typeof rawToken !== "string") return malformed$1(statusCode);
88
+ const messages = [];
89
+ for (const chunk of rawChunks ?? []) for (const wireMessage of chunk.structuredMessages ?? []) messages.push({
90
+ createTime: wireMessage.createTime,
91
+ message: wireMessage.message,
92
+ messageType: wireMessage.messageType
93
+ });
94
+ return {
95
+ data: {
96
+ messages,
97
+ nextPageToken: rawToken
98
+ },
99
+ success: true
100
+ };
101
+ }
102
+ function isAcceptedMessageType(value) {
103
+ return value === "OUTPUT" || value === "INFO" || value === "WARNING" || value === "ERROR";
104
+ }
105
+ function isLogMessageWire(value) {
106
+ return isRecord(value) && typeof value["createTime"] === "string" && typeof value["message"] === "string" && isAcceptedMessageType(value["messageType"]);
107
+ }
108
+ function isOptionalStructuredMessages(value) {
109
+ return value === void 0 || Array.isArray(value) && value.every((item) => isLogMessageWire(item));
110
+ }
111
+ function isLogChunkWire(value) {
112
+ return isRecord(value) && isOptionalStructuredMessages(value["structuredMessages"]);
113
+ }
114
+ function isOptionalLogChunks(value) {
115
+ return value === void 0 || Array.isArray(value) && value.every((item) => isLogChunkWire(item));
116
+ }
117
+ function malformed$1(statusCode) {
118
+ return {
119
+ err: new ApiError(MALFORMED_LOGS_MESSAGE, { statusCode }),
120
+ success: false
121
+ };
122
+ }
123
+ //#endregion
124
+ //#region src/domains/cloud-v2/luau-execution-task-logs/specs.ts
125
+ function makeSpec$1(spec) {
126
+ return Object.freeze(spec);
127
+ }
128
+ /**
129
+ * Per-method dispatch spec for listing the structured log messages
130
+ * produced by a Luau execution task. Frozen at module scope so both
131
+ * the top-level `LuauExecutionClient` and the `luauExecution` Operation
132
+ * Group on `PlacesClient` share the same instance reference.
133
+ */
134
+ const LIST_LOGS_SPEC = makeSpec$1({
135
+ buildRequest: buildListLogsRequest,
136
+ methodDefaults: IDEMPOTENT_METHOD_DEFAULTS,
137
+ methodKind: "idempotent",
138
+ operationLimit: LIST_LOGS_OPERATION_LIMIT,
139
+ parse: parseListLogsResponse,
140
+ requiredScopes: LIST_LOGS_REQUIRED_SCOPES
141
+ });
142
+ //#endregion
143
+ //#region src/domains/cloud-v2/luau-execution-tasks/builders.ts
144
+ const JSON_HEADERS = { "content-type": "application/json" };
145
+ /**
146
+ * Builds a `POST` request for the Open Cloud "create Luau execution
147
+ * session task" endpoint, targeting the place's head version. Serializes
148
+ * `timeoutSeconds` into the wire's duration string format (`"<n>s"`)
149
+ * when supplied.
150
+ *
151
+ * @param parameters - Universe and place identifiers, the script body,
152
+ * and an optional `timeoutSeconds`.
153
+ * @returns A pure {@link HttpRequest} describing the submit call.
154
+ */
155
+ function buildSubmitAtHeadRequest(parameters) {
156
+ const { placeId, universeId } = parameters;
157
+ return {
158
+ body: buildSubmitBody(parameters),
159
+ headers: JSON_HEADERS,
160
+ method: "POST",
161
+ url: `/cloud/v2/universes/${universeId}/places/${placeId}/luau-execution-session-tasks`
162
+ };
163
+ }
164
+ /**
165
+ * Builds a `POST` request for the Open Cloud "create Luau execution
166
+ * session task" endpoint, targeting a specific place version. Differs
167
+ * from {@link buildSubmitAtHeadRequest} only in URL shape: the path
168
+ * includes the `versions/{versionId}` segment so the script runs
169
+ * against that exact place version instead of the live head.
170
+ *
171
+ * @param parameters - Universe, place, and version identifiers, the
172
+ * script body, and an optional `timeoutSeconds`.
173
+ * @returns A pure {@link HttpRequest} describing the submit call.
174
+ */
175
+ function buildSubmitAtVersionRequest(parameters) {
176
+ const { placeId, universeId, versionId } = parameters;
177
+ return {
178
+ body: buildSubmitBody(parameters),
179
+ headers: JSON_HEADERS,
180
+ method: "POST",
181
+ url: `/cloud/v2/universes/${universeId}/places/${placeId}/versions/${versionId}/luau-execution-session-tasks`
182
+ };
183
+ }
184
+ /**
185
+ * Builds a `GET` request for the Open Cloud "read Luau execution session
186
+ * task" endpoint. The endpoint accepts only the maximal x-aep-resource
187
+ * path shape (universe, place, version, session, task), so the supplied
188
+ * ref must include `versionId` and `sessionId`; refs extracted from the
189
+ * narrower path formats are rejected with a {@link ValidationError}.
190
+ *
191
+ * @param parameters - Task ref and optional view selector. When `view`
192
+ * is omitted, no `?view=` query is sent and the server applies its
193
+ * own default (`BASIC`).
194
+ * @returns A success result wrapping the request, or a
195
+ * {@link ValidationError} when the ref is missing `versionId` or
196
+ * `sessionId`.
197
+ */
198
+ function buildGetRequest(parameters) {
199
+ const { ref, view } = parameters;
200
+ const { placeId, sessionId, taskId, universeId, versionId } = ref;
201
+ if (versionId === void 0) return {
202
+ err: new ValidationError("Task ref is missing versionId; cannot GET", { code: "incomplete_ref" }),
203
+ success: false
204
+ };
205
+ if (sessionId === void 0) return {
206
+ err: new ValidationError("Task ref is missing sessionId; cannot GET", { code: "incomplete_ref" }),
207
+ success: false
208
+ };
209
+ const base = `/cloud/v2/universes/${universeId}/places/${placeId}/versions/${versionId}/luau-execution-sessions/${sessionId}/tasks/${taskId}`;
210
+ return {
211
+ data: {
212
+ method: "GET",
213
+ url: view === void 0 ? base : `${base}?view=${view}`
214
+ },
215
+ success: true
216
+ };
217
+ }
218
+ function buildSubmitBody(parameters) {
219
+ const { binaryInput, enableBinaryOutput: shouldEnableBinaryOutput, script, timeoutSeconds } = parameters;
220
+ const body = { script };
221
+ if (timeoutSeconds !== void 0) body["timeout"] = `${timeoutSeconds}s`;
222
+ if (binaryInput !== void 0) body["binaryInput"] = binaryInput;
223
+ if (shouldEnableBinaryOutput !== void 0) body["enableBinaryOutput"] = shouldEnableBinaryOutput;
224
+ return body;
225
+ }
226
+ //#endregion
227
+ //#region src/domains/cloud-v2/luau-execution-tasks/operations.ts
228
+ const SUBMIT_PER_MINUTE = 40;
229
+ const GET_PER_MINUTE = 200;
230
+ const SECONDS_PER_MINUTE = 60;
231
+ /**
232
+ * Per-second request ceiling for submitting a Luau execution task,
233
+ * sourced from `x-roblox-rate-limits.perApiKeyOwner` on the
234
+ * `Cloud_CreateLuauExecutionSessionTask__Using_Universes` operation
235
+ * (40 requests per minute per API key owner). The two URL shapes
236
+ * (head and version) share this queue because Roblox attributes both
237
+ * to the same per-minute quota.
238
+ */
239
+ const SUBMIT_OPERATION_LIMIT = Object.freeze({
240
+ maxPerSecond: SUBMIT_PER_MINUTE / SECONDS_PER_MINUTE,
241
+ operationKey: "luau-execution-tasks.submit"
242
+ });
243
+ /**
244
+ * Per-second request ceiling for fetching a Luau execution task,
245
+ * sourced from `x-roblox-rate-limits.perApiKeyOwner` on the
246
+ * `Cloud_GetLuauExecutionSessionTask` operation (200 requests per
247
+ * minute per API key owner).
248
+ */
249
+ const GET_OPERATION_LIMIT = Object.freeze({
250
+ maxPerSecond: GET_PER_MINUTE / SECONDS_PER_MINUTE,
251
+ operationKey: "luau-execution-tasks.get"
252
+ });
253
+ /**
254
+ * Scopes required to submit a Luau execution task, sourced from
255
+ * `x-roblox-scopes` on the create operation in the vendored OpenAPI
256
+ * schema. Surfaced via the `requiredScopes` field of the per-method
257
+ * spec so a 401 or 403 ApiError is upgraded to a `PermissionError`
258
+ * naming the missing scope.
259
+ */
260
+ const SUBMIT_REQUIRED_SCOPES = Object.freeze(["universe.place.luau-execution-session:write"]);
261
+ /**
262
+ * Scopes required to fetch a Luau execution task, sourced from
263
+ * `x-roblox-scopes` on the get operation. The `:write` scope also
264
+ * grants read in upstream auth, but we surface only `:read` here as
265
+ * the minimum-privilege requirement for this method.
266
+ */
267
+ const GET_REQUIRED_SCOPES = Object.freeze(["universe.place.luau-execution-session:read"]);
268
+ //#endregion
269
+ //#region src/domains/cloud-v2/luau-execution-tasks/parsers.ts
270
+ const MALFORMED_TASK_MESSAGE = "Malformed luau-execution-session-task response";
271
+ const PATH_PATTERN = /^universes\/(\d+)\/places\/(\d+)(?:\/versions\/(\d+))?(?:\/luau-execution-sessions\/([^/]+)\/tasks\/([^/]+)|\/luau-execution-session-tasks\/([^/]+))$/;
272
+ /**
273
+ * Parses a successful Open Cloud `LuauExecutionSessionTask` response
274
+ * body into the public {@link LuauExecutionTask} discriminated union.
275
+ * Handles every supported task state (in-progress, COMPLETE, FAILED)
276
+ * across all four x-aep-resource path shapes the server returns.
277
+ *
278
+ * @param response - The full {@link HttpResponse} from the Open Cloud
279
+ * API.
280
+ * @returns A success result wrapping the parsed task, or an
281
+ * {@link ApiError} when the body or path do not match a supported
282
+ * shape.
283
+ */
284
+ function parseLuauExecutionTaskResponse(response) {
285
+ const { body, status: statusCode } = response;
286
+ if (!isLuauExecutionTaskWire(body)) return malformed(statusCode);
287
+ const ref = parseTaskRef(body.path);
288
+ if (ref === void 0) return malformed(statusCode);
289
+ const timeoutSeconds = parseTimeoutSeconds(body.timeout);
290
+ const createdAt = parseOptionalDate(body.createTime);
291
+ const updatedAt = parseOptionalDate(body.updateTime);
292
+ if (body.state === "COMPLETE") return parseCompleteTask({
293
+ body,
294
+ createdAt,
295
+ ref,
296
+ statusCode,
297
+ timeoutSeconds,
298
+ updatedAt
299
+ });
300
+ if (body.state === "FAILED") return parseFailedTask({
301
+ body,
302
+ createdAt,
303
+ ref,
304
+ statusCode,
305
+ timeoutSeconds,
306
+ updatedAt
307
+ });
308
+ return parseInProgressTask({
309
+ body,
310
+ createdAt,
311
+ ref,
312
+ state: body.state,
313
+ statusCode,
314
+ timeoutSeconds,
315
+ updatedAt
316
+ });
317
+ }
318
+ function isAcceptedWireState(state) {
319
+ return state === "QUEUED" || state === "PROCESSING" || state === "CANCELLED" || state === "COMPLETE" || state === "FAILED";
320
+ }
321
+ function isErrorWireCode(code) {
322
+ return code === "SCRIPT_ERROR" || code === "DEADLINE_EXCEEDED" || code === "OUTPUT_SIZE_LIMIT_EXCEEDED" || code === "INTERNAL_ERROR";
323
+ }
324
+ function isErrorWire(value) {
325
+ return isRecord(value) && isErrorWireCode(value["code"]) && typeof value["message"] === "string";
326
+ }
327
+ function isOptionalErrorWire(value) {
328
+ return value === void 0 || isErrorWire(value);
329
+ }
330
+ function isOutputWire(value) {
331
+ return isRecord(value) && Array.isArray(value["results"]);
332
+ }
333
+ function isOptionalOutputWire(value) {
334
+ return value === void 0 || isOutputWire(value);
335
+ }
336
+ function isOptionalString(value) {
337
+ return value === void 0 || typeof value === "string";
338
+ }
339
+ function isOptionalBoolean(value) {
340
+ return value === void 0 || typeof value === "boolean";
341
+ }
342
+ function isOptionalDateTimeString(value) {
343
+ return value === void 0 || isDateTimeString(value);
344
+ }
345
+ function isLuauExecutionTaskWire(body) {
346
+ return isRecord(body) && typeof body["path"] === "string" && isOptionalDateTimeString(body["createTime"]) && isOptionalDateTimeString(body["updateTime"]) && isAcceptedWireState(body["state"]) && typeof body["user"] === "string" && isOptionalOutputWire(body["output"]) && isOptionalErrorWire(body["error"]) && isOptionalDurationWire(body["timeout"]) && isOptionalString(body["binaryInput"]) && isOptionalBoolean(body["enableBinaryOutput"]) && isOptionalString(body["binaryOutputUri"]);
347
+ }
348
+ function parseOptionalDate(value) {
349
+ return value === void 0 ? void 0 : new Date(value);
350
+ }
351
+ const DURATION_PATTERN = /^(\d+)s$/;
352
+ function isOptionalDurationWire(value) {
353
+ return value === void 0 || typeof value === "string" && DURATION_PATTERN.test(value);
354
+ }
355
+ function parseTimeoutSeconds(value) {
356
+ if (value === void 0) return;
357
+ const seconds = DURATION_PATTERN.exec(value)?.[1];
358
+ if (seconds === void 0) return;
359
+ return Number.parseInt(seconds, 10);
360
+ }
361
+ function malformed(statusCode) {
362
+ return {
363
+ err: new ApiError(MALFORMED_TASK_MESSAGE, { statusCode }),
364
+ success: false
365
+ };
366
+ }
367
+ function parseInProgressTask(args) {
368
+ const { body, createdAt, ref, state, timeoutSeconds, updatedAt } = args;
369
+ return {
370
+ data: {
371
+ binaryInput: body.binaryInput,
372
+ binaryOutputUri: body.binaryOutputUri,
373
+ createdAt,
374
+ enableBinaryOutput: body.enableBinaryOutput,
375
+ ref,
376
+ state,
377
+ timeoutSeconds,
378
+ updatedAt,
379
+ user: body.user
380
+ },
381
+ success: true
382
+ };
383
+ }
384
+ function parseCompleteTask(args) {
385
+ const { body, createdAt, ref, statusCode, timeoutSeconds, updatedAt } = args;
386
+ if (body.output === void 0) return malformed(statusCode);
387
+ return {
388
+ data: {
389
+ binaryInput: body.binaryInput,
390
+ binaryOutputUri: body.binaryOutputUri,
391
+ createdAt,
392
+ enableBinaryOutput: body.enableBinaryOutput,
393
+ output: { results: body.output.results },
394
+ ref,
395
+ state: "COMPLETE",
396
+ timeoutSeconds,
397
+ updatedAt,
398
+ user: body.user
399
+ },
400
+ success: true
401
+ };
402
+ }
403
+ function parseFailedTask(args) {
404
+ const { body, createdAt, ref, statusCode, timeoutSeconds, updatedAt } = args;
405
+ if (body.error === void 0) return malformed(statusCode);
406
+ return {
407
+ data: {
408
+ binaryInput: body.binaryInput,
409
+ binaryOutputUri: body.binaryOutputUri,
410
+ createdAt,
411
+ enableBinaryOutput: body.enableBinaryOutput,
412
+ error: {
413
+ code: body.error.code,
414
+ message: body.error.message
415
+ },
416
+ ref,
417
+ state: "FAILED",
418
+ timeoutSeconds,
419
+ updatedAt,
420
+ user: body.user
421
+ },
422
+ success: true
423
+ };
424
+ }
425
+ function parseTaskRef(path) {
426
+ const match = PATH_PATTERN.exec(path);
427
+ if (match === null) return;
428
+ const [, universeId, placeId, versionId, sessionId, sessionTaskId, plainTaskId] = match;
429
+ const taskId = sessionTaskId ?? plainTaskId;
430
+ if (universeId === void 0 || placeId === void 0 || taskId === void 0) return;
431
+ return {
432
+ placeId,
433
+ sessionId,
434
+ taskId,
435
+ universeId,
436
+ versionId
437
+ };
438
+ }
439
+ //#endregion
440
+ //#region src/domains/cloud-v2/luau-execution-tasks/specs.ts
441
+ function makeSpec(spec) {
442
+ return Object.freeze(spec);
443
+ }
444
+ /**
445
+ * Per-method dispatch spec for submitting a Luau execution task at a
446
+ * place's head version. Frozen at module scope so both the top-level
447
+ * `LuauExecutionClient` and the `luauExecution` Operation Group on
448
+ * `PlacesClient` share the same instance reference.
449
+ */
450
+ const SUBMIT_HEAD_SPEC = makeSpec({
451
+ buildRequest: (parameters) => okRequest(buildSubmitAtHeadRequest(parameters)),
452
+ methodDefaults: CREATE_METHOD_DEFAULTS,
453
+ methodKind: "create",
454
+ operationLimit: SUBMIT_OPERATION_LIMIT,
455
+ parse: parseLuauExecutionTaskResponse,
456
+ requiredScopes: SUBMIT_REQUIRED_SCOPES
457
+ });
458
+ /**
459
+ * Per-method dispatch spec for submitting a Luau execution task at a
460
+ * specific place version. Shares the rate-limit queue and required
461
+ * scope set with {@link SUBMIT_HEAD_SPEC} because Roblox attributes
462
+ * both URL shapes to one per-minute quota.
463
+ */
464
+ const SUBMIT_VERSION_SPEC = makeSpec({
465
+ buildRequest: (parameters) => okRequest(buildSubmitAtVersionRequest(parameters)),
466
+ methodDefaults: CREATE_METHOD_DEFAULTS,
467
+ methodKind: "create",
468
+ operationLimit: SUBMIT_OPERATION_LIMIT,
469
+ parse: parseLuauExecutionTaskResponse,
470
+ requiredScopes: SUBMIT_REQUIRED_SCOPES
471
+ });
472
+ /**
473
+ * Per-method dispatch spec for fetching a Luau execution task. Uses
474
+ * idempotent retry semantics (429 and 5xx both retried) so reads
475
+ * recover transparently from transient server errors.
476
+ */
477
+ const GET_SPEC = makeSpec({
478
+ buildRequest: buildGetRequest,
479
+ methodDefaults: IDEMPOTENT_METHOD_DEFAULTS,
480
+ methodKind: "idempotent",
481
+ operationLimit: GET_OPERATION_LIMIT,
482
+ parse: parseLuauExecutionTaskResponse,
483
+ requiredScopes: GET_REQUIRED_SCOPES
484
+ });
485
+ /** Steady-state delay once elapsed time reaches the final tier bound. */
486
+ const STEADY_POLL_DELAY_MS = 5e3;
487
+ /**
488
+ * Fast-to-slow poll-cadence tiers keyed on elapsed wall-clock time. Elapsed
489
+ * times at or beyond the last `untilMs` fall through to
490
+ * {@link STEADY_POLL_DELAY_MS}.
491
+ */
492
+ const DEFAULT_POLL_TIERS = [{
493
+ delayMs: 500,
494
+ untilMs: 2e4
495
+ }, {
496
+ delayMs: 1e3,
497
+ untilMs: 6e4
498
+ }];
499
+ /**
500
+ * Default poll cadence as a function of elapsed wall-clock time since
501
+ * polling began. Polls quickly while a task is young so short runs resolve
502
+ * snappily, then eases off so a long run leaves rate-limit headroom for
503
+ * newer tasks: 0-20s is 500ms, 20-60s is 1000ms, 60s+ is 5000ms.
504
+ *
505
+ * @since 0.1.0
506
+ *
507
+ * @example
508
+ * ```ts
509
+ * import { defaultPollDelay } from "@bedrock-rbx/ocale/luau-execution";
510
+ *
511
+ * expect(defaultPollDelay(0)).toBe(500);
512
+ * expect(defaultPollDelay(30_000)).toBe(1000);
513
+ * expect(defaultPollDelay(120_000)).toBe(5000);
514
+ * ```
515
+ *
516
+ * @param elapsedMs - Milliseconds elapsed since polling started.
517
+ * @returns The delay in milliseconds to wait before the next poll.
518
+ */
519
+ function defaultPollDelay(elapsedMs) {
520
+ return DEFAULT_POLL_TIERS.find((candidate) => elapsedMs < candidate.untilMs)?.delayMs ?? STEADY_POLL_DELAY_MS;
521
+ }
522
+ /**
523
+ * Defaults the per-request `timeout` to the effective poll budget when the
524
+ * caller has not set one. A luau-execution submit and each poll `get` normally
525
+ * answer in well under a second (the submit endpoint enqueues the task without
526
+ * waiting for it to run), so the only job of a per-request deadline here is to
527
+ * bound a black-hole connection. Leaving these requests on the client-wide 30s
528
+ * default (tuned for snappy CRUD) turns a slow-but-alive backend into a
529
+ * self-abort, an error the retry layer never retries by construction, before
530
+ * the loop's wall-clock budget is ever consulted. Deriving the deadline from
531
+ * `timeoutMs` keeps a single request alive exactly as long as the caller
532
+ * already agreed to wait for the whole operation, so the backend can answer or
533
+ * surface a retryable status instead.
534
+ *
535
+ * @param options - The caller's poll and per-request options.
536
+ * @returns The options with `timeout` filled from the budget when it was unset.
537
+ */
538
+ function withBudgetRequestTimeout(options) {
539
+ if (options.timeout !== void 0) return options;
540
+ return {
541
+ ...options,
542
+ timeout: options.timeoutMs ?? 3e5
543
+ };
544
+ }
545
+ const ABORTED = Symbol("poll-aborted");
546
+ /**
547
+ * Core polling loop. Calls `deps.fetch()` repeatedly, sleeping
548
+ * `pollDelay(elapsedMs)` ms between iterations, until a terminal state
549
+ * is observed, the wall-clock budget is exhausted, or an `AbortSignal`
550
+ * fires. A transient transport failure ({@link NetworkError}) is tolerated
551
+ * and the loop continues, giving up only after `maxConsecutivePollFailures`
552
+ * consecutive failures; any other failure aborts immediately, since an API
553
+ * response (a 404 for a vanished task, a 403) means there is nothing left to
554
+ * poll. A successful poll resets the failure count.
555
+ *
556
+ * @param deps - Injected fetch, now, and sleep callbacks.
557
+ * @param options - Optional poll delay, timeout, failure cap, and abort signal.
558
+ * @returns The terminal task, or an error if aborted, timed out, or the transport keeps failing.
559
+ */
560
+ async function pollUntilDoneCore(deps, options = {}) {
561
+ const timeoutMs = options.timeoutMs ?? 3e5;
562
+ const pollDelay = options.pollDelay ?? defaultPollDelay;
563
+ const maxFailures = options.maxConsecutivePollFailures ?? 3;
564
+ const sig = options.signal;
565
+ const startedAt = deps.now();
566
+ if (sig?.aborted === true) return abortedResult(sig);
567
+ let state = {
568
+ consecutiveFailures: 0,
569
+ lastTask: void 0
570
+ };
571
+ for (;;) {
572
+ const elapsedMs = deps.now() - startedAt;
573
+ if (elapsedMs >= timeoutMs) return {
574
+ err: makeTimeout(state.lastTask, timeoutMs),
575
+ success: false
576
+ };
577
+ const action = applyOutcome(await fetchOnce(deps, sig), {
578
+ maxFailures,
579
+ signal: sig,
580
+ state
581
+ });
582
+ if (action.kind === "return") return action.result;
583
+ ({state} = action);
584
+ if (await sleepWithAbort({
585
+ ms: pollDelay(elapsedMs),
586
+ signal: sig,
587
+ sleep: deps.sleep
588
+ })) return abortedResult(sig);
589
+ }
590
+ }
591
+ function makeAborted(signal) {
592
+ return new PollAbortedError("Polling was aborted", { reason: signal?.reason });
593
+ }
594
+ function abortedResult(signal) {
595
+ return {
596
+ err: makeAborted(signal),
597
+ success: false
598
+ };
599
+ }
600
+ /**
601
+ * Maps a single fetch outcome to the next loop action. Terminal, failed, and
602
+ * aborted outcomes return immediately; a transient transport failure advances
603
+ * the consecutive-failure count and returns once it reaches `maxFailures`; a
604
+ * pending task resets the count and continues.
605
+ *
606
+ * @param outcome - The classified result of one poll fetch.
607
+ * @param context - The loop state, failure cap, and abort signal.
608
+ * @returns Whether to return a final Result or continue with updated state.
609
+ */
610
+ function applyOutcome(outcome, context) {
611
+ const { maxFailures, signal, state } = context;
612
+ switch (outcome.kind) {
613
+ case "aborted": return {
614
+ kind: "return",
615
+ result: abortedResult(signal)
616
+ };
617
+ case "failed": return {
618
+ kind: "return",
619
+ result: {
620
+ err: outcome.error,
621
+ success: false
622
+ }
623
+ };
624
+ case "pending": return {
625
+ kind: "continue",
626
+ state: {
627
+ consecutiveFailures: 0,
628
+ lastTask: outcome.task
629
+ }
630
+ };
631
+ case "terminal": return {
632
+ kind: "return",
633
+ result: {
634
+ data: outcome.task,
635
+ success: true
636
+ }
637
+ };
638
+ case "transient": {
639
+ const consecutiveFailures = state.consecutiveFailures + 1;
640
+ if (consecutiveFailures >= maxFailures) return {
641
+ kind: "return",
642
+ result: {
643
+ err: outcome.error,
644
+ success: false
645
+ }
646
+ };
647
+ return {
648
+ kind: "continue",
649
+ state: {
650
+ consecutiveFailures,
651
+ lastTask: state.lastTask
652
+ }
653
+ };
654
+ }
655
+ }
656
+ }
657
+ function abortObserver(signal) {
658
+ const { promise, resolve } = Promise.withResolvers();
659
+ function onAbort() {
660
+ resolve(ABORTED);
661
+ }
662
+ signal.addEventListener("abort", onAbort);
663
+ function cleanup() {
664
+ signal.removeEventListener("abort", onAbort);
665
+ }
666
+ return {
667
+ cleanup,
668
+ promise
669
+ };
670
+ }
671
+ async function raceWithAbort(promise, signal) {
672
+ if (signal === void 0) return promise;
673
+ if (signal.aborted) return ABORTED;
674
+ const observer = abortObserver(signal);
675
+ try {
676
+ return await Promise.race([promise, observer.promise]);
677
+ } finally {
678
+ observer.cleanup();
679
+ }
680
+ }
681
+ async function sleepWithAbort(options) {
682
+ const { ms, signal, sleep } = options;
683
+ return await raceWithAbort(sleep(ms), signal) === ABORTED;
684
+ }
685
+ function makeTimeout(task, timeoutMs) {
686
+ return new PollTimeoutError(`Polling timed out after ${timeoutMs} ms`, {
687
+ lastObservedTask: task,
688
+ timeoutMs
689
+ });
690
+ }
691
+ function isTerminal(task) {
692
+ return task.state === "COMPLETE" || task.state === "FAILED" || task.state === "CANCELLED";
693
+ }
694
+ /**
695
+ * A failed poll is worth re-polling only when it is a `NetworkError` carrying a
696
+ * known transient transport code. A self-aborted request timeout has no
697
+ * `code`, and an API response (4xx/5xx) is authoritative, so both abort the
698
+ * loop rather than being re-polled. Transient-ness is classified against the
699
+ * canonical `TRANSIENT_TRANSPORT_CODES` set; this is the loop's own tolerance
700
+ * dimension, distinct from the per-request `retryableTransportCodes` override
701
+ * (which governs request-level retries inside each poll). Loop tolerance is
702
+ * bounded separately by `maxConsecutivePollFailures`.
703
+ *
704
+ * @param error - The error returned by a failed poll.
705
+ * @returns `true` when the loop should tolerate and re-poll.
706
+ */
707
+ function isTransientTransport(error) {
708
+ if (!(error instanceof NetworkError)) return false;
709
+ const code = findErrorCode(error);
710
+ return code !== void 0 && TRANSIENT_TRANSPORT_CODES.includes(code);
711
+ }
712
+ async function fetchOnce(deps, signal) {
713
+ const fetchResult = await raceWithAbort(deps.fetch(), signal);
714
+ if (fetchResult === ABORTED) return { kind: "aborted" };
715
+ if (!fetchResult.success) return isTransientTransport(fetchResult.err) ? {
716
+ error: fetchResult.err,
717
+ kind: "transient"
718
+ } : {
719
+ error: fetchResult.err,
720
+ kind: "failed"
721
+ };
722
+ return isTerminal(fetchResult.data) ? {
723
+ kind: "terminal",
724
+ task: fetchResult.data
725
+ } : {
726
+ kind: "pending",
727
+ task: fetchResult.data
728
+ };
729
+ }
730
+ //#endregion
731
+ //#region src/resources/luau-execution/polling-helpers.ts
732
+ /**
733
+ * Builds the {@link PollDeps} bundle used by {@link pollUntilDoneCore},
734
+ * closing over the supplied {@link ResourceClient}, task ref, and
735
+ * per-request options so the core loop stays narrow.
736
+ *
737
+ * @param inner - The {@link ResourceClient} that issues each `tasks.get` call.
738
+ * @param args - The polling options and the task ref to fetch on every iteration.
739
+ * @returns A {@link PollDeps} bundle wiring `fetch`, `now`, and `sleep`.
740
+ */
741
+ function buildPollDeps(inner, args) {
742
+ return {
743
+ fetch: async () => {
744
+ return inner.execute({
745
+ options: args.options,
746
+ parameters: {
747
+ ref: args.ref,
748
+ view: "BASIC"
749
+ },
750
+ spec: GET_SPEC
751
+ });
752
+ },
753
+ now: Date.now,
754
+ sleep: inner.sleep
755
+ };
756
+ }
757
+ /**
758
+ * Submits a Luau execution task and polls it to a terminal state.
759
+ * Dispatches to the head-version or specific-version submit spec based on
760
+ * the presence of `versionId`, then delegates to {@link pollUntilDoneCore}.
761
+ *
762
+ * @param inner - The {@link ResourceClient} that issues submit and poll calls.
763
+ * @param args - The polling options and submit parameters.
764
+ * @returns A {@link Result} wrapping the terminal {@link LuauExecutionTask}, or
765
+ * the {@link OpenCloudError} that caused submit or polling to fail.
766
+ */
767
+ async function submitAndPoll(inner, args) {
768
+ const { options, parameters } = args;
769
+ const submitResult = await ("versionId" in parameters ? inner.execute({
770
+ options,
771
+ parameters,
772
+ spec: SUBMIT_VERSION_SPEC
773
+ }) : inner.execute({
774
+ options,
775
+ parameters,
776
+ spec: SUBMIT_HEAD_SPEC
777
+ }));
778
+ if (!submitResult.success) return submitResult;
779
+ return pollUntilDoneCore(buildPollDeps(inner, {
780
+ options,
781
+ ref: submitResult.data.ref
782
+ }), options);
783
+ }
784
+ //#endregion
785
+ export { withBudgetRequestTimeout as a, SUBMIT_VERSION_SPEC as c, pollUntilDoneCore as i, LIST_LOGS_SPEC as l, submitAndPoll as n, GET_SPEC as o, defaultPollDelay as r, SUBMIT_HEAD_SPEC as s, buildPollDeps as t };
786
+
787
+ //# sourceMappingURL=polling-helpers-Cb4j4cq7.mjs.map