@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.
- package/README.md +129 -0
- package/dist/badges.d.mts +33 -2
- package/dist/badges.d.mts.map +1 -1
- package/dist/badges.mjs +12 -4
- package/dist/badges.mjs.map +1 -1
- package/dist/{data.generated-BtkDGH8C.d.mts → data.generated-B2ceLfSn.d.mts} +9 -1
- package/dist/data.generated-B2ceLfSn.d.mts.map +1 -0
- package/dist/developer-products.d.mts +38 -2
- package/dist/developer-products.d.mts.map +1 -1
- package/dist/developer-products.mjs +22 -5
- package/dist/developer-products.mjs.map +1 -1
- package/dist/game-passes.d.mts +40 -2
- package/dist/game-passes.d.mts.map +1 -1
- package/dist/game-passes.mjs +22 -5
- package/dist/game-passes.mjs.map +1 -1
- package/dist/index.d.mts +110 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -3
- package/dist/locales.d.mts +1 -1
- package/dist/locales.mjs +2 -0
- package/dist/locales.mjs.map +1 -1
- package/dist/luau-execution.d.mts +112 -6
- package/dist/luau-execution.d.mts.map +1 -1
- package/dist/luau-execution.mjs +129 -4
- package/dist/luau-execution.mjs.map +1 -1
- package/dist/places.d.mts +64 -6
- package/dist/places.d.mts.map +1 -1
- package/dist/places.mjs +33 -61
- package/dist/places.mjs.map +1 -1
- package/dist/poll-timeout-C0nmJzOd.mjs +83 -0
- package/dist/poll-timeout-C0nmJzOd.mjs.map +1 -0
- package/dist/polling-helpers-Cb4j4cq7.mjs +787 -0
- package/dist/polling-helpers-Cb4j4cq7.mjs.map +1 -0
- package/dist/polling-rj0dGREg.d.mts +312 -0
- package/dist/polling-rj0dGREg.d.mts.map +1 -0
- package/dist/{price-information-s7DY0GV2.mjs → price-information-Bu87sPyh.mjs} +2 -2
- package/dist/{price-information-s7DY0GV2.mjs.map → price-information-Bu87sPyh.mjs.map} +1 -1
- package/dist/{rate-limit-CKfuhxT1.mjs → rate-limit-Co9i28qi.mjs} +24 -38
- package/dist/rate-limit-Co9i28qi.mjs.map +1 -0
- package/dist/{resource-client-Wi4Mwqy5.mjs → resource-client-lE7Tg3BK.mjs} +349 -241
- package/dist/resource-client-lE7Tg3BK.mjs.map +1 -0
- package/dist/retry-CbHBw60o.mjs +380 -0
- package/dist/retry-CbHBw60o.mjs.map +1 -0
- package/dist/retry-r1TXe5Zd.d.mts +189 -0
- package/dist/retry-r1TXe5Zd.d.mts.map +1 -0
- package/dist/signatures-JF-7Psce.mjs +59 -0
- package/dist/signatures-JF-7Psce.mjs.map +1 -0
- package/dist/storage.d.mts +322 -31
- package/dist/storage.d.mts.map +1 -1
- package/dist/storage.mjs +506 -18
- package/dist/storage.mjs.map +1 -1
- package/dist/testing.d.mts +727 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +457 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/{to-blob-1BtHsDGK.mjs → to-blob-RPqMBuft.mjs} +1 -1
- package/dist/{to-blob-1BtHsDGK.mjs.map → to-blob-RPqMBuft.mjs.map} +1 -1
- package/dist/{types-Cp8w8uwA.d.mts → types-CRiJg5EQ.d.mts} +46 -4
- package/dist/types-CRiJg5EQ.d.mts.map +1 -0
- package/dist/universes.d.mts +47 -10
- package/dist/universes.d.mts.map +1 -1
- package/dist/universes.mjs +7 -5
- package/dist/universes.mjs.map +1 -1
- package/dist/{validation-b7KAoEio.mjs → validation-9oU6qNNQ.mjs} +4 -2
- package/dist/validation-9oU6qNNQ.mjs.map +1 -0
- package/dist/wire-D3K-a-UP.d.mts +37 -0
- package/dist/wire-D3K-a-UP.d.mts.map +1 -0
- package/package.json +9 -7
- package/dist/data.generated-BtkDGH8C.d.mts.map +0 -1
- package/dist/is-date-time-string-Cuf1TaSC.mjs +0 -19
- package/dist/is-date-time-string-Cuf1TaSC.mjs.map +0 -1
- package/dist/rate-limit-CKfuhxT1.mjs.map +0 -1
- package/dist/rate-limit-DzHBFwps.d.mts +0 -92
- package/dist/rate-limit-DzHBFwps.d.mts.map +0 -1
- package/dist/resource-client-Wi4Mwqy5.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 +0 -149
- package/dist/types-BZ0959rh.d.mts.map +0 -1
- package/dist/types-Cp8w8uwA.d.mts.map +0 -1
- 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
|