-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0af81f8
commit d33fa41
Showing
17 changed files
with
453 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
node_modules | ||
dist |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import {cache, grpc} from '@momento/wire-types-typescript'; | ||
import {authInterceptor} from "./grpc/AuthInterceptor"; | ||
import {cacheNameInterceptor} from "./grpc/CacheNameInterceptor"; | ||
import {MomentoCacheResult, momentoResultConverter} from "./messages/Result"; | ||
import {IllegalArgumentError} from "./errors/IllegalArgumentError"; | ||
import {ClientSdkError} from "./errors/ClientSdkError"; | ||
import {errorMapper} from "./errors/GrpcErrorMapper"; | ||
|
||
type GetResponse = { | ||
body: string; | ||
message: string; | ||
result: MomentoCacheResult; | ||
} | ||
|
||
type SetResponse = { | ||
message: string; | ||
result: MomentoCacheResult; | ||
} | ||
|
||
export class Cache { | ||
private readonly client: cache.cache_client.ScsClient; | ||
private readonly textEncoder: TextEncoder; | ||
private readonly textDecoder: TextDecoder; | ||
private readonly interceptors: grpc.Interceptor[]; | ||
private readonly cacheName: string; | ||
constructor(authToken: string, cacheName: string, endpoint: string) { | ||
this.client = new cache.cache_client.ScsClient(endpoint, grpc.ChannelCredentials.createSsl()) | ||
this.textEncoder = new TextEncoder(); | ||
this.textDecoder = new TextDecoder(); | ||
this.cacheName = cacheName; | ||
this.interceptors = [authInterceptor(authToken), cacheNameInterceptor(cacheName)] | ||
} | ||
|
||
public async set(key: string, value: string, ttl: number = 1000): Promise<SetResponse> { | ||
this.ensureValidSetRequest(key, value, ttl) | ||
const encodedKey = this.textEncoder.encode(key) | ||
const encodedValue = this.textEncoder.encode(value) | ||
|
||
const request = new cache.cache_client.SetRequest({ | ||
cache_body: encodedValue, | ||
cache_key: encodedKey, | ||
ttl_milliseconds: ttl | ||
}) | ||
return new Promise((resolve, reject) => { | ||
this.client.Set(request, { interceptors: this.interceptors }, (err, resp) => { | ||
if (err) { | ||
reject(errorMapper(err)) | ||
} | ||
if (resp) { | ||
resolve(this.parseSetResponse(resp)) | ||
} | ||
reject(new ClientSdkError("unable to perform set")) | ||
}) | ||
}) | ||
} | ||
|
||
public async get(key: string): Promise<GetResponse> { | ||
this.ensureValidKey(key); | ||
const request = new cache.cache_client.GetRequest({ | ||
cache_key: this.textEncoder.encode(key) | ||
}) | ||
|
||
return new Promise((resolve, reject) => { | ||
this.client.Get(request, { interceptors: this.interceptors }, (err, resp) => { | ||
if (err) { | ||
reject(errorMapper(err)) | ||
} | ||
if (resp) { | ||
resolve(this.parseGetResponse(resp)) | ||
} | ||
reject(new ClientSdkError("unable to get from cache")) | ||
}) | ||
}) | ||
} | ||
|
||
private parseGetResponse(resp: cache.cache_client.GetResponse): GetResponse { | ||
return { | ||
result: momentoResultConverter(resp.result), | ||
message: resp.message, | ||
body: this.textDecoder.decode(resp.cache_body) | ||
} | ||
} | ||
|
||
private parseSetResponse(resp: cache.cache_client.SetResponse): SetResponse { | ||
return { | ||
result: momentoResultConverter(resp.result), | ||
message: resp.message | ||
} | ||
} | ||
|
||
private ensureValidKey(key: any) { | ||
if (!key) { | ||
throw new IllegalArgumentError("key must not be empty") | ||
} | ||
} | ||
|
||
private ensureValidSetRequest(key: any, value: any, ttl: number) { | ||
this.ensureValidKey(key) | ||
|
||
if (!value) { | ||
throw new IllegalArgumentError("value must not be empty") | ||
} | ||
|
||
if (ttl && ttl < 0) { | ||
throw new IllegalArgumentError("ttl must be a positive integer") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import {control, grpc} from '@momento/wire-types-typescript'; | ||
import jwtDecode from "jwt-decode"; | ||
import {Cache} from "./Cache"; | ||
import {authInterceptor} from "./grpc/AuthInterceptor"; | ||
import {IllegalArgumentError} from "./errors/IllegalArgumentError"; | ||
import {ClientSdkError} from "./errors/ClientSdkError"; | ||
import {Status} from "@grpc/grpc-js/build/src/constants"; | ||
import {CacheAlreadyExistsError} from "./errors/CacheAlreadyExistsError"; | ||
import {errorMapper} from "./errors/GrpcErrorMapper"; | ||
|
||
type Claims = { | ||
cp: string, | ||
c: string | ||
} | ||
|
||
export class Momento { | ||
private readonly client: control.control_client.ScsControlClient; | ||
private readonly interceptors: grpc.Interceptor[]; | ||
private readonly controlEndpoint: string; | ||
private readonly cacheEndpoint: string; | ||
private readonly authToken: string; | ||
constructor(authToken: string, endpointOverride?: string) { | ||
const claims = Momento.decodeJwt(authToken); | ||
this.authToken = authToken; | ||
this.interceptors = [authInterceptor(authToken)] | ||
this.controlEndpoint = endpointOverride ? `control.${endpointOverride}` : claims.cp; | ||
this.cacheEndpoint = endpointOverride ? `cache.${endpointOverride}` : claims.c; | ||
this.client = new control.control_client.ScsControlClient(this.controlEndpoint, grpc.ChannelCredentials.createSsl()) | ||
} | ||
|
||
private static decodeJwt(jwt?: string): Claims { | ||
if(!jwt) { | ||
throw new IllegalArgumentError("malformed auth token") | ||
} | ||
try { | ||
return jwtDecode<Claims>(jwt) | ||
} catch (e) { | ||
throw new ClientSdkError("failed to parse jwt") | ||
} | ||
|
||
} | ||
|
||
public getCache(name: string): Cache { | ||
this.validateCachename(name); | ||
return new Cache(this.authToken, name, this.cacheEndpoint); | ||
} | ||
|
||
public createCache(name: string): Promise<control.control_client.CreateCacheResponse> { | ||
this.validateCachename(name); | ||
const request = new control.control_client.CreateCacheRequest({ cache_name: name }) | ||
return new Promise((resolve, reject) => { | ||
this.client.CreateCache(request, { interceptors: this.interceptors}, (err, resp) => { | ||
if (err) { | ||
if (err.code === Status.ALREADY_EXISTS) { | ||
reject(new CacheAlreadyExistsError(`cache with name: ${name} already exists`)) | ||
} | ||
reject(errorMapper(err)) | ||
} | ||
if(resp) { | ||
resolve(resp) | ||
} | ||
reject(new ClientSdkError("unable to create cache")) | ||
}) | ||
}) | ||
} | ||
|
||
private validateCachename(name: string) { | ||
if (!name) { | ||
throw new IllegalArgumentError("cache name must not be null") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import {ClientSdkError} from "./ClientSdkError"; | ||
|
||
export class CacheAlreadyExistsError extends ClientSdkError { | ||
constructor(message: string) { | ||
super(message); | ||
this.name = "CacheAlreadyExistsError" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import {ClientSdkError} from "./ClientSdkError"; | ||
|
||
export class CacheNotFoundError extends ClientSdkError { | ||
constructor(message: string) { | ||
super(message); | ||
this.name = "CacheNotFoundError"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export class ClientSdkError extends Error { | ||
constructor(message: string) { | ||
super(message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import {grpc} from '@momento/wire-types-typescript'; | ||
import {ClientSdkError} from "./ClientSdkError"; | ||
import {Status} from "@grpc/grpc-js/build/src/constants"; | ||
import {PermissionDeniedError} from "./PermissionDeniedError"; | ||
import {CacheNotFoundError} from "./CacheNotFoundError"; | ||
import {IllegalArgumentError} from "./IllegalArgumentError"; | ||
import {InternalServerError} from "./InternalServerError"; | ||
|
||
export function errorMapper(err: grpc.ServiceError): ClientSdkError { | ||
if (err.code === Status.PERMISSION_DENIED) { | ||
return new PermissionDeniedError(err.message) | ||
} | ||
if (err.code === Status.NOT_FOUND) { | ||
return new CacheNotFoundError(err.message) | ||
} | ||
if (err.code === Status.INVALID_ARGUMENT) { | ||
return new IllegalArgumentError(err.message) | ||
} | ||
return new InternalServerError("unable to process request") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import {ClientSdkError} from "./ClientSdkError"; | ||
|
||
export class IllegalArgumentError extends ClientSdkError { | ||
constructor(message: string) { | ||
super(message); | ||
this.name = "IllegalArgumentError" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import {ClientSdkError} from "./ClientSdkError"; | ||
|
||
export class InternalServerError extends ClientSdkError { | ||
constructor(message: string) { | ||
super(message); | ||
this.name = "InternalServerError" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import {ClientSdkError} from "./ClientSdkError"; | ||
|
||
export class PermissionDeniedError extends ClientSdkError { | ||
constructor(message: string) { | ||
super(message); | ||
this.name = "PermissionDeniedError" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import {grpc} from "@momento/wire-types-typescript"; | ||
|
||
grpc.InterceptingCall | ||
|
||
export const authInterceptor = (token: string): grpc.Interceptor => { | ||
return (options, nextCall) => { | ||
return new grpc.InterceptingCall(nextCall(options), { | ||
start: function (metadata, listener, next) { | ||
metadata.add("Authorization", token) | ||
next(metadata, {}) | ||
}, | ||
}) | ||
} | ||
} |
Oops, something went wrong.