@@ -52,6 +52,36 @@ interface RcsOperation {
5252 numberOfLines : number ;
5353}
5454
55+ /**
56+ * Contains filter with updated or original content and a promise that resolves
57+ * if next patch is available, containing the next patch task and the updated
58+ * filter content.
59+ */
60+ type AppliedPatchResult = {
61+ /**
62+ * The updated filter content after applying the patch, or just the original
63+ * filter content if the patch was not applied.
64+ */
65+ filterContent : string ;
66+
67+ /**
68+ * A promise that resolves via applied patch.
69+ * If the patch is not available, it resolves to null.
70+ */
71+ nextPatchTask ?: Promise < AppliedPatchResult | null > ;
72+ } ;
73+
74+ /**
75+ * Extends the ApplyPatchParams interface to include a flag indicating whether
76+ * the patch is being applied recursively to hide it from the user.
77+ */
78+ type ApplyPatchParamsWithRecursiveFlag = ApplyPatchParams & {
79+ /**
80+ * Indicates whether the patch is being applied recursively.
81+ */
82+ isRecursiveUpdate : boolean ,
83+ } ;
84+
5585/**
5686 * If the differential update is not available the server may signal about that
5787 * by returning one of the following responses.
@@ -218,7 +248,8 @@ const checkPatchExpired = (diffPath: string): boolean => {
218248 * If `isFileHostedViaNetworkProtocol` is `false`, only 2xx status codes are
219249 * accepted, indicating a successful local or similar file request.
220250 * Any other status codes result in an error.
221- * @param isRecursiveUpdate Indicates whether the function is called recursively.
251+ * @param isRecursiveUpdate Indicates whether the it is an applying multiple
252+ * patches in a row.
222253 * @param log A function that logs a message.
223254 *
224255 * @returns A promise that resolves to the content of the downloaded file
@@ -258,9 +289,12 @@ const downloadFile = async (
258289 }
259290 }
260291
261- if ( ( response . status === AcceptableHttpStatusCodes . NotFound
262- || response . status === AcceptableHttpStatusCodes . NoContent ) && ! isRecursiveUpdate ) {
263- log ( 'Update is not available.' ) ;
292+ if ( response . status === AcceptableHttpStatusCodes . NotFound
293+ || response . status === AcceptableHttpStatusCodes . NoContent
294+ ) {
295+ if ( ! isRecursiveUpdate ) {
296+ log ( 'Update is not available.' ) ;
297+ }
264298 return null ;
265299 }
266300
@@ -319,22 +353,25 @@ export const extractBaseUrl = (filterUrl: string): string => {
319353 * @param params The parameters for applying the patch {@link ApplyPatchParams}.
320354 *
321355 * @returns A promise that resolves to the updated filter content after applying the patch,
322- * or null if there is no Diff-Path tag in the filter.
356+ * or null only if there is no Diff-Path tag in the filter - this is a special
357+ * case to prevent future unnecessary patch requests.
323358 *
324359 * @throws
325360 * 1. An {@link Error} if there is an error during
326361 * - the patch application process
327- * - during network request.
362+ * - or during the network request.
328363 * 2. The {@link UnacceptableResponseError} if the network request returns an unacceptable status code.
329364 */
330365export const applyPatch = async ( params : ApplyPatchParams ) : Promise < string | null > => {
331- // Wrapper to hide the callStack parameter from the user.
332- const applyPatchWrapper = async ( innerParams : ApplyPatchParams & { callStack : number } ) : Promise < string | null > => {
366+ // Wrapper to hide the `isRecursiveUpdate` parameter from the user.
367+ const applyPatchWrapper = async (
368+ innerParams : ApplyPatchParamsWithRecursiveFlag ,
369+ ) : Promise < AppliedPatchResult | null > => {
333370 const {
334371 filterUrl,
335372 filterContent,
336373 verbose = false ,
337- callStack ,
374+ isRecursiveUpdate ,
338375 } = innerParams ;
339376
340377 const filterLines = splitByLines ( filterContent ) ;
@@ -348,7 +385,7 @@ export const applyPatch = async (params: ApplyPatchParams): Promise<string | nul
348385
349386 // If the patch has not expired yet, return the filter content without changes.
350387 if ( ! checkPatchExpired ( diffPath ) ) {
351- return filterContent ;
388+ return { filterContent } ;
352389 }
353390
354391 const log = createLogger ( verbose ) ;
@@ -360,13 +397,13 @@ export const applyPatch = async (params: ApplyPatchParams): Promise<string | nul
360397 baseUrl ,
361398 diffPath ,
362399 baseUrl . startsWith ( 'http://' ) || baseUrl . startsWith ( 'https://' ) ,
363- callStack > 0 ,
400+ isRecursiveUpdate ,
364401 log ,
365402 ) ;
366403
367404 // Update is not available yet.
368405 if ( res === null ) {
369- return filterContent ;
406+ return { filterContent } ;
370407 }
371408
372409 patch = res ;
@@ -394,26 +431,48 @@ export const applyPatch = async (params: ApplyPatchParams): Promise<string | nul
394431 }
395432
396433 try {
397- const recursiveUpdatedFilter = await applyPatchWrapper ( {
434+ const nextPatchTask = applyPatchWrapper ( {
398435 filterUrl,
399436 filterContent : updatedFilter ,
400- callStack : callStack + 1 ,
437+ isRecursiveUpdate : true ,
401438 verbose,
402439 } ) ;
403440
404- // It can be null if the filter dropped support for Diff-Path in new versions.
405- if ( recursiveUpdatedFilter === null ) {
406- // Then we return the filter with the last successfully applied patch.
407- return updatedFilter ;
408- }
409-
410- return recursiveUpdatedFilter ;
441+ return { filterContent : updatedFilter , nextPatchTask } ;
411442 } catch ( e ) {
412443 // If we catch an error during the recursive update, we will return
413444 // the last successfully applied patch.
414- return updatedFilter ;
445+ return { filterContent : updatedFilter } ;
415446 }
416447 } ;
417448
418- return applyPatchWrapper ( Object . assign ( params , { callStack : 0 } ) ) ;
449+ const paramsWithRecursiveFlag = { ...params , isRecursiveUpdate : false } ;
450+
451+ let applyingPatchTask : Promise < AppliedPatchResult | null > | null = applyPatchWrapper ( paramsWithRecursiveFlag ) ;
452+ let latestFilter : string | null = null ;
453+
454+ // Apply patches until there are no more new patches to apply.
455+ // This allows to apply multiple patches in a row if the filter supports it
456+ // without recursive calls, since applying patches can be a memory-intensive
457+ // operation, because of large amount of contexts for each function call.
458+ while ( applyingPatchTask ) {
459+ let freshFilter : AppliedPatchResult | null = null ;
460+
461+ // Without try-await since we should throw an error in some cases and
462+ // all needed catches are inside the `applyPatchWrapper` function.
463+ // eslint-disable-next-line no-await-in-loop
464+ freshFilter = await applyingPatchTask ;
465+
466+ // If there is no fresh filter, it means that the patch was not applied
467+ // or the filter does not support Diff-Path tag anymore, so we return
468+ // the latest filter content.
469+ if ( ! freshFilter ) {
470+ return latestFilter ;
471+ }
472+
473+ latestFilter = freshFilter . filterContent ;
474+ applyingPatchTask = freshFilter . nextPatchTask || null ;
475+ }
476+
477+ return latestFilter ;
419478} ;
0 commit comments