apify-client 3.0.0-beta.50 → 3.0.0-beta.51

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.
@@ -81,7 +81,7 @@ export declare class StreamedLog {
81
81
  */
82
82
  start(): void;
83
83
  /**
84
- * Stop log redirection.
84
+ * Stop log redirection. Waits up to one second for lines already in flight, then aborts the log stream request.
85
85
  */
86
86
  stop(): Promise<void>;
87
87
  }
@@ -5,6 +5,8 @@ import { ResourceClient } from '../base/resource_client.js';
5
5
  import { timeoutOptionsShape } from '../timeouts.js';
6
6
  import { cast, catchNotFoundForResourceOrThrow, concatBytes, parseArgument } from '../utils.js';
7
7
  const logOptionsSchema = z.strictObject({ raw: z.boolean().optional(), ...timeoutOptionsShape });
8
+ /** How long `StreamedLog.stop()` waits for lines already in flight before aborting the log stream request. */
9
+ const STOP_GRACE_MILLIS = 1000;
8
10
  /**
9
11
  * Client for accessing Actor run or build logs.
10
12
  *
@@ -141,6 +143,7 @@ export class StreamedLog {
141
143
  #signal;
142
144
  #streamingTask = null;
143
145
  #stopLogging = false;
146
+ #stopController = new AbortController();
144
147
  constructor(options) {
145
148
  const { toLog, logClient, fromStart = true, signal } = options;
146
149
  this.#destinationLog = toLog;
@@ -156,16 +159,19 @@ export class StreamedLog {
156
159
  throw new Error('Streaming task already active');
157
160
  }
158
161
  this.#stopLogging = false;
162
+ this.#stopController = new AbortController();
159
163
  this.#streamingTask = this.#streamLog();
160
164
  }
161
165
  /**
162
- * Stop log redirection.
166
+ * Stop log redirection. Waits up to one second for lines already in flight, then aborts the log stream request.
163
167
  */
164
168
  async stop() {
165
169
  if (!this.#streamingTask) {
166
170
  throw new Error('Streaming task is not active');
167
171
  }
168
172
  this.#stopLogging = true;
173
+ const stopController = this.#stopController;
174
+ const abortTimeout = setTimeout(() => stopController.abort(), STOP_GRACE_MILLIS);
169
175
  try {
170
176
  await this.#streamingTask;
171
177
  }
@@ -175,6 +181,7 @@ export class StreamedLog {
175
181
  }
176
182
  }
177
183
  finally {
184
+ clearTimeout(abortTimeout);
178
185
  this.#streamingTask = null;
179
186
  }
180
187
  }
@@ -182,12 +189,15 @@ export class StreamedLog {
182
189
  * Get log stream from response and redirect it to another log.
183
190
  */
184
191
  async #streamLog() {
192
+ const signal = this.#signal
193
+ ? AbortSignal.any([this.#signal, this.#stopController.signal])
194
+ : this.#stopController.signal;
185
195
  try {
186
- const logStream = await this.#logClient.stream({ raw: true, signal: this.#signal });
196
+ const logStream = await this.#logClient.stream({ raw: true, signal });
187
197
  if (!logStream) {
188
198
  return;
189
199
  }
190
- const lastChunkRemainder = await this.#logStreamChunks(logStream);
200
+ const lastChunkRemainder = await this.#logStreamChunks(logStream, signal);
191
201
  // Process whatever is left when exiting. Maybe it is incomplete, maybe it is last log without EOL.
192
202
  const lastMessage = this.#decoder.decode(lastChunkRemainder).trim();
193
203
  if (lastMessage.length) {
@@ -195,30 +205,37 @@ export class StreamedLog {
195
205
  }
196
206
  }
197
207
  catch (err) {
198
- if (this.#signal?.aborted)
208
+ if (signal.aborted)
199
209
  return;
200
210
  log.warning(`Log redirection stopped due to error`, err);
201
211
  }
202
212
  }
203
- async #logStreamChunks(logStream) {
213
+ async #logStreamChunks(logStream, signal) {
204
214
  // Chunk may be incomplete. Keep remainder for next chunk.
205
215
  let previousChunkRemainder = new Uint8Array();
206
- for await (const chunk of logStream) {
207
- // Handle possible leftover incomplete line from previous chunk.
208
- // Everything before last end of line is complete.
209
- const chunkWithPreviousRemainder = new Uint8Array(previousChunkRemainder.length + chunk.length);
210
- chunkWithPreviousRemainder.set(previousChunkRemainder, 0);
211
- chunkWithPreviousRemainder.set(chunk, previousChunkRemainder.length);
212
- const lastCompleteMessageIndex = chunkWithPreviousRemainder.lastIndexOf(0x0a);
213
- previousChunkRemainder = chunkWithPreviousRemainder.slice(lastCompleteMessageIndex);
214
- // Push complete part of the chunk to the buffer
215
- this.#streamBuffer.push(chunkWithPreviousRemainder.slice(0, lastCompleteMessageIndex));
216
- this.#logBufferContent();
217
- // Keep processing the new data until stopped
218
- if (this.#stopLogging) {
219
- break;
216
+ try {
217
+ for await (const chunk of logStream) {
218
+ // Handle possible leftover incomplete line from previous chunk.
219
+ // Everything before last end of line is complete.
220
+ const chunkWithPreviousRemainder = new Uint8Array(previousChunkRemainder.length + chunk.length);
221
+ chunkWithPreviousRemainder.set(previousChunkRemainder, 0);
222
+ chunkWithPreviousRemainder.set(chunk, previousChunkRemainder.length);
223
+ const lastCompleteMessageIndex = chunkWithPreviousRemainder.lastIndexOf(0x0a);
224
+ previousChunkRemainder = chunkWithPreviousRemainder.slice(lastCompleteMessageIndex);
225
+ // Push complete part of the chunk to the buffer
226
+ this.#streamBuffer.push(chunkWithPreviousRemainder.slice(0, lastCompleteMessageIndex));
227
+ this.#logBufferContent();
228
+ // Keep processing the new data until stopped
229
+ if (this.#stopLogging) {
230
+ break;
231
+ }
220
232
  }
221
233
  }
234
+ catch (err) {
235
+ // An aborted stream still hands back its unterminated last line, so the caller can flush it.
236
+ if (!signal.aborted)
237
+ throw err;
238
+ }
222
239
  return previousChunkRemainder;
223
240
  }
224
241
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apify-client",
3
- "version": "3.0.0-beta.50",
3
+ "version": "3.0.0-beta.51",
4
4
  "description": "Apify API client for JavaScript",
5
5
  "engines": {
6
6
  "node": ">=22.13.0"