@bedrock-rbx/ocale 0.1.0-beta.2 → 0.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/badges.d.mts +2 -2
- package/dist/badges.mjs +4 -4
- package/dist/{data.generated-BtkDGH8C.d.mts → data.generated-DdwXMiv9.d.mts} +1 -1
- package/dist/{data.generated-BtkDGH8C.d.mts.map → data.generated-DdwXMiv9.d.mts.map} +1 -1
- package/dist/developer-products.d.mts +2 -2
- package/dist/developer-products.mjs +5 -5
- package/dist/game-passes.d.mts +2 -2
- package/dist/game-passes.mjs +5 -5
- package/dist/index.d.mts +92 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -3
- package/dist/{is-date-time-string-Cuf1TaSC.mjs → is-date-time-string-DL6l9mo6.mjs} +1 -1
- package/dist/{is-date-time-string-Cuf1TaSC.mjs.map → is-date-time-string-DL6l9mo6.mjs.map} +1 -1
- package/dist/locales.d.mts +1 -1
- package/dist/luau-execution.d.mts +93 -6
- package/dist/luau-execution.d.mts.map +1 -1
- package/dist/luau-execution.mjs +115 -3
- package/dist/luau-execution.mjs.map +1 -1
- package/dist/permission-error-DOVtNq3A.mjs +46 -0
- package/dist/permission-error-DOVtNq3A.mjs.map +1 -0
- package/dist/places.d.mts +46 -6
- package/dist/places.d.mts.map +1 -1
- package/dist/places.mjs +25 -61
- package/dist/places.mjs.map +1 -1
- package/dist/poll-timeout-Dg_QFEqi.mjs +79 -0
- package/dist/poll-timeout-Dg_QFEqi.mjs.map +1 -0
- package/dist/{types-BZ0959rh.d.mts → polling-DDKKpz6Z.d.mts} +106 -2
- package/dist/polling-DDKKpz6Z.d.mts.map +1 -0
- package/dist/polling-helpers-DJqtcrCQ.mjs +636 -0
- package/dist/polling-helpers-DJqtcrCQ.mjs.map +1 -0
- package/dist/{price-information-s7DY0GV2.mjs → price-information-XyhlYrn8.mjs} +2 -2
- package/dist/{price-information-s7DY0GV2.mjs.map → price-information-XyhlYrn8.mjs.map} +1 -1
- package/dist/{rate-limit-DzHBFwps.d.mts → rate-limit-BHAddFXO.d.mts} +2 -2
- package/dist/{rate-limit-DzHBFwps.d.mts.map → rate-limit-BHAddFXO.d.mts.map} +1 -1
- package/dist/{rate-limit-CKfuhxT1.mjs → rate-limit-D1q2Js-z.mjs} +2 -44
- package/dist/rate-limit-D1q2Js-z.mjs.map +1 -0
- package/dist/{resource-client-Wi4Mwqy5.mjs → resource-client-D7HKNrOp.mjs} +12 -3
- package/dist/{resource-client-Wi4Mwqy5.mjs.map → resource-client-D7HKNrOp.mjs.map} +1 -1
- package/dist/signatures-9rpsTjPL.mjs +59 -0
- package/dist/signatures-9rpsTjPL.mjs.map +1 -0
- package/dist/storage.d.mts +7 -1
- package/dist/storage.d.mts.map +1 -1
- package/dist/storage.mjs +3 -3
- package/dist/testing.d.mts +497 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +367 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/{to-blob-1BtHsDGK.mjs → to-blob-B27VhoRp.mjs} +1 -1
- package/dist/{to-blob-1BtHsDGK.mjs.map → to-blob-B27VhoRp.mjs.map} +1 -1
- package/dist/{types-Cp8w8uwA.d.mts → types-BOhSh9ug.d.mts} +1 -1
- package/dist/{types-Cp8w8uwA.d.mts.map → types-BOhSh9ug.d.mts.map} +1 -1
- package/dist/universes.d.mts +3 -8
- package/dist/universes.d.mts.map +1 -1
- package/dist/universes.mjs +5 -5
- package/dist/{validation-b7KAoEio.mjs → validation-DkL5KQqz.mjs} +2 -2
- package/dist/{validation-b7KAoEio.mjs.map → validation-DkL5KQqz.mjs.map} +1 -1
- package/dist/wire-BeIO-d1x.d.mts +35 -0
- package/dist/wire-BeIO-d1x.d.mts.map +1 -0
- package/package.json +3 -3
- package/dist/rate-limit-CKfuhxT1.mjs.map +0 -1
- package/dist/specs-Co6qYp_E.mjs +0 -309
- package/dist/specs-Co6qYp_E.mjs.map +0 -1
- package/dist/types-BZ0959rh.d.mts.map +0 -1
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
import { r as ApiError } from "./rate-limit-D1q2Js-z.mjs";
|
|
2
|
+
import { n as PollAbortedError, t as PollTimeoutError } from "./poll-timeout-Dg_QFEqi.mjs";
|
|
3
|
+
import { t as ValidationError } from "./validation-DkL5KQqz.mjs";
|
|
4
|
+
import { a as IDEMPOTENT_METHOD_DEFAULTS, i as CREATE_METHOD_DEFAULTS, n as okRequest, o as defaultRetryDelay, s as isRecord } from "./resource-client-D7HKNrOp.mjs";
|
|
5
|
+
//#region src/domains/cloud-v2/luau-execution-task-logs/builders.ts
|
|
6
|
+
/**
|
|
7
|
+
* Builds a `GET` request for the Open Cloud "list Luau execution session
|
|
8
|
+
* task logs" endpoint. The endpoint requires the maximal x-aep-resource
|
|
9
|
+
* path shape (universe, place, version, session, task), so the supplied
|
|
10
|
+
* ref must include `versionId` and `sessionId`; refs extracted from the
|
|
11
|
+
* narrower path formats are rejected with a {@link ValidationError}.
|
|
12
|
+
*
|
|
13
|
+
* The `view` query parameter is hard-coded to `STRUCTURED` so callers
|
|
14
|
+
* always receive typed structured messages. No public `view` parameter
|
|
15
|
+
* is exposed.
|
|
16
|
+
*
|
|
17
|
+
* @param parameters - Task ref, and optional `pageSize` and `pageToken`
|
|
18
|
+
* pagination controls.
|
|
19
|
+
* @returns A success result wrapping the request, or a
|
|
20
|
+
* {@link ValidationError} when the ref is missing `versionId` or
|
|
21
|
+
* `sessionId`.
|
|
22
|
+
*/
|
|
23
|
+
function buildListLogsRequest(parameters) {
|
|
24
|
+
const { pageSize, pageToken, ref } = parameters;
|
|
25
|
+
const { placeId, sessionId, taskId, universeId, versionId } = ref;
|
|
26
|
+
if (versionId === void 0) return {
|
|
27
|
+
err: new ValidationError("Task ref is missing versionId; cannot list logs", { code: "incomplete_ref" }),
|
|
28
|
+
success: false
|
|
29
|
+
};
|
|
30
|
+
if (sessionId === void 0) return {
|
|
31
|
+
err: new ValidationError("Task ref is missing sessionId; cannot list logs", { code: "incomplete_ref" }),
|
|
32
|
+
success: false
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
data: {
|
|
36
|
+
method: "GET",
|
|
37
|
+
url: `${`/cloud/v2/universes/${universeId}/places/${placeId}/versions/${versionId}/luau-execution-sessions/${sessionId}/tasks/${taskId}/logs`}?${buildQuery(pageSize, pageToken).toString()}`
|
|
38
|
+
},
|
|
39
|
+
success: true
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function buildQuery(pageSize, pageToken) {
|
|
43
|
+
const query = new URLSearchParams({ view: "STRUCTURED" });
|
|
44
|
+
if (pageSize !== void 0) query.append("maxPageSize", String(pageSize));
|
|
45
|
+
if (pageToken !== void 0) query.append("pageToken", pageToken);
|
|
46
|
+
return query;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Per-second request ceiling for listing Luau execution task logs,
|
|
50
|
+
* sourced from `x-roblox-rate-limits.perApiKeyOwner` on the
|
|
51
|
+
* `Cloud_ListLuauExecutionSessionTaskLogs` operation (45 requests per
|
|
52
|
+
* minute per API key owner).
|
|
53
|
+
*/
|
|
54
|
+
const LIST_LOGS_OPERATION_LIMIT = Object.freeze({
|
|
55
|
+
maxPerSecond: 45 / 60,
|
|
56
|
+
operationKey: "luau-execution-task-logs.list"
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* Scopes required to list Luau execution task logs, sourced from
|
|
60
|
+
* `x-roblox-scopes` on the list-logs operation in the vendored OpenAPI
|
|
61
|
+
* schema. Surfaced via the `requiredScopes` field of the per-method
|
|
62
|
+
* spec so a 401 or 403 ApiError is upgraded to a `PermissionError`
|
|
63
|
+
* naming the missing scope. Only `:read` is required as the
|
|
64
|
+
* minimum-privilege scope for this read-only operation.
|
|
65
|
+
*/
|
|
66
|
+
const LIST_LOGS_REQUIRED_SCOPES = Object.freeze(["universe.place.luau-execution-session:read"]);
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/domains/cloud-v2/luau-execution-task-logs/parsers.ts
|
|
69
|
+
const MALFORMED_LOGS_MESSAGE = "Malformed list-luau-execution-task-logs response";
|
|
70
|
+
/**
|
|
71
|
+
* Parses a successful Open Cloud list-luau-execution-task-logs response
|
|
72
|
+
* body into the public {@link LogPage} shape. Chunks are flattened into
|
|
73
|
+
* a single ordered array of {@link LogMessage} values. The
|
|
74
|
+
* `MESSAGE_TYPE_UNSPECIFIED` sentinel is rejected.
|
|
75
|
+
*
|
|
76
|
+
* @param response - The full {@link HttpResponse} from the Open Cloud API.
|
|
77
|
+
* @returns A success result wrapping the parsed {@link LogPage}, or an
|
|
78
|
+
* {@link ApiError} when the body does not match a supported shape.
|
|
79
|
+
*/
|
|
80
|
+
function parseListLogsResponse(response) {
|
|
81
|
+
const { body, status: statusCode } = response;
|
|
82
|
+
if (!isListLogsResponseWire(body)) return malformed$1(statusCode);
|
|
83
|
+
const chunks = body.luauExecutionSessionTaskLogs ?? [];
|
|
84
|
+
const messages = [];
|
|
85
|
+
for (const chunk of chunks) for (const wireMessage of chunk.structuredMessages ?? []) messages.push({
|
|
86
|
+
createTime: wireMessage.createTime,
|
|
87
|
+
message: wireMessage.message,
|
|
88
|
+
messageType: wireMessage.messageType
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
data: {
|
|
92
|
+
messages,
|
|
93
|
+
nextPageToken: body.nextPageToken
|
|
94
|
+
},
|
|
95
|
+
success: true
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function isAcceptedMessageType(value) {
|
|
99
|
+
return value === "OUTPUT" || value === "INFO" || value === "WARNING" || value === "ERROR";
|
|
100
|
+
}
|
|
101
|
+
function isLogMessageWire(value) {
|
|
102
|
+
return isRecord(value) && typeof value["createTime"] === "string" && typeof value["message"] === "string" && isAcceptedMessageType(value["messageType"]);
|
|
103
|
+
}
|
|
104
|
+
function isOptionalStructuredMessages(value) {
|
|
105
|
+
return value === void 0 || Array.isArray(value) && value.every((item) => isLogMessageWire(item));
|
|
106
|
+
}
|
|
107
|
+
function isLogChunkWire(value) {
|
|
108
|
+
return isRecord(value) && isOptionalStructuredMessages(value["structuredMessages"]);
|
|
109
|
+
}
|
|
110
|
+
function isOptionalLogChunks(value) {
|
|
111
|
+
return value === void 0 || Array.isArray(value) && value.every((item) => isLogChunkWire(item));
|
|
112
|
+
}
|
|
113
|
+
function isListLogsResponseWire(body) {
|
|
114
|
+
return isRecord(body) && isOptionalLogChunks(body["luauExecutionSessionTaskLogs"]) && (body["nextPageToken"] === void 0 || typeof body["nextPageToken"] === "string");
|
|
115
|
+
}
|
|
116
|
+
function malformed$1(statusCode) {
|
|
117
|
+
return {
|
|
118
|
+
err: new ApiError(MALFORMED_LOGS_MESSAGE, { statusCode }),
|
|
119
|
+
success: false
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/domains/cloud-v2/luau-execution-task-logs/specs.ts
|
|
124
|
+
function makeSpec$1(spec) {
|
|
125
|
+
return Object.freeze(spec);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Per-method dispatch spec for listing the structured log messages
|
|
129
|
+
* produced by a Luau execution task. Frozen at module scope so both
|
|
130
|
+
* the top-level `LuauExecutionClient` and the `luauExecution` Operation
|
|
131
|
+
* Group on `PlacesClient` share the same instance reference.
|
|
132
|
+
*/
|
|
133
|
+
const LIST_LOGS_SPEC = makeSpec$1({
|
|
134
|
+
buildRequest: buildListLogsRequest,
|
|
135
|
+
methodDefaults: IDEMPOTENT_METHOD_DEFAULTS,
|
|
136
|
+
methodKind: "idempotent",
|
|
137
|
+
operationLimit: LIST_LOGS_OPERATION_LIMIT,
|
|
138
|
+
parse: parseListLogsResponse,
|
|
139
|
+
requiredScopes: LIST_LOGS_REQUIRED_SCOPES
|
|
140
|
+
});
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/domains/cloud-v2/luau-execution-tasks/builders.ts
|
|
143
|
+
const JSON_HEADERS = { "content-type": "application/json" };
|
|
144
|
+
/**
|
|
145
|
+
* Builds a `POST` request for the Open Cloud "create Luau execution
|
|
146
|
+
* session task" endpoint, targeting the place's head version. Serializes
|
|
147
|
+
* `timeoutSeconds` into the wire's duration string format (`"<n>s"`)
|
|
148
|
+
* when supplied.
|
|
149
|
+
*
|
|
150
|
+
* @param parameters - Universe and place identifiers, the script body,
|
|
151
|
+
* and an optional `timeoutSeconds`.
|
|
152
|
+
* @returns A pure {@link HttpRequest} describing the submit call.
|
|
153
|
+
*/
|
|
154
|
+
function buildSubmitAtHeadRequest(parameters) {
|
|
155
|
+
const { placeId, universeId } = parameters;
|
|
156
|
+
return {
|
|
157
|
+
body: buildSubmitBody(parameters),
|
|
158
|
+
headers: JSON_HEADERS,
|
|
159
|
+
method: "POST",
|
|
160
|
+
url: `/cloud/v2/universes/${universeId}/places/${placeId}/luau-execution-session-tasks`
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Builds a `POST` request for the Open Cloud "create Luau execution
|
|
165
|
+
* session task" endpoint, targeting a specific place version. Differs
|
|
166
|
+
* from {@link buildSubmitAtHeadRequest} only in URL shape: the path
|
|
167
|
+
* includes the `versions/{versionId}` segment so the script runs
|
|
168
|
+
* against that exact place version instead of the live head.
|
|
169
|
+
*
|
|
170
|
+
* @param parameters - Universe, place, and version identifiers, the
|
|
171
|
+
* script body, and an optional `timeoutSeconds`.
|
|
172
|
+
* @returns A pure {@link HttpRequest} describing the submit call.
|
|
173
|
+
*/
|
|
174
|
+
function buildSubmitAtVersionRequest(parameters) {
|
|
175
|
+
const { placeId, universeId, versionId } = parameters;
|
|
176
|
+
return {
|
|
177
|
+
body: buildSubmitBody(parameters),
|
|
178
|
+
headers: JSON_HEADERS,
|
|
179
|
+
method: "POST",
|
|
180
|
+
url: `/cloud/v2/universes/${universeId}/places/${placeId}/versions/${versionId}/luau-execution-session-tasks`
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Builds a `GET` request for the Open Cloud "read Luau execution session
|
|
185
|
+
* task" endpoint. The endpoint accepts only the maximal x-aep-resource
|
|
186
|
+
* path shape (universe, place, version, session, task), so the supplied
|
|
187
|
+
* ref must include `versionId` and `sessionId`; refs extracted from the
|
|
188
|
+
* narrower path formats are rejected with a {@link ValidationError}.
|
|
189
|
+
*
|
|
190
|
+
* @param parameters - Task ref and optional view selector. When `view`
|
|
191
|
+
* is omitted, no `?view=` query is sent and the server applies its
|
|
192
|
+
* own default (`BASIC`).
|
|
193
|
+
* @returns A success result wrapping the request, or a
|
|
194
|
+
* {@link ValidationError} when the ref is missing `versionId` or
|
|
195
|
+
* `sessionId`.
|
|
196
|
+
*/
|
|
197
|
+
function buildGetRequest(parameters) {
|
|
198
|
+
const { ref, view } = parameters;
|
|
199
|
+
const { placeId, sessionId, taskId, universeId, versionId } = ref;
|
|
200
|
+
if (versionId === void 0) return {
|
|
201
|
+
err: new ValidationError("Task ref is missing versionId; cannot GET", { code: "incomplete_ref" }),
|
|
202
|
+
success: false
|
|
203
|
+
};
|
|
204
|
+
if (sessionId === void 0) return {
|
|
205
|
+
err: new ValidationError("Task ref is missing sessionId; cannot GET", { code: "incomplete_ref" }),
|
|
206
|
+
success: false
|
|
207
|
+
};
|
|
208
|
+
const base = `/cloud/v2/universes/${universeId}/places/${placeId}/versions/${versionId}/luau-execution-sessions/${sessionId}/tasks/${taskId}`;
|
|
209
|
+
return {
|
|
210
|
+
data: {
|
|
211
|
+
method: "GET",
|
|
212
|
+
url: view === void 0 ? base : `${base}?view=${view}`
|
|
213
|
+
},
|
|
214
|
+
success: true
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function buildSubmitBody(parameters) {
|
|
218
|
+
const { binaryInput, enableBinaryOutput: shouldEnableBinaryOutput, script, timeoutSeconds } = parameters;
|
|
219
|
+
const body = { script };
|
|
220
|
+
if (timeoutSeconds !== void 0) body["timeout"] = `${timeoutSeconds}s`;
|
|
221
|
+
if (binaryInput !== void 0) body["binaryInput"] = binaryInput;
|
|
222
|
+
if (shouldEnableBinaryOutput !== void 0) body["enableBinaryOutput"] = shouldEnableBinaryOutput;
|
|
223
|
+
return body;
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/domains/cloud-v2/luau-execution-tasks/operations.ts
|
|
227
|
+
const SUBMIT_PER_MINUTE = 40;
|
|
228
|
+
const GET_PER_MINUTE = 200;
|
|
229
|
+
const SECONDS_PER_MINUTE = 60;
|
|
230
|
+
/**
|
|
231
|
+
* Per-second request ceiling for submitting a Luau execution task,
|
|
232
|
+
* sourced from `x-roblox-rate-limits.perApiKeyOwner` on the
|
|
233
|
+
* `Cloud_CreateLuauExecutionSessionTask__Using_Universes` operation
|
|
234
|
+
* (40 requests per minute per API key owner). The two URL shapes
|
|
235
|
+
* (head and version) share this queue because Roblox attributes both
|
|
236
|
+
* to the same per-minute quota.
|
|
237
|
+
*/
|
|
238
|
+
const SUBMIT_OPERATION_LIMIT = Object.freeze({
|
|
239
|
+
maxPerSecond: SUBMIT_PER_MINUTE / SECONDS_PER_MINUTE,
|
|
240
|
+
operationKey: "luau-execution-tasks.submit"
|
|
241
|
+
});
|
|
242
|
+
/**
|
|
243
|
+
* Per-second request ceiling for fetching a Luau execution task,
|
|
244
|
+
* sourced from `x-roblox-rate-limits.perApiKeyOwner` on the
|
|
245
|
+
* `Cloud_GetLuauExecutionSessionTask` operation (200 requests per
|
|
246
|
+
* minute per API key owner).
|
|
247
|
+
*/
|
|
248
|
+
const GET_OPERATION_LIMIT = Object.freeze({
|
|
249
|
+
maxPerSecond: GET_PER_MINUTE / SECONDS_PER_MINUTE,
|
|
250
|
+
operationKey: "luau-execution-tasks.get"
|
|
251
|
+
});
|
|
252
|
+
/**
|
|
253
|
+
* Scopes required to submit a Luau execution task, sourced from
|
|
254
|
+
* `x-roblox-scopes` on the create operation in the vendored OpenAPI
|
|
255
|
+
* schema. Surfaced via the `requiredScopes` field of the per-method
|
|
256
|
+
* spec so a 401 or 403 ApiError is upgraded to a `PermissionError`
|
|
257
|
+
* naming the missing scope.
|
|
258
|
+
*/
|
|
259
|
+
const SUBMIT_REQUIRED_SCOPES = Object.freeze(["universe.place.luau-execution-session:write"]);
|
|
260
|
+
/**
|
|
261
|
+
* Scopes required to fetch a Luau execution task, sourced from
|
|
262
|
+
* `x-roblox-scopes` on the get operation. The `:write` scope also
|
|
263
|
+
* grants read in upstream auth, but we surface only `:read` here as
|
|
264
|
+
* the minimum-privilege requirement for this method.
|
|
265
|
+
*/
|
|
266
|
+
const GET_REQUIRED_SCOPES = Object.freeze(["universe.place.luau-execution-session:read"]);
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region src/domains/cloud-v2/luau-execution-tasks/parsers.ts
|
|
269
|
+
const MALFORMED_TASK_MESSAGE = "Malformed luau-execution-session-task response";
|
|
270
|
+
const PATH_PATTERN = /^universes\/(\d+)\/places\/(\d+)(?:\/versions\/(\d+))?(?:\/luau-execution-sessions\/([^/]+)\/tasks\/([^/]+)|\/luau-execution-session-tasks\/([^/]+))$/;
|
|
271
|
+
/**
|
|
272
|
+
* Parses a successful Open Cloud `LuauExecutionSessionTask` response
|
|
273
|
+
* body into the public {@link LuauExecutionTask} discriminated union.
|
|
274
|
+
* Handles every supported task state (in-progress, COMPLETE, FAILED)
|
|
275
|
+
* across all four x-aep-resource path shapes the server returns.
|
|
276
|
+
*
|
|
277
|
+
* @param response - The full {@link HttpResponse} from the Open Cloud
|
|
278
|
+
* API.
|
|
279
|
+
* @returns A success result wrapping the parsed task, or an
|
|
280
|
+
* {@link ApiError} when the body or path do not match a supported
|
|
281
|
+
* shape.
|
|
282
|
+
*/
|
|
283
|
+
function parseLuauExecutionTaskResponse(response) {
|
|
284
|
+
const { body, status: statusCode } = response;
|
|
285
|
+
if (!isLuauExecutionTaskWire(body)) return malformed(statusCode);
|
|
286
|
+
const ref = parseTaskRef(body.path);
|
|
287
|
+
if (ref === void 0) return malformed(statusCode);
|
|
288
|
+
const timeoutSeconds = parseTimeoutSeconds(body.timeout);
|
|
289
|
+
if (body.state === "COMPLETE") return parseCompleteTask({
|
|
290
|
+
body,
|
|
291
|
+
ref,
|
|
292
|
+
statusCode,
|
|
293
|
+
timeoutSeconds
|
|
294
|
+
});
|
|
295
|
+
if (body.state === "FAILED") return parseFailedTask({
|
|
296
|
+
body,
|
|
297
|
+
ref,
|
|
298
|
+
statusCode,
|
|
299
|
+
timeoutSeconds
|
|
300
|
+
});
|
|
301
|
+
return parseInProgressTask({
|
|
302
|
+
body,
|
|
303
|
+
ref,
|
|
304
|
+
state: body.state,
|
|
305
|
+
statusCode,
|
|
306
|
+
timeoutSeconds
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
function isAcceptedWireState(state) {
|
|
310
|
+
return state === "QUEUED" || state === "PROCESSING" || state === "CANCELLED" || state === "COMPLETE" || state === "FAILED";
|
|
311
|
+
}
|
|
312
|
+
function isErrorWireCode(code) {
|
|
313
|
+
return code === "SCRIPT_ERROR" || code === "DEADLINE_EXCEEDED" || code === "OUTPUT_SIZE_LIMIT_EXCEEDED" || code === "INTERNAL_ERROR";
|
|
314
|
+
}
|
|
315
|
+
function isErrorWire(value) {
|
|
316
|
+
return isRecord(value) && isErrorWireCode(value["code"]) && typeof value["message"] === "string";
|
|
317
|
+
}
|
|
318
|
+
function isOptionalErrorWire(value) {
|
|
319
|
+
return value === void 0 || isErrorWire(value);
|
|
320
|
+
}
|
|
321
|
+
function isOutputWire(value) {
|
|
322
|
+
return isRecord(value) && Array.isArray(value["results"]);
|
|
323
|
+
}
|
|
324
|
+
function isOptionalOutputWire(value) {
|
|
325
|
+
return value === void 0 || isOutputWire(value);
|
|
326
|
+
}
|
|
327
|
+
function isOptionalString(value) {
|
|
328
|
+
return value === void 0 || typeof value === "string";
|
|
329
|
+
}
|
|
330
|
+
function isOptionalBoolean(value) {
|
|
331
|
+
return value === void 0 || typeof value === "boolean";
|
|
332
|
+
}
|
|
333
|
+
function isLuauExecutionTaskWire(body) {
|
|
334
|
+
return isRecord(body) && typeof body["path"] === "string" && typeof body["createTime"] === "string" && typeof body["updateTime"] === "string" && 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"]);
|
|
335
|
+
}
|
|
336
|
+
const DURATION_PATTERN = /^(\d+)s$/;
|
|
337
|
+
function isOptionalDurationWire(value) {
|
|
338
|
+
return value === void 0 || typeof value === "string" && DURATION_PATTERN.test(value);
|
|
339
|
+
}
|
|
340
|
+
function parseTimeoutSeconds(value) {
|
|
341
|
+
if (value === void 0) return;
|
|
342
|
+
const seconds = DURATION_PATTERN.exec(value)?.[1];
|
|
343
|
+
if (seconds === void 0) return;
|
|
344
|
+
return Number.parseInt(seconds, 10);
|
|
345
|
+
}
|
|
346
|
+
function malformed(statusCode) {
|
|
347
|
+
return {
|
|
348
|
+
err: new ApiError(MALFORMED_TASK_MESSAGE, { statusCode }),
|
|
349
|
+
success: false
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function parseInProgressTask(args) {
|
|
353
|
+
const { body, ref, state, timeoutSeconds } = args;
|
|
354
|
+
return {
|
|
355
|
+
data: {
|
|
356
|
+
binaryInput: body.binaryInput,
|
|
357
|
+
binaryOutputUri: body.binaryOutputUri,
|
|
358
|
+
createdAt: new Date(body.createTime),
|
|
359
|
+
enableBinaryOutput: body.enableBinaryOutput,
|
|
360
|
+
ref,
|
|
361
|
+
state,
|
|
362
|
+
timeoutSeconds,
|
|
363
|
+
updatedAt: new Date(body.updateTime),
|
|
364
|
+
user: body.user
|
|
365
|
+
},
|
|
366
|
+
success: true
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function parseCompleteTask(args) {
|
|
370
|
+
const { body, ref, statusCode, timeoutSeconds } = args;
|
|
371
|
+
if (body.output === void 0) return malformed(statusCode);
|
|
372
|
+
return {
|
|
373
|
+
data: {
|
|
374
|
+
binaryInput: body.binaryInput,
|
|
375
|
+
binaryOutputUri: body.binaryOutputUri,
|
|
376
|
+
createdAt: new Date(body.createTime),
|
|
377
|
+
enableBinaryOutput: body.enableBinaryOutput,
|
|
378
|
+
output: { results: body.output.results },
|
|
379
|
+
ref,
|
|
380
|
+
state: "COMPLETE",
|
|
381
|
+
timeoutSeconds,
|
|
382
|
+
updatedAt: new Date(body.updateTime),
|
|
383
|
+
user: body.user
|
|
384
|
+
},
|
|
385
|
+
success: true
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function parseFailedTask(args) {
|
|
389
|
+
const { body, ref, statusCode, timeoutSeconds } = args;
|
|
390
|
+
if (body.error === void 0) return malformed(statusCode);
|
|
391
|
+
return {
|
|
392
|
+
data: {
|
|
393
|
+
binaryInput: body.binaryInput,
|
|
394
|
+
binaryOutputUri: body.binaryOutputUri,
|
|
395
|
+
createdAt: new Date(body.createTime),
|
|
396
|
+
enableBinaryOutput: body.enableBinaryOutput,
|
|
397
|
+
error: {
|
|
398
|
+
code: body.error.code,
|
|
399
|
+
message: body.error.message
|
|
400
|
+
},
|
|
401
|
+
ref,
|
|
402
|
+
state: "FAILED",
|
|
403
|
+
timeoutSeconds,
|
|
404
|
+
updatedAt: new Date(body.updateTime),
|
|
405
|
+
user: body.user
|
|
406
|
+
},
|
|
407
|
+
success: true
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
function parseTaskRef(path) {
|
|
411
|
+
const match = PATH_PATTERN.exec(path);
|
|
412
|
+
if (match === null) return;
|
|
413
|
+
const [, universeId, placeId, versionId, sessionId, sessionTaskId, plainTaskId] = match;
|
|
414
|
+
const taskId = sessionTaskId ?? plainTaskId;
|
|
415
|
+
if (universeId === void 0 || placeId === void 0 || taskId === void 0) return;
|
|
416
|
+
return {
|
|
417
|
+
placeId,
|
|
418
|
+
sessionId,
|
|
419
|
+
taskId,
|
|
420
|
+
universeId,
|
|
421
|
+
versionId
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region src/domains/cloud-v2/luau-execution-tasks/specs.ts
|
|
426
|
+
function makeSpec(spec) {
|
|
427
|
+
return Object.freeze(spec);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Per-method dispatch spec for submitting a Luau execution task at a
|
|
431
|
+
* place's head version. Frozen at module scope so both the top-level
|
|
432
|
+
* `LuauExecutionClient` and the `luauExecution` Operation Group on
|
|
433
|
+
* `PlacesClient` share the same instance reference.
|
|
434
|
+
*/
|
|
435
|
+
const SUBMIT_HEAD_SPEC = makeSpec({
|
|
436
|
+
buildRequest: (parameters) => okRequest(buildSubmitAtHeadRequest(parameters)),
|
|
437
|
+
methodDefaults: CREATE_METHOD_DEFAULTS,
|
|
438
|
+
methodKind: "create",
|
|
439
|
+
operationLimit: SUBMIT_OPERATION_LIMIT,
|
|
440
|
+
parse: parseLuauExecutionTaskResponse,
|
|
441
|
+
requiredScopes: SUBMIT_REQUIRED_SCOPES
|
|
442
|
+
});
|
|
443
|
+
/**
|
|
444
|
+
* Per-method dispatch spec for submitting a Luau execution task at a
|
|
445
|
+
* specific place version. Shares the rate-limit queue and required
|
|
446
|
+
* scope set with {@link SUBMIT_HEAD_SPEC} because Roblox attributes
|
|
447
|
+
* both URL shapes to one per-minute quota.
|
|
448
|
+
*/
|
|
449
|
+
const SUBMIT_VERSION_SPEC = makeSpec({
|
|
450
|
+
buildRequest: (parameters) => okRequest(buildSubmitAtVersionRequest(parameters)),
|
|
451
|
+
methodDefaults: CREATE_METHOD_DEFAULTS,
|
|
452
|
+
methodKind: "create",
|
|
453
|
+
operationLimit: SUBMIT_OPERATION_LIMIT,
|
|
454
|
+
parse: parseLuauExecutionTaskResponse,
|
|
455
|
+
requiredScopes: SUBMIT_REQUIRED_SCOPES
|
|
456
|
+
});
|
|
457
|
+
/**
|
|
458
|
+
* Per-method dispatch spec for fetching a Luau execution task. Uses
|
|
459
|
+
* idempotent retry semantics (429 and 5xx both retried) so reads
|
|
460
|
+
* recover transparently from transient server errors.
|
|
461
|
+
*/
|
|
462
|
+
const GET_SPEC = makeSpec({
|
|
463
|
+
buildRequest: buildGetRequest,
|
|
464
|
+
methodDefaults: IDEMPOTENT_METHOD_DEFAULTS,
|
|
465
|
+
methodKind: "idempotent",
|
|
466
|
+
operationLimit: GET_OPERATION_LIMIT,
|
|
467
|
+
parse: parseLuauExecutionTaskResponse,
|
|
468
|
+
requiredScopes: GET_REQUIRED_SCOPES
|
|
469
|
+
});
|
|
470
|
+
const ABORTED = Symbol("poll-aborted");
|
|
471
|
+
/**
|
|
472
|
+
* Core polling loop. Calls `deps.fetch()` repeatedly, sleeping
|
|
473
|
+
* `pollDelay(attempt)` ms between iterations, until a terminal state
|
|
474
|
+
* is observed, the wall-clock budget is exhausted, or an `AbortSignal`
|
|
475
|
+
* fires. Returns the terminal task on success.
|
|
476
|
+
*
|
|
477
|
+
* @param deps - Injected fetch, now, and sleep callbacks.
|
|
478
|
+
* @param options - Optional poll delay, timeout, and abort signal.
|
|
479
|
+
* @returns The terminal task, or an error if aborted, timed out, or the transport fails.
|
|
480
|
+
*/
|
|
481
|
+
async function pollUntilDoneCore(deps, options = {}) {
|
|
482
|
+
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
483
|
+
const pollDelay = options.pollDelay ?? defaultRetryDelay;
|
|
484
|
+
const sig = options.signal;
|
|
485
|
+
const startedAt = deps.now();
|
|
486
|
+
if (sig?.aborted === true) return abortedResult(sig);
|
|
487
|
+
let lastTask;
|
|
488
|
+
for (let attempt = 0;; attempt += 1) {
|
|
489
|
+
if (deps.now() - startedAt >= timeoutMs) return {
|
|
490
|
+
err: makeTimeout(lastTask, timeoutMs),
|
|
491
|
+
success: false
|
|
492
|
+
};
|
|
493
|
+
const iteration = await pollIteration({
|
|
494
|
+
delayMs: pollDelay(attempt),
|
|
495
|
+
deps,
|
|
496
|
+
signal: sig
|
|
497
|
+
});
|
|
498
|
+
if (iteration.done) return iteration.result;
|
|
499
|
+
lastTask = iteration.task;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function makeAborted(signal) {
|
|
503
|
+
return new PollAbortedError("Polling was aborted", { reason: signal?.reason });
|
|
504
|
+
}
|
|
505
|
+
function abortedResult(signal) {
|
|
506
|
+
return {
|
|
507
|
+
err: makeAborted(signal),
|
|
508
|
+
success: false
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function isTerminal(task) {
|
|
512
|
+
return task.state === "COMPLETE" || task.state === "FAILED" || task.state === "CANCELLED";
|
|
513
|
+
}
|
|
514
|
+
function abortObserver(signal) {
|
|
515
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
516
|
+
function onAbort() {
|
|
517
|
+
resolve(ABORTED);
|
|
518
|
+
}
|
|
519
|
+
signal.addEventListener("abort", onAbort);
|
|
520
|
+
function cleanup() {
|
|
521
|
+
signal.removeEventListener("abort", onAbort);
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
cleanup,
|
|
525
|
+
promise
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
async function raceWithAbort(promise, signal) {
|
|
529
|
+
if (signal === void 0) return promise;
|
|
530
|
+
if (signal.aborted) return ABORTED;
|
|
531
|
+
const observer = abortObserver(signal);
|
|
532
|
+
try {
|
|
533
|
+
return await Promise.race([promise, observer.promise]);
|
|
534
|
+
} finally {
|
|
535
|
+
observer.cleanup();
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
async function sleepWithAbort(options) {
|
|
539
|
+
const { ms, signal, sleep } = options;
|
|
540
|
+
return await raceWithAbort(sleep(ms), signal) === ABORTED;
|
|
541
|
+
}
|
|
542
|
+
async function pollIteration(options) {
|
|
543
|
+
const { delayMs, deps, signal } = options;
|
|
544
|
+
const fetchResult = await raceWithAbort(deps.fetch(), signal);
|
|
545
|
+
if (fetchResult === ABORTED) return {
|
|
546
|
+
done: true,
|
|
547
|
+
result: abortedResult(signal)
|
|
548
|
+
};
|
|
549
|
+
if (!fetchResult.success) return {
|
|
550
|
+
done: true,
|
|
551
|
+
result: fetchResult
|
|
552
|
+
};
|
|
553
|
+
if (isTerminal(fetchResult.data)) return {
|
|
554
|
+
done: true,
|
|
555
|
+
result: {
|
|
556
|
+
data: fetchResult.data,
|
|
557
|
+
success: true
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
if (await sleepWithAbort({
|
|
561
|
+
ms: delayMs,
|
|
562
|
+
signal,
|
|
563
|
+
sleep: deps.sleep
|
|
564
|
+
})) return {
|
|
565
|
+
done: true,
|
|
566
|
+
result: abortedResult(signal)
|
|
567
|
+
};
|
|
568
|
+
return {
|
|
569
|
+
done: false,
|
|
570
|
+
task: fetchResult.data
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function makeTimeout(task, timeoutMs) {
|
|
574
|
+
return new PollTimeoutError(`Polling timed out after ${timeoutMs} ms`, {
|
|
575
|
+
lastObservedTask: task,
|
|
576
|
+
timeoutMs
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
//#endregion
|
|
580
|
+
//#region src/resources/luau-execution/polling-helpers.ts
|
|
581
|
+
/**
|
|
582
|
+
* Builds the {@link PollDeps} bundle used by {@link pollUntilDoneCore},
|
|
583
|
+
* closing over the supplied {@link ResourceClient}, task ref, and
|
|
584
|
+
* per-request options so the core loop stays narrow.
|
|
585
|
+
*
|
|
586
|
+
* @param inner - The {@link ResourceClient} that issues each `tasks.get` call.
|
|
587
|
+
* @param args - The polling options and the task ref to fetch on every iteration.
|
|
588
|
+
* @returns A {@link PollDeps} bundle wiring `fetch`, `now`, and `sleep`.
|
|
589
|
+
*/
|
|
590
|
+
function buildPollDeps(inner, args) {
|
|
591
|
+
return {
|
|
592
|
+
fetch: async () => {
|
|
593
|
+
return inner.execute({
|
|
594
|
+
options: args.options,
|
|
595
|
+
parameters: {
|
|
596
|
+
ref: args.ref,
|
|
597
|
+
view: "BASIC"
|
|
598
|
+
},
|
|
599
|
+
spec: GET_SPEC
|
|
600
|
+
});
|
|
601
|
+
},
|
|
602
|
+
now: Date.now,
|
|
603
|
+
sleep: inner.sleep
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Submits a Luau execution task and polls it to a terminal state.
|
|
608
|
+
* Dispatches to the head-version or specific-version submit spec based on
|
|
609
|
+
* the presence of `versionId`, then delegates to {@link pollUntilDoneCore}.
|
|
610
|
+
*
|
|
611
|
+
* @param inner - The {@link ResourceClient} that issues submit and poll calls.
|
|
612
|
+
* @param args - The polling options and submit parameters.
|
|
613
|
+
* @returns A {@link Result} wrapping the terminal {@link LuauExecutionTask}, or
|
|
614
|
+
* the {@link OpenCloudError} that caused submit or polling to fail.
|
|
615
|
+
*/
|
|
616
|
+
async function submitAndPoll(inner, args) {
|
|
617
|
+
const { options, parameters } = args;
|
|
618
|
+
const submitResult = await ("versionId" in parameters ? inner.execute({
|
|
619
|
+
options,
|
|
620
|
+
parameters,
|
|
621
|
+
spec: SUBMIT_VERSION_SPEC
|
|
622
|
+
}) : inner.execute({
|
|
623
|
+
options,
|
|
624
|
+
parameters,
|
|
625
|
+
spec: SUBMIT_HEAD_SPEC
|
|
626
|
+
}));
|
|
627
|
+
if (!submitResult.success) return submitResult;
|
|
628
|
+
return pollUntilDoneCore(buildPollDeps(inner, {
|
|
629
|
+
options,
|
|
630
|
+
ref: submitResult.data.ref
|
|
631
|
+
}), options);
|
|
632
|
+
}
|
|
633
|
+
//#endregion
|
|
634
|
+
export { SUBMIT_HEAD_SPEC as a, GET_SPEC as i, submitAndPoll as n, SUBMIT_VERSION_SPEC as o, pollUntilDoneCore as r, LIST_LOGS_SPEC as s, buildPollDeps as t };
|
|
635
|
+
|
|
636
|
+
//# sourceMappingURL=polling-helpers-DJqtcrCQ.mjs.map
|