diff --git a/lib/util/github/graphql/cache-strategies/abstract-cache-strategy.ts b/lib/util/github/graphql/cache-strategies/abstract-cache-strategy.ts index ed23730c885e24da3e8d6b96293c4d970169ef88..98ddffcd3f342f24d4a6ac7ee2520b00d6d70428 100644 --- a/lib/util/github/graphql/cache-strategies/abstract-cache-strategy.ts +++ b/lib/util/github/graphql/cache-strategies/abstract-cache-strategy.ts @@ -38,10 +38,9 @@ export abstract class AbstractGithubGraphqlCacheStrategy< protected createdAt = this.now; /** - * This flag helps to indicate whether the cache record - * should be persisted after the current cache access cycle. + * This flag indicates whether there is any new or updated items */ - protected hasUpdatedItems = false; + protected hasNovelty = false; /** * Loading and persisting data is delegated to the concrete strategy. @@ -107,18 +106,18 @@ export abstract class AbstractGithubGraphqlCacheStrategy< let isPaginationDone = false; for (const item of items) { const { version } = item; + const oldItem = cachedItems[version]; // If we reached previously stored item that is stabilized, // we assume the further pagination will not yield any new items. - const oldItem = cachedItems[version]; - if (oldItem) { - if (this.isStabilized(oldItem)) { - isPaginationDone = true; - } - - if (!dequal(oldItem, item)) { - this.hasUpdatedItems = true; - } + if (oldItem && this.isStabilized(oldItem)) { + isPaginationDone = true; + break; + } + + // Check if item is new or updated + if (!oldItem || !dequal(oldItem, item)) { + this.hasNovelty = true; } cachedItems[version] = item; @@ -137,13 +136,19 @@ export abstract class AbstractGithubGraphqlCacheStrategy< const cachedItems = await this.getItems(); const resultItems: Record<string, GithubItem> = {}; + let hasDeletedItems = false; for (const [version, item] of Object.entries(cachedItems)) { if (this.isStabilized(item) || this.reconciledVersions.has(version)) { resultItems[version] = item; + } else { + hasDeletedItems = true; } } - await this.store(resultItems); + if (this.hasNovelty || hasDeletedItems) { + await this.store(resultItems); + } + return Object.values(resultItems); } diff --git a/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts b/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts index 13bd58bceb29bc4958932991621e52f75eb68be8..4dcf3d1ac7927a04d5efbf511aa0b443dc360095 100644 --- a/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts +++ b/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts @@ -68,10 +68,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { expect(res).toEqual([]); expect(isPaginationDone).toBe(false); - expect(memCache.get('github-graphql-cache:foo:bar')).toEqual({ - items: {}, - createdAt: isoTs(now), - }); + expect(memCache.get('github-graphql-cache:foo:bar')).toEqual(cacheRecord); }); it('reconciles old cache record with new items', async () => { @@ -136,7 +133,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { expect(isPaginationDone).toBe(true); }); - it('reconciles entire page', async () => { + it('reconciles only not stabilized items in page', async () => { const oldItems = { '1': { releaseTimestamp: isoTs('2020-01-01 00:00'), version: '1' }, '2': { releaseTimestamp: isoTs('2020-01-01 01:00'), version: '2' }, @@ -164,9 +161,9 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { expect(isPaginationDone).toBe(true); expect(memCache.get('github-graphql-cache:foo:bar')).toMatchObject({ items: { - '1': { releaseTimestamp: isoTs('2022-12-31 10:00') }, - '2': { releaseTimestamp: isoTs('2022-12-31 11:00') }, - '3': { releaseTimestamp: isoTs('2022-12-31 12:00') }, + '1': { releaseTimestamp: isoTs('2020-01-01 00:00') }, + '2': { releaseTimestamp: isoTs('2020-01-01 01:00') }, + '3': { releaseTimestamp: isoTs('2020-01-01 02:00') }, '4': { releaseTimestamp: isoTs('2022-12-31 13:00') }, }, }); diff --git a/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts b/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts index 80c02add16a5b3ce8780bba829c4a7d45c3bb182..b07e7cc762a475d200aecfcaa4385031a17e19a7 100644 --- a/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts +++ b/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts @@ -36,21 +36,17 @@ describe('util/github/graphql/cache-strategies/package-cache-strategy', () => { const now = '2022-10-30 12:00'; mockTime(now); - const updatedItem = { - ...item3, - releaseTimestamp: isoTs('2020-01-01 12:30'), - }; const newItem = { version: '4', releaseTimestamp: isoTs('2022-10-15 18:00'), }; - const page = [newItem, updatedItem]; + const page = [newItem, item3, item2, item1]; const strategy = new GithubGraphqlPackageCacheStrategy('foo', 'bar'); const isPaginationDone = await strategy.reconcile(page); const res = await strategy.finalize(); - expect(res).toEqual([item1, item2, updatedItem, newItem]); + expect(res).toEqual([item1, item2, item3, newItem]); expect(isPaginationDone).toBe(true); expect(cacheSet.mock.calls).toEqual([ [ @@ -60,7 +56,7 @@ describe('util/github/graphql/cache-strategies/package-cache-strategy', () => { items: { '1': item1, '2': item2, - '3': updatedItem, + '3': item3, '4': newItem, }, createdAt: isoTs('2022-10-15 12:00'), diff --git a/lib/util/github/graphql/cache-strategies/package-cache-strategy.ts b/lib/util/github/graphql/cache-strategies/package-cache-strategy.ts index 8e9f6f25e97439e7e5c23388cad769cb6799022a..f503207ca0118701fcffc833a01d1c8bba3a997b 100644 --- a/lib/util/github/graphql/cache-strategies/package-cache-strategy.ts +++ b/lib/util/github/graphql/cache-strategies/package-cache-strategy.ts @@ -15,23 +15,21 @@ export class GithubGraphqlPackageCacheStrategy< async persist( cacheRecord: GithubGraphqlCacheRecord<GithubItem> ): Promise<void> { - if (this.hasUpdatedItems) { - const expiry = this.createdAt - .plus({ - // Not using 'days' as it does not handle adjustments for Daylight Saving time. - // The offset in the resulting DateTime object does not match that of the expiry or this.now. - hours: AbstractGithubGraphqlCacheStrategy.cacheTTLDays * 24, - }) - .toUTC(); - const ttlMinutes = expiry.diff(this.now, ['minutes']).as('minutes'); - if (ttlMinutes && ttlMinutes > 0) { - await packageCache.set( - this.cacheNs, - this.cacheKey, - cacheRecord, - ttlMinutes - ); - } + const expiry = this.createdAt + .plus({ + // Not using 'days' as it does not handle adjustments for Daylight Saving time. + // The offset in the resulting DateTime object does not match that of the expiry or this.now. + hours: AbstractGithubGraphqlCacheStrategy.cacheTTLDays * 24, + }) + .toUTC(); + const ttlMinutes = expiry.diff(this.now, ['minutes']).as('minutes'); + if (ttlMinutes && ttlMinutes > 0) { + await packageCache.set( + this.cacheNs, + this.cacheKey, + cacheRecord, + ttlMinutes + ); } } }