apify-client 3.0.0-beta.54 → 3.0.0-beta.56
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/generated/api.d.ts
CHANGED
|
@@ -10298,7 +10298,8 @@ export interface operations {
|
|
|
10298
10298
|
version: string;
|
|
10299
10299
|
/**
|
|
10300
10300
|
* @description If `true` or `1`, the system will use a cache to speed up the build
|
|
10301
|
-
* process.
|
|
10301
|
+
* process. Pass `false` or `0` to build without the cache. By default,
|
|
10302
|
+
* cache is used.
|
|
10302
10303
|
*/
|
|
10303
10304
|
useCache?: boolean;
|
|
10304
10305
|
/**
|
|
@@ -81,7 +81,8 @@ export declare class StreamedLog {
|
|
|
81
81
|
*/
|
|
82
82
|
start(): void;
|
|
83
83
|
/**
|
|
84
|
-
* Stop log redirection. Waits up to one second for lines already in flight, then aborts the log stream request.
|
|
84
|
+
* Stop log redirection. Waits up to one second for lines already in flight, then aborts the log stream request. If
|
|
85
|
+
* no stream has delivered anything yet, reads the whole log in one request instead.
|
|
85
86
|
*/
|
|
86
87
|
stop(): Promise<void>;
|
|
87
88
|
}
|
|
@@ -3,7 +3,15 @@ import { z } from 'zod';
|
|
|
3
3
|
import log, { Logger, LogLevel } from '@apify/log';
|
|
4
4
|
import { ResourceClient } from '../base/resource_client.js';
|
|
5
5
|
import { timeoutOptionsShape } from '../timeouts.js';
|
|
6
|
-
import { cast, catchNotFoundForResourceOrThrow, concatBytes, parseArgument } from '../utils.js';
|
|
6
|
+
import { cast, catchNotFoundForResourceOrThrow, concatBytes, parseArgument, sleep } from '../utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Pause before the first reopen of a log stream that ended before the run logged anything. Each further reopen doubles
|
|
9
|
+
* the pause up to `EMPTY_LOG_STREAM_MAX_RETRY_MILLIS`, which bounds the request rate while a run waits long to start,
|
|
10
|
+
* for example for free memory.
|
|
11
|
+
*/
|
|
12
|
+
const EMPTY_LOG_STREAM_RETRY_MILLIS = 500;
|
|
13
|
+
/** Upper bound on the pause between reopens of an empty log stream. */
|
|
14
|
+
const EMPTY_LOG_STREAM_MAX_RETRY_MILLIS = 5_000;
|
|
7
15
|
const logOptionsSchema = z.strictObject({ raw: z.boolean().optional(), ...timeoutOptionsShape });
|
|
8
16
|
/** How long `StreamedLog.stop()` waits for lines already in flight before aborting the log stream request. */
|
|
9
17
|
const STOP_GRACE_MILLIS = 1000;
|
|
@@ -144,6 +152,7 @@ export class StreamedLog {
|
|
|
144
152
|
#streamingTask = null;
|
|
145
153
|
#stopLogging = false;
|
|
146
154
|
#stopController = new AbortController();
|
|
155
|
+
#wakeController = new AbortController();
|
|
147
156
|
constructor(options) {
|
|
148
157
|
const { toLog, logClient, fromStart = true, signal } = options;
|
|
149
158
|
this.#destinationLog = toLog;
|
|
@@ -160,16 +169,19 @@ export class StreamedLog {
|
|
|
160
169
|
}
|
|
161
170
|
this.#stopLogging = false;
|
|
162
171
|
this.#stopController = new AbortController();
|
|
172
|
+
this.#wakeController = new AbortController();
|
|
163
173
|
this.#streamingTask = this.#streamLog();
|
|
164
174
|
}
|
|
165
175
|
/**
|
|
166
|
-
* Stop log redirection. Waits up to one second for lines already in flight, then aborts the log stream request.
|
|
176
|
+
* Stop log redirection. Waits up to one second for lines already in flight, then aborts the log stream request. If
|
|
177
|
+
* no stream has delivered anything yet, reads the whole log in one request instead.
|
|
167
178
|
*/
|
|
168
179
|
async stop() {
|
|
169
180
|
if (!this.#streamingTask) {
|
|
170
181
|
throw new Error('Streaming task is not active');
|
|
171
182
|
}
|
|
172
183
|
this.#stopLogging = true;
|
|
184
|
+
this.#wakeController.abort();
|
|
173
185
|
const stopController = this.#stopController;
|
|
174
186
|
const abortTimeout = setTimeout(() => stopController.abort(), STOP_GRACE_MILLIS);
|
|
175
187
|
try {
|
|
@@ -193,11 +205,41 @@ export class StreamedLog {
|
|
|
193
205
|
? AbortSignal.any([this.#signal, this.#stopController.signal])
|
|
194
206
|
: this.#stopController.signal;
|
|
195
207
|
try {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
208
|
+
let lastChunkRemainder;
|
|
209
|
+
let retryMillis = EMPTY_LOG_STREAM_RETRY_MILLIS;
|
|
210
|
+
// The API serves the log of a run that has not logged anything yet as an empty stream that ends at once,
|
|
211
|
+
// so reopen it until the first bytes arrive. If stopped before any bytes arrive, fetch it in one request.
|
|
212
|
+
while (!lastChunkRemainder) {
|
|
213
|
+
if (this.#stopLogging) {
|
|
214
|
+
// `signal` is aborted once the stop grace period ends, which would cut this read short.
|
|
215
|
+
const logContent = await this.#logClient.get({ raw: true, signal: this.#signal });
|
|
216
|
+
lastChunkRemainder = await this.#logStreamChunks([new TextEncoder().encode(logContent ?? '')], signal);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
let logStream;
|
|
220
|
+
try {
|
|
221
|
+
logStream = await this.#logClient.stream({ raw: true, signal });
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
// The stop grace period ended while the stream was connecting, so read the log in one request.
|
|
225
|
+
if (this.#stopLogging && !this.#signal?.aborted)
|
|
226
|
+
continue;
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
if (!logStream) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// A stream opened during stop() would be cut after its first chunk, so read the log in one request.
|
|
233
|
+
if (this.#stopLogging) {
|
|
234
|
+
logStream.destroy();
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
lastChunkRemainder = await this.#logStreamChunks(logStream, signal);
|
|
238
|
+
if (!lastChunkRemainder) {
|
|
239
|
+
await sleep(retryMillis, AbortSignal.any([signal, this.#wakeController.signal]));
|
|
240
|
+
retryMillis = Math.min(retryMillis * 2, EMPTY_LOG_STREAM_MAX_RETRY_MILLIS);
|
|
241
|
+
}
|
|
199
242
|
}
|
|
200
|
-
const lastChunkRemainder = await this.#logStreamChunks(logStream, signal);
|
|
201
243
|
// Process whatever is left when exiting. Maybe it is incomplete, maybe it is last log without EOL.
|
|
202
244
|
const lastMessage = this.#decoder.decode(lastChunkRemainder).trim();
|
|
203
245
|
if (lastMessage.length) {
|
|
@@ -210,13 +252,18 @@ export class StreamedLog {
|
|
|
210
252
|
log.warning(`Log redirection stopped due to error`, err);
|
|
211
253
|
}
|
|
212
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Redirect every complete message in the chunks and return the incomplete rest, or `undefined` when there were no
|
|
257
|
+
* chunks at all.
|
|
258
|
+
*/
|
|
213
259
|
async #logStreamChunks(logStream, signal) {
|
|
214
260
|
// Chunk may be incomplete. Keep remainder for next chunk.
|
|
215
|
-
let previousChunkRemainder
|
|
261
|
+
let previousChunkRemainder;
|
|
216
262
|
try {
|
|
217
263
|
for await (const chunk of logStream) {
|
|
218
264
|
// Handle possible leftover incomplete line from previous chunk.
|
|
219
265
|
// Everything before last end of line is complete.
|
|
266
|
+
previousChunkRemainder ??= new Uint8Array();
|
|
220
267
|
const chunkWithPreviousRemainder = new Uint8Array(previousChunkRemainder.length + chunk.length);
|
|
221
268
|
chunkWithPreviousRemainder.set(previousChunkRemainder, 0);
|
|
222
269
|
chunkWithPreviousRemainder.set(chunk, previousChunkRemainder.length);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apify-client",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.56",
|
|
4
4
|
"description": "Apify API client for JavaScript",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.13.0"
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
],
|
|
60
60
|
"apify": {
|
|
61
61
|
"openapiSpec": {
|
|
62
|
-
"version": "v2-2026-10-
|
|
62
|
+
"version": "v2-2026-10-08T073338Z"
|
|
63
63
|
}
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|