Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@internetarchive/collection-name-cache

Package Overview
Dependencies
Maintainers
15
Versions
96
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@internetarchive/collection-name-cache - npm Package Compare versions

Comparing version 0.0.1-alpha.5 to 0.0.1-alpha.6

dist/test/mocks/mock-local-cache.d.ts

3

dist/src/collection-name-cache.d.ts

@@ -41,5 +41,7 @@ import { SearchServiceInterface } from '@internetarchive/search-service';

private pruningAge;
private maxCacheSize;
private cacheLoaded;
constructor(options: {
searchService: SearchServiceInterface;
maxCacheSize?: number;
localCache?: LocalCacheInterface;

@@ -54,2 +56,3 @@ loadDelay?: number;

pruneCache(): Promise<void>;
private persistCache;
}

49

dist/src/collection-name-cache.js

@@ -6,3 +6,3 @@ /* eslint-disable camelcase */

constructor(options) {
var _a, _b, _c;
var _a, _b, _c, _d;
this.cacheKeyName = 'collection-name-cache';

@@ -15,3 +15,3 @@ this.cacheTtl = 60 * 60 * 24 * 7;

this.defaultPruningAge = 1000 * 60 * 60 * 24 * 7;
this.defaultPruningInterval = 1000 * 10;
this.defaultPruningInterval = 1000 * 30;
this.fetchTimeout = null;

@@ -22,2 +22,3 @@ this.pendingIdentifierQueue = new Set();

this.pruningAge = this.defaultPruningAge;
this.maxCacheSize = 2500;
this.cacheLoaded = false;

@@ -28,6 +29,7 @@ this.searchService = options.searchService;

this.pruningAge = (_b = options.pruningAge) !== null && _b !== void 0 ? _b : this.pruningAge;
this.maxCacheSize = (_c = options.maxCacheSize) !== null && _c !== void 0 ? _c : this.maxCacheSize;
this.pruneCache();
setInterval(async () => {
await this.loadFromCache();
await this.pruneCache();
}, (_c = options.pruneInterval) !== null && _c !== void 0 ? _c : this.defaultPruningInterval);
}, (_d = options.pruneInterval) !== null && _d !== void 0 ? _d : this.defaultPruningInterval);
}

@@ -87,3 +89,3 @@ /** @inheritdoc */

