Skip to content

feat: support async methods in ICacheStore #115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,9 @@ You can override the default caching strategy by passing in a class that impleme
```ts
interface ICachingStrategy {
getOrCreate<T>(cacheKey: string, createFunction: () => Promise<T & ICachable & object>): Promise<T & ICachable>;
get<T>(cacheKey: string): T & ICachable | null;
setCacheItem<T>(cacheKey: string, item: T & ICachable): void;
remove(cacheKey: string): void;
get<T>(cacheKey: string): Promise<T & ICachable | null>;
setCacheItem<T>(cacheKey: string, item: T & ICachable): void | Promise<void>;
remove(cacheKey: string): void | Promise<void>;
}
```

Expand Down
2 changes: 1 addition & 1 deletion src/SpotifyApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe("SpotifyAPI Instance", () => {

it("when access token provided, it is accurately retrieved taking precedence over any existing cached token.", async () => {
const config: SdkOptions = { cachingStrategy: new InMemoryCachingStrategy() };
config.cachingStrategy?.setCacheItem("spotify-sdk:ProvidedAccessTokenStrategy:token", { access_token: "some-old-token" });
await config.cachingStrategy?.setCacheItem("spotify-sdk:ProvidedAccessTokenStrategy:token", { access_token: "some-old-token" });

const sut = SpotifyApi.withAccessToken("client-id", { access_token: "some-new-token" } as AccessToken, config);
const token = await sut.getAccessToken();
Expand Down
4 changes: 2 additions & 2 deletions src/SpotifyApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,8 @@ export class SpotifyApi {
/**
* Removes the access token if it exists.
*/
public logOut(): void {
this.authenticationStrategy.removeAccessToken();
public async logOut(): Promise<void> {
await this.authenticationStrategy.removeAccessToken();
}

public static withUserAuthorization(clientId: string, redirectUri: string, scopes: string[] = [], config?: SdkOptions): SpotifyApi {
Expand Down
6 changes: 3 additions & 3 deletions src/auth/AuthorizationCodeWithPKCEStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ export default class AuthorizationCodeWithPKCEStrategy implements IAuthStrategy
return token;
}

public removeAccessToken(): void {
this.cache.remove(AuthorizationCodeWithPKCEStrategy.cacheKey);
public async removeAccessToken(): Promise<void> {
await this.cache.remove(AuthorizationCodeWithPKCEStrategy.cacheKey);
}

private async redirectOrVerifyToken(): Promise<AccessToken> {
Expand All @@ -66,7 +66,7 @@ export default class AuthorizationCodeWithPKCEStrategy implements IAuthStrategy
const challenge = await AccessTokenHelpers.generateCodeChallenge(verifier);

const singleUseVerifier: CachedVerifier = { verifier, expiresOnAccess: true };
this.cache.setCacheItem("spotify-sdk:verifier", singleUseVerifier);
await this.cache.setCacheItem("spotify-sdk:verifier", singleUseVerifier);

const redirectTarget = await this.generateRedirectUrlForUser(this.scopes, challenge);
await this.configuration!.redirectionStrategy.redirect(redirectTarget);
Expand Down
4 changes: 2 additions & 2 deletions src/auth/ClientCredentialsStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export default class ClientCredentialsStrategy implements IAuthStrategy {
return token;
}

public removeAccessToken(): void {
this.cache.remove(ClientCredentialsStrategy.cacheKey);
public async removeAccessToken(): Promise<void> {
await this.cache.remove(ClientCredentialsStrategy.cacheKey);
}

private async getTokenFromApi(): Promise<AccessToken> {
Expand Down
2 changes: 1 addition & 1 deletion src/auth/IAuthStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ export default interface IAuthStrategy {
setConfiguration(configuration: SdkConfiguration): void;
getOrCreateAccessToken(): Promise<AccessToken>;
getAccessToken(): Promise<AccessToken | null>;
removeAccessToken(): void;
removeAccessToken(): void | Promise<void>;
}
4 changes: 2 additions & 2 deletions src/auth/ImplicitGrantStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export default class ImplicitGrantStrategy implements IAuthStrategy {
return token;
}

public removeAccessToken(): void {
this.cache.remove(ImplicitGrantStrategy.cacheKey);
public async removeAccessToken(): Promise<void> {
await this.cache.remove(ImplicitGrantStrategy.cacheKey);
}

private async redirectOrVerifyToken(): Promise<AccessToken> {
Expand Down
10 changes: 5 additions & 5 deletions src/caching/GenericCache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,27 @@ describe('GenericCache', () => {
});

it('should set and get a value', async () => {
sut.set('test', { test: "test" }, 1000);
await sut.set('test', { test: "test" }, 1000);
const result = await sut.get<{ test: string }>('test');
expect(result?.test).toBe('test');
});

it('should remove a value', async () => {
sut.set('test', { test: "test" }, 1000);
await sut.set('test', { test: "test" }, 1000);
sut.remove('test');
const result = await sut.get<{ test: string }>('test');
expect(result).toBeNull();
});

it('should return null for expired value', async () => {
sut.set('test', { test: "test" }, 0);
await sut.set('test', { test: "test" }, 0);
const result = await sut.get<{ test: string }>('test');
expect(result).toBeNull();
});

it("should return and remove value if expiresOnAccess is true", async () => {
const value = { test: "test", expiresOnAccess: true };
sut.setCacheItem('test', value);
await sut.setCacheItem('test', value);

const result = await sut.get<{ test: string }>('test');
expect(result).toEqual(value);
Expand All @@ -60,7 +60,7 @@ describe('GenericCache', () => {
});

it("should return existing item if it exists in the cache when getOrCreate is called", async () => {
sut.set('test', { test: "test" }, 1000);
await sut.set('test', { test: "test" }, 1000);

const result = await sut.getOrCreate('test', async () => {
return { test: "test2" };
Expand Down
24 changes: 12 additions & 12 deletions src/caching/GenericCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,22 @@ export default class GenericCache implements ICachingStrategy {
}

if (!isEmptyAccessToken(newCacheItem)) {
this.setCacheItem(cacheKey, newCacheItem);
await this.setCacheItem(cacheKey, newCacheItem);
}

return newCacheItem;
}

public async get<T>(cacheKey: string): Promise<T & ICachable | null> {
let asString = this.storage.get(cacheKey);
let asString = await this.storage.get(cacheKey);
let cachedItem: T & ICachable = asString ? JSON.parse(asString) : null;

if (this.itemDueToExpire(cachedItem) && this.updateFunctions.has(cacheKey)) {
const updateFunction = this.updateFunctions.get(cacheKey);
await this.tryUpdateItem(cacheKey, cachedItem, updateFunction!);

// Ensure updated item is returned
asString = this.storage.get(cacheKey);
asString = await this.storage.get(cacheKey);
cachedItem = asString ? JSON.parse(asString) : null;
}

Expand All @@ -58,31 +58,31 @@ export default class GenericCache implements ICachingStrategy {
}

if (cachedItem.expires && (cachedItem.expires === -1 || cachedItem.expires <= Date.now())) {
this.remove(cacheKey);
await this.remove(cacheKey);
return null;
}

if (cachedItem.expiresOnAccess && cachedItem.expiresOnAccess === true) {
this.remove(cacheKey);
await this.remove(cacheKey);
return cachedItem;
}

return cachedItem;
}

public set(cacheKey: string, value: object, expiresIn: number): void {
public async set(cacheKey: string, value: object, expiresIn: number): Promise<void> {
const expires = Date.now() + expiresIn;
const cacheItem: ICachable = { ...value, expires };
this.setCacheItem(cacheKey, cacheItem);
await this.setCacheItem(cacheKey, cacheItem);
}

public setCacheItem(cacheKey: string, cacheItem: ICachable): void {
public async setCacheItem(cacheKey: string, cacheItem: ICachable): Promise<void> {
const asString = JSON.stringify(cacheItem);
this.storage.set(cacheKey, asString);
await this.storage.set(cacheKey, asString);
}

public remove(cacheKey: string): void {
this.storage.remove(cacheKey);
public async remove(cacheKey: string): Promise<void> {
await this.storage.remove(cacheKey);
}

private itemDueToExpire(item: ICachable): boolean {
Expand Down Expand Up @@ -114,7 +114,7 @@ export default class GenericCache implements ICachingStrategy {
try {
const updated = await updateFunction(cachedItem);
if (updated) {
this.setCacheItem(key, updated);
await this.setCacheItem(key, updated);
}
} catch (e) {
console.error(e);
Expand Down
6 changes: 3 additions & 3 deletions src/caching/ICacheStore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface ICacheStore {
get(key: string): string | null;
set(key: string, value: string): void;
remove(key: string): void;
get(key: string): string | null | Promise<string | null>;
set(key: string, value: string): void | Promise<void>;
remove(key: string): void | Promise<void>;
}
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export interface ICachingStrategy {
): Promise<T & ICachable>;

get<T>(cacheKey: string): Promise<T & ICachable | null>;
setCacheItem<T>(cacheKey: string, item: T & ICachable): void;
remove(cacheKey: string): void;
setCacheItem<T>(cacheKey: string, item: T & ICachable): void | Promise<void>;
remove(cacheKey: string): void | Promise<void>;
}

export interface ICachable {
Expand Down