Skip to content

Commit

Permalink
add memory cache when supplied cache is NOOP
Browse files Browse the repository at this point in the history
  • Loading branch information
shamilovtim committed Nov 20, 2023
1 parent 2421b28 commit 6c2ecbf
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 27 deletions.
5 changes: 3 additions & 2 deletions packages/dids/src/did-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import type {
DidResolverCache,
DidMethodResolver,
Expand All @@ -6,7 +7,7 @@ import type {
} from './types.js';

import { parseDid } from './utils.js';
import { DidResolverCacheNoop } from './resolver-cache-noop.js';
import { DidResolverCacheMemory } from './resolver-cache-memory.js';

export type DidResolverOptions = {
didResolvers: DidMethodResolver[];
Expand Down Expand Up @@ -37,7 +38,7 @@ export class DidResolver {
* @param options.cache - Optional. A cache for storing resolved DID documents. If not provided, a no-operation cache is used.
*/
constructor(options: DidResolverOptions) {
this.cache = options.cache || DidResolverCacheNoop;
this.cache = options.cache ?? new DidResolverCacheMemory();

for (const resolver of options.didResolvers) {
this.didResolvers.set(resolver.methodName, resolver);
Expand Down
65 changes: 65 additions & 0 deletions packages/dids/src/resolver-cache-memory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { type DidResolutionResult, type DidResolverCache } from '@web5/dids';
import ms from 'ms';

type DidResolverCacheOptions = {
ttl?: string;
};

type CacheWrapper = {
ttlMillis: number;
value: DidResolutionResult;
};

/**
* Simple unpersisted memory-based cache for did resolution results.
*/
export class DidResolverCacheMemory implements DidResolverCache {
private cache: Map<string, CacheWrapper>;
private ttl: number;

constructor(options: DidResolverCacheOptions = {}) {
let { ttl } = options;

ttl ??= '15m';

this.cache = new Map();
this.ttl = ms(ttl);
}

async get(did: string) {
const cacheWrapper = this.cache.get(did);
if (!cacheWrapper) return;

if (Date.now() >= cacheWrapper.ttlMillis) {
this.cache.delete(did);
// this.cache.nextTick(() => this.cache.del(did));
return;
} else {
return cacheWrapper.value;
}
}

set(did: string, value: DidResolutionResult) {
const cacheWrapper = {
ttlMillis: Date.now() + this.ttl,
value,
};

this.cache.set(did, cacheWrapper);
return Promise.resolve();
}

delete(did: string) {
this.cache.delete(did);
return Promise.resolve();
}

clear() {
this.cache.clear();
return Promise.resolve();
}

close() {
return Promise.resolve();
}
}
25 changes: 0 additions & 25 deletions packages/dids/src/resolver-cache-noop.ts

This file was deleted.

110 changes: 110 additions & 0 deletions packages/dids/tests/resolver-cache-memory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import sinon from 'sinon';
import chai, { expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { DidResolverCacheMemory } from '../src/resolver-cache-memory.js';

chai.use(chaiAsPromised);

describe('DidResolverCacheMemory', () => {
let cache: DidResolverCacheMemory;
let clock: sinon.SinonFakeTimers;

before(() => {
clock = sinon.useFakeTimers();
});

beforeEach(() => {
cache = new DidResolverCacheMemory({ ttl: '15m' });
});

afterEach(() => {
cache.close();
});

after(() => {
clock.restore();
});

describe('get', () => {
it('should return undefined for non-existent keys', async () => {
const result = await cache.get('nonexistent');
expect(result).to.be.undefined;
});

it('should return the cached value for existing keys', async () => {
const testDidResolutionResult = {
didResolutionMetadata : {},
didDocument : { id: 'abc123' },
didDocumentMetadata : {}
};

const testDid = 'did:example:alice';
await cache.set(testDid, testDidResolutionResult);
const result = await cache.get(testDid);
expect(result).to.deep.equal(testDidResolutionResult);
});

it('should return undefined for expired keys', async () => {
const testDidResolutionResult = {
didResolutionMetadata : {},
didDocument : { id: 'abc123' },
didDocumentMetadata : {}
};
const testDid = 'did:garfield:hello';
await cache.set(testDid, testDidResolutionResult);
clock.tick(16 * 60 * 1000); // Advance time to past the TTL
const result = await cache.get(testDid);
expect(result).to.be.undefined;
});
});

describe('set', () => {
it('should store a value', async () => {
const testDidResolutionResult = {
didResolutionMetadata : {},
didDocument : { id: 'abc123' },
didDocumentMetadata : {}
};
const testDid = 'did:example:alice';
await cache.set(testDid, testDidResolutionResult);
const result = await cache.get(testDid);
expect(result).to.deep.equal(testDidResolutionResult);
});
});

describe('delete', () => {
it('should remove a stored value', async () => {
const testDidResolutionResult = {
didResolutionMetadata : {},
didDocument : { id: 'abc123' },
didDocumentMetadata : {}
};
const testDid = 'did:example:alice';
await cache.set(testDid, testDidResolutionResult);
await cache.delete(testDid);
const result = await cache.get(testDid);
expect(result).to.be.undefined;
});
});

describe('clear', () => {
it('should clear all stored values', async () => {
const testDid1 = 'did:example:alice';
const testDid2 = 'did:example:alice';

await cache.set(testDid1, {
didResolutionMetadata : {},
didDocument : { id: 'abc123' },
didDocumentMetadata : {}
});
await cache.set(testDid2, {
didResolutionMetadata : {},
didDocument : { id: 'garfield' },
didDocumentMetadata : {}
});
await cache.clear();
expect(await cache.get(testDid1)).to.be.undefined;
expect(await cache.get(testDid2)).to.be.undefined;
});
});
});
5 changes: 5 additions & 0 deletions packages/identity-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Description

This package implements the building blocks in `@web5/agent` in order to provide a minimum implementation of an Identity Agent.

<!-- TODO: This is just a start. Will add a better description eventually. -->

0 comments on commit 6c2ecbf

Please sign in to comment.