async loadPendingIdentifiers() {
var _a, _b, _c, _d;
var _a, _b, _c;
await this.loadFromCache();

@@ -139,13 +141,17 @@ const pendingIdentifiers = Array.from(this.pendingIdentifierQueue).splice(0, 100);

}
await ((_d = this.localCache) === null || _d === void 0 ? void 0 : _d.set({
key: this.cacheKeyName,
value: this.collectionNameCache,
ttl: this.cacheTtl,
}));
await this.persistCache();
}
// prune entries from the cache
async pruneCache() {
var _a;
// prune old entries from the cache
await this.loadFromCache();
const now = Date.now();
for (const [identifier, storageInfo] of Object.entries(this.collectionNameCache)) {
// sorting the keys by lastAccess ascending so we can remove the oldest
const sortedCache = Object.entries(this.collectionNameCache).sort((a, b) => {
var _a, _b, _c, _d;
const aLastAccess = (_b = (_a = a[1]) === null || _a === void 0 ? void 0 : _a.lastAccess) !== null && _b !== void 0 ? _b : 0;
const bLastAccess = (_d = (_c = b[1]) === null || _c === void 0 ? void 0 : _c.lastAccess) !== null && _d !== void 0 ? _d : 0;
return aLastAccess - bLastAccess;
});
const identifiersToDelete = new Set();
for (const [identifier, storageInfo] of sortedCache) {
if (!storageInfo)

@@ -155,5 +161,20 @@ continue;

if (lastAccess < now - this.pruningAge) {
delete this.collectionNameCache[identifier];
identifiersToDelete.add(identifier);
}
}
// delete oldest identifiers if number is greater than maxCacheSize
if (sortedCache.length > this.maxCacheSize) {
for (let i = 0; i < sortedCache.length - this.maxCacheSize; i += 1) {
const [identifier] = sortedCache[i];
identifiersToDelete.add(identifier);
}
}
// delete the identifiers from the cache
for (const identifier of identifiersToDelete) {
delete this.collectionNameCache[identifier];
}
await this.persistCache();
}
async persistCache() {
var _a;
await ((_a = this.localCache) === null || _a === void 0 ? void 0 : _a.set({

@@ -160,0 +181,0 @@ key: this.cacheKeyName,

import { expect } from '@open-wc/testing';
import { CollectionNameCache } from '../src/collection-name-cache';
import { MockLocalCache } from './mocks/mock-local-cache';
import { mockSearchResponse, mockSearchResponseOnlyFoo, } from './mocks/mock-search-response';

@@ -170,3 +171,3 @@ import { MockSearchService } from './mocks/mock-search-service';

pruneInterval: 20,
pruningAge: 75,
pruningAge: 80,
});

@@ -190,3 +191,80 @@ await collectionNameFetcher.preloadIdentifiers([

});
it('removes old items if caches gets too big', async () => {
const mockSearchService = new MockSearchService();
mockSearchService.searchResult = mockSearchResponse;
const collectionNameFetcher = new CollectionNameCache({
searchService: mockSearchService,
loadDelay: 110,
pruneInterval: 150,
maxCacheSize: 2,
});
// add some time in-between so the timestamps aren't all identical
await collectionNameFetcher.collectionNameFor('foo-collection');
await promisedSleep(50);
await collectionNameFetcher.collectionNameFor('bar-collection');
await promisedSleep(50);
await collectionNameFetcher.collectionNameFor('baz-collection');
expect(mockSearchService.searchCallCount).to.equal(1);
// waiting 60ms for the pruner to come through and prune the cache, which should remove the first item
// since our max size is 2
await promisedSleep(60);
// first check the bar-collection, which should not have been pruned so we still only have 1 request
await collectionNameFetcher.collectionNameFor('bar-collection');
expect(mockSearchService.searchCallCount).to.equal(1);
// now we're going to fetch the one that should have been pruned so we should see another request
await collectionNameFetcher.collectionNameFor('foo-collection');
// wait to make sure the load delay elapses
await promisedSleep(120);
// and another request had to be made
expect(mockSearchService.searchCallCount).to.equal(2);
});
it('can persist the cache to localCache', async () => {
const mockLocalCache = new MockLocalCache();
const mockSearchService = new MockSearchService();
mockSearchService.searchResult = mockSearchResponse;
const collectionNameFetcher = new CollectionNameCache({
searchService: mockSearchService,
localCache: mockLocalCache,
loadDelay: 25,
});
await collectionNameFetcher.collectionNameFor('foo-collection');
await collectionNameFetcher.collectionNameFor('bar-collection');
await collectionNameFetcher.collectionNameFor('baz-collection');
// wait for the load to occur
await promisedSleep(50);
expect(mockLocalCache.storage['collection-name-cache']['bar-collection'].name).to.equal('Bar Collection');
expect(mockLocalCache.storage['collection-name-cache']['foo-collection'].name).to.equal('Foo Collection');
expect(mockLocalCache.storage['collection-name-cache']['baz-collection'].name).to.equal('Baz Collection');
});
it('will use localCache data if available', async () => {
const mockLocalCache = new MockLocalCache();
mockLocalCache.storage['collection-name-cache'] = {
'foo-collection': {
name: 'Foo Collection',
timestamp: Date.now(),
},
'bar-collection': {
name: 'Bar Collection',
lastAccess: Date.now(),
},
};
const mockSearchService = new MockSearchService();
mockSearchService.searchResult = mockSearchResponse;
const collectionNameFetcher = new CollectionNameCache({
searchService: mockSearchService,
localCache: mockLocalCache,
loadDelay: 25,
});
await collectionNameFetcher.collectionNameFor('foo-collection');
await collectionNameFetcher.collectionNameFor('bar-collection');
await promisedSleep(50);
expect(mockSearchService.searchCallCount).to.equal(0);
// this is not in the cache
await collectionNameFetcher.collectionNameFor('baz-collection');
// wait for the load to occur
await promisedSleep(50);
expect(mockSearchService.searchCallCount).to.equal(1);
expect(mockLocalCache.storage['collection-name-cache']['baz-collection'].name).to.equal('Baz Collection');
});
});
//# sourceMappingURL=collection-name-cache.test.js.map

@@ -10,3 +10,3 @@ {

"author": "Internet Archive",
"version": "0.0.1-alpha.5",
"version": "0.0.1-alpha.6",
"main": "dist/index.js",

@@ -13,0 +13,0 @@ "module": "dist/index.js",

@@ -54,3 +54,3 @@ /* eslint-disable camelcase */

private defaultPruningInterval = 1000 * 10;
private defaultPruningInterval = 1000 * 30;

@@ -114,2 +114,4 @@ private fetchTimeout: number | null = null;

private maxCacheSize = 2500;
private cacheLoaded = false;

@@ -119,2 +121,3 @@

searchService: SearchServiceInterface;
maxCacheSize?: number;
localCache?: LocalCacheInterface;

@@ -129,5 +132,6 @@ loadDelay?: number;

this.pruningAge = options.pruningAge ?? this.pruningAge;
this.maxCacheSize = options.maxCacheSize ?? this.maxCacheSize;
this.pruneCache();
setInterval(async () => {
await this.loadFromCache();
await this.pruneCache();

@@ -215,23 +219,46 @@ }, options.pruneInterval ?? this.defaultPruningInterval);

await this.localCache?.set({
key: this.cacheKeyName,
value: this.collectionNameCache,
ttl: this.cacheTtl,
});
await this.persistCache();
}
// prune entries from the cache
async pruneCache(): Promise<void> {
// prune old entries from the cache
await this.loadFromCache();
const now = Date.now();
for (const [identifier, storageInfo] of Object.entries(
this.collectionNameCache
)) {
// sorting the keys by lastAccess ascending so we can remove the oldest
const sortedCache = Object.entries(this.collectionNameCache).sort(
(a, b) => {
const aLastAccess = a[1]?.lastAccess ?? 0;
const bLastAccess = b[1]?.lastAccess ?? 0;
return aLastAccess - bLastAccess;
}
);
const identifiersToDelete = new Set<string>();
for (const [identifier, storageInfo] of sortedCache) {
if (!storageInfo) continue;
const { lastAccess } = storageInfo;
if (lastAccess < now - this.pruningAge) {
delete this.collectionNameCache[identifier];
identifiersToDelete.add(identifier);
}
}
// delete oldest identifiers if number is greater than maxCacheSize
if (sortedCache.length > this.maxCacheSize) {
for (let i = 0; i < sortedCache.length - this.maxCacheSize; i += 1) {
const [identifier] = sortedCache[i];
identifiersToDelete.add(identifier);
}
}
// delete the identifiers from the cache
for (const identifier of identifiersToDelete) {
delete this.collectionNameCache[identifier];
}
await this.persistCache();
}
private async persistCache(): Promise<void> {
await this.localCache?.set({

@@ -238,0 +265,0 @@ key: this.cacheKeyName,

import { expect } from '@open-wc/testing';
import { CollectionNameCache } from '../src/collection-name-cache';
import { MockLocalCache } from './mocks/mock-local-cache';
import {

@@ -213,3 +214,3 @@ mockSearchResponse,

pruneInterval: 20,
pruningAge: 75,
pruningAge: 80,
});

@@ -240,2 +241,104 @@

});
it('removes old items if caches gets too big', async () => {
const mockSearchService = new MockSearchService();
mockSearchService.searchResult = mockSearchResponse;
const collectionNameFetcher = new CollectionNameCache({
searchService: mockSearchService,
loadDelay: 110,
pruneInterval: 150,
maxCacheSize: 2,
});
// add some time in-between so the timestamps aren't all identical
await collectionNameFetcher.collectionNameFor('foo-collection');
await promisedSleep(50);
await collectionNameFetcher.collectionNameFor('bar-collection');
await promisedSleep(50);
await collectionNameFetcher.collectionNameFor('baz-collection');
expect(mockSearchService.searchCallCount).to.equal(1);
// waiting 60ms for the pruner to come through and prune the cache, which should remove the first item
// since our max size is 2
await promisedSleep(60);
// first check the bar-collection, which should not have been pruned so we still only have 1 request
await collectionNameFetcher.collectionNameFor('bar-collection');
expect(mockSearchService.searchCallCount).to.equal(1);
// now we're going to fetch the one that should have been pruned so we should see another request
await collectionNameFetcher.collectionNameFor('foo-collection');
// wait to make sure the load delay elapses
await promisedSleep(120);
// and another request had to be made
expect(mockSearchService.searchCallCount).to.equal(2);
});
it('can persist the cache to localCache', async () => {
const mockLocalCache = new MockLocalCache();
const mockSearchService = new MockSearchService();
mockSearchService.searchResult = mockSearchResponse;
const collectionNameFetcher = new CollectionNameCache({
searchService: mockSearchService,
localCache: mockLocalCache,
loadDelay: 25,
});
await collectionNameFetcher.collectionNameFor('foo-collection');
await collectionNameFetcher.collectionNameFor('bar-collection');
await collectionNameFetcher.collectionNameFor('baz-collection');
// wait for the load to occur
await promisedSleep(50);
expect(
mockLocalCache.storage['collection-name-cache']['bar-collection'].name
).to.equal('Bar Collection');
expect(
mockLocalCache.storage['collection-name-cache']['foo-collection'].name
).to.equal('Foo Collection');
expect(
mockLocalCache.storage['collection-name-cache']['baz-collection'].name
).to.equal('Baz Collection');
});
it('will use localCache data if available', async () => {
const mockLocalCache = new MockLocalCache();
mockLocalCache.storage['collection-name-cache'] = {
'foo-collection': {
name: 'Foo Collection',
timestamp: Date.now(),
},
'bar-collection': {
name: 'Bar Collection',
lastAccess: Date.now(),
},
};
const mockSearchService = new MockSearchService();
mockSearchService.searchResult = mockSearchResponse;
const collectionNameFetcher = new CollectionNameCache({
searchService: mockSearchService,
localCache: mockLocalCache,
loadDelay: 25,
});
await collectionNameFetcher.collectionNameFor('foo-collection');
await collectionNameFetcher.collectionNameFor('bar-collection');
await promisedSleep(50);
expect(mockSearchService.searchCallCount).to.equal(0);
// this is not in the cache
await collectionNameFetcher.collectionNameFor('baz-collection');
// wait for the load to occur
await promisedSleep(50);
expect(mockSearchService.searchCallCount).to.equal(1);
expect(
mockLocalCache.storage['collection-name-cache']['baz-collection'].name
).to.equal('Baz Collection');
});
});

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc