From 8508af07dda4521df9946b6db4e63a44d26fdb06 Mon Sep 17 00:00:00 2001 From: Nate Anderson Date: Fri, 19 May 2023 16:46:49 -0700 Subject: [PATCH] feat: Add web sdk configurations (#518) * feat: Add web sdk configurations Move transport-strategy and grpc-configuration out of core since it contains options that are only useful for the node sdk. Create web versions of transport strategy and grpc configuration. Create web version of configurations. Plumb deadline millis into the web sdk. --- .../src/config/configuration.ts | 2 +- .../src/config/configurations.ts | 2 +- .../src/config/transport/index.ts | 3 +- .../config/transport/transport-strategy.ts | 130 ++++++++++++++++- .../transport/transport-strategy.test.ts | 2 +- .../client-sdk-web/src/cache-client-props.ts | 22 +++ packages/client-sdk-web/src/cache-client.ts | 36 +---- .../src/config/configuration.ts | 73 +++++++--- .../src/config/configurations.ts | 96 +++++++++++++ .../config/transport/grpc-configuration.ts | 18 --- .../src/config/transport/index.ts | 0 .../config/transport/transport-strategy.ts | 72 ++++++++++ packages/client-sdk-web/src/index.ts | 2 + .../src/internal/data-client.ts | 47 +++++- .../src/internal/pubsub-client.ts | 50 ++++--- .../src/utils/web-client-utils.ts | 5 + .../test/integration/integration-setup.ts | 29 ++-- .../test/integration/ping.test.ts | 22 ++- .../transport/transport-strategy.test.ts | 53 +++++++ .../config/transport/transport-strategy.ts | 134 ------------------ 20 files changed, 537 insertions(+), 261 deletions(-) rename packages/{core => client-sdk-nodejs}/test/unit/config/transport/transport-strategy.test.ts (98%) create mode 100644 packages/client-sdk-web/src/cache-client-props.ts create mode 100644 packages/client-sdk-web/src/config/configurations.ts rename packages/{core => client-sdk-web}/src/config/transport/grpc-configuration.ts (50%) rename packages/{core => client-sdk-web}/src/config/transport/index.ts (100%) create mode 100644 packages/client-sdk-web/src/config/transport/transport-strategy.ts create mode 100644 packages/client-sdk-web/test/unit/config/transport/transport-strategy.test.ts delete mode 100644 packages/core/src/config/transport/transport-strategy.ts diff --git a/packages/client-sdk-nodejs/src/config/configuration.ts b/packages/client-sdk-nodejs/src/config/configuration.ts index 7813899ba..f0cdaad56 100644 --- a/packages/client-sdk-nodejs/src/config/configuration.ts +++ b/packages/client-sdk-nodejs/src/config/configuration.ts @@ -1,7 +1,7 @@ import {RetryStrategy} from './retry/retry-strategy'; import {Middleware} from './middleware/middleware'; import {MomentoLoggerFactory} from '../'; -import {TransportStrategy} from '../config/transport/transport-strategy'; +import {TransportStrategy} from './transport'; export interface ConfigurationProps { /** diff --git a/packages/client-sdk-nodejs/src/config/configurations.ts b/packages/client-sdk-nodejs/src/config/configurations.ts index 3df315b9b..c99627b09 100644 --- a/packages/client-sdk-nodejs/src/config/configurations.ts +++ b/packages/client-sdk-nodejs/src/config/configurations.ts @@ -11,7 +11,7 @@ import { StaticGrpcConfiguration, StaticTransportStrategy, TransportStrategy, -} from '../config/transport'; +} from './transport'; // 4 minutes. We want to remain comfortably underneath the idle timeout for AWS NLB, which is 350s. const defaultMaxIdleMillis = 4 * 60 * 1_000; diff --git a/packages/client-sdk-nodejs/src/config/transport/index.ts b/packages/client-sdk-nodejs/src/config/transport/index.ts index 72e3e8fad..274180861 100644 --- a/packages/client-sdk-nodejs/src/config/transport/index.ts +++ b/packages/client-sdk-nodejs/src/config/transport/index.ts @@ -1 +1,2 @@ -export * from '@gomomento/sdk-core/dist/src/config/transport'; +export * from './grpc-configuration'; +export * from './transport-strategy'; diff --git a/packages/client-sdk-nodejs/src/config/transport/transport-strategy.ts b/packages/client-sdk-nodejs/src/config/transport/transport-strategy.ts index 4f103b195..290acd344 100644 --- a/packages/client-sdk-nodejs/src/config/transport/transport-strategy.ts +++ b/packages/client-sdk-nodejs/src/config/transport/transport-strategy.ts @@ -1 +1,129 @@ -export * from '@gomomento/sdk-core/dist/src/config/transport/transport-strategy'; +import {GrpcConfiguration, GrpcConfigurationProps} from './grpc-configuration'; + +export interface TransportStrategy { + /** + * Configures the low-level gRPC settings for the Momento client's communication + * with the Momento server. + * @returns {GrpcConfiguration} + */ + getGrpcConfig(): GrpcConfiguration; + + /** + * Copy constructor for overriding the gRPC configuration + * @param {GrpcConfiguration} grpcConfig + * @returns {TransportStrategy} a new TransportStrategy with the specified gRPC config. + */ + withGrpcConfig(grpcConfig: GrpcConfiguration): TransportStrategy; + + /** + * Copy constructor to update the client-side timeout + * @param {number} clientTimeoutMillis + * @returns {TransportStrategy} a new TransportStrategy with the specified client timeout + */ + withClientTimeoutMillis(clientTimeoutMillis: number): TransportStrategy; + + /** + * The maximum duration for which a connection may remain idle before being replaced. This + * setting can be used to force re-connection of a client if it has been idle for too long. + * In environments such as AWS lambda, if the lambda is suspended for too long the connection + * may be closed by the load balancer, resulting in an error on the subsequent request. If + * this setting is set to a duration less than the load balancer timeout, we can ensure that + * the connection will be refreshed to avoid errors. + * @returns {number} + */ + getMaxIdleMillis(): number; + + /** + * Copy constructor to update the max idle connection timeout. (See {getMaxIdleMillis}.) + * @param {number} maxIdleMillis + * @returns {TransportStrategy} a new TransportStrategy with the specified max idle connection timeout. + */ + withMaxIdleMillis(maxIdleMillis: number): TransportStrategy; +} + +export interface TransportStrategyProps { + /** + * low-level gRPC settings for communication with the Momento server + */ + grpcConfiguration: GrpcConfiguration; + /** + * The maximum duration for which a connection may remain idle before being replaced. This + * setting can be used to force re-connection of a client if it has been idle for too long. + * In environments such as AWS lambda, if the lambda is suspended for too long the connection + * may be closed by the load balancer, resulting in an error on the subsequent request. If + * this setting is set to a duration less than the load balancer timeout, we can ensure that + * the connection will be refreshed to avoid errors. + * @returns {number} + */ + maxIdleMillis: number; +} + +export class StaticGrpcConfiguration implements GrpcConfiguration { + private readonly deadlineMillis: number; + private readonly maxSessionMemoryMb: number; + constructor(props: GrpcConfigurationProps) { + this.deadlineMillis = props.deadlineMillis; + this.maxSessionMemoryMb = props.maxSessionMemoryMb; + } + + getDeadlineMillis(): number { + return this.deadlineMillis; + } + + getMaxSessionMemoryMb(): number { + return this.maxSessionMemoryMb; + } + + withDeadlineMillis(deadlineMillis: number): StaticGrpcConfiguration { + return new StaticGrpcConfiguration({ + deadlineMillis: deadlineMillis, + maxSessionMemoryMb: this.maxSessionMemoryMb, + }); + } + + withMaxSessionMemoryMb(maxSessionMemoryMb: number): StaticGrpcConfiguration { + return new StaticGrpcConfiguration({ + deadlineMillis: this.deadlineMillis, + maxSessionMemoryMb: maxSessionMemoryMb, + }); + } +} + +export class StaticTransportStrategy implements TransportStrategy { + private readonly grpcConfig: GrpcConfiguration; + private readonly maxIdleMillis: number; + + constructor(props: TransportStrategyProps) { + this.grpcConfig = props.grpcConfiguration; + this.maxIdleMillis = props.maxIdleMillis; + } + + getGrpcConfig(): GrpcConfiguration { + return this.grpcConfig; + } + + withGrpcConfig(grpcConfig: GrpcConfiguration): StaticTransportStrategy { + return new StaticTransportStrategy({ + grpcConfiguration: grpcConfig, + maxIdleMillis: this.maxIdleMillis, + }); + } + + getMaxIdleMillis(): number { + return this.maxIdleMillis; + } + + withMaxIdleMillis(maxIdleMillis: number): TransportStrategy { + return new StaticTransportStrategy({ + grpcConfiguration: this.grpcConfig, + maxIdleMillis: maxIdleMillis, + }); + } + + withClientTimeoutMillis(clientTimeout: number): StaticTransportStrategy { + return new StaticTransportStrategy({ + grpcConfiguration: this.grpcConfig.withDeadlineMillis(clientTimeout), + maxIdleMillis: this.maxIdleMillis, + }); + } +} diff --git a/packages/core/test/unit/config/transport/transport-strategy.test.ts b/packages/client-sdk-nodejs/test/unit/config/transport/transport-strategy.test.ts similarity index 98% rename from packages/core/test/unit/config/transport/transport-strategy.test.ts rename to packages/client-sdk-nodejs/test/unit/config/transport/transport-strategy.test.ts index 55726633d..6f6f380ce 100644 --- a/packages/core/test/unit/config/transport/transport-strategy.test.ts +++ b/packages/client-sdk-nodejs/test/unit/config/transport/transport-strategy.test.ts @@ -1,7 +1,7 @@ import { StaticGrpcConfiguration, StaticTransportStrategy, -} from '../../../../src/config/transport/transport-strategy'; +} from '../../../../src'; describe('StaticGrpcConfiguration', () => { const testDeadlineMillis = 90210; diff --git a/packages/client-sdk-web/src/cache-client-props.ts b/packages/client-sdk-web/src/cache-client-props.ts new file mode 100644 index 000000000..bd5e1a907 --- /dev/null +++ b/packages/client-sdk-web/src/cache-client-props.ts @@ -0,0 +1,22 @@ +import {CredentialProvider} from '.'; +import {Configuration} from './config/configuration'; + +export interface CacheClientProps { + /** + * Configuration settings for the cache client + */ + configuration: Configuration; + /** + * controls how the client will get authentication information for connecting to the Momento service + */ + credentialProvider: CredentialProvider; + /** + * the default time to live of object inside of cache, in seconds + */ + defaultTtlSeconds: number; +} + +/** + * @deprecated use {CacheClientProps} instead + */ +export type SimpleCacheClientProps = CacheClientProps; diff --git a/packages/client-sdk-web/src/cache-client.ts b/packages/client-sdk-web/src/cache-client.ts index e9458e13f..b3c2690f1 100644 --- a/packages/client-sdk-web/src/cache-client.ts +++ b/packages/client-sdk-web/src/cache-client.ts @@ -1,10 +1,6 @@ import {ControlClient} from './internal/control-client'; import {DataClient} from './internal/data-client'; import {PingClient} from './internal/ping-client'; -import { - CredentialProvider, - NoopMomentoLoggerFactory, -} from '@gomomento/sdk-core'; import { AbstractCacheClient, IControlClient, @@ -12,10 +8,7 @@ import { IDataClient, IPingClient, } from '@gomomento/sdk-core/dist/src/internal/clients'; - -export interface CacheClientProps { - credentialProvider: CredentialProvider; -} +import {CacheClientProps} from './cache-client-props'; export class CacheClient extends AbstractCacheClient implements ICacheClient { constructor(props: CacheClientProps) { @@ -28,28 +21,14 @@ export class CacheClient extends AbstractCacheClient implements ICacheClient { function createControlClient(props: CacheClientProps): IControlClient { return new ControlClient({ - // TODO - // TODO - // TODO these shouldn't be hard-coded - // TODO - // TODO - configuration: { - getLoggerFactory: () => new NoopMomentoLoggerFactory(), - }, + configuration: props.configuration, credentialProvider: props.credentialProvider, }); } function createDataClient(props: CacheClientProps): IDataClient { return new DataClient({ - // TODO - // TODO - // TODO these shouldn't be hard-coded - // TODO - // TODO - configuration: { - getLoggerFactory: () => new NoopMomentoLoggerFactory(), - }, + configuration: props.configuration, credentialProvider: props.credentialProvider, defaultTtlSeconds: 60, }); @@ -57,14 +36,7 @@ function createDataClient(props: CacheClientProps): IDataClient { function createPingClient(props: CacheClientProps): IPingClient { return new PingClient({ - // TODO - // TODO - // TODO these shouldn't be hard-coded - // TODO - // TODO endpoint: props.credentialProvider.getCacheEndpoint(), - configuration: { - getLoggerFactory: () => new NoopMomentoLoggerFactory(), - }, + configuration: props.configuration, }); } diff --git a/packages/client-sdk-web/src/config/configuration.ts b/packages/client-sdk-web/src/config/configuration.ts index 778601524..e10c11cc3 100644 --- a/packages/client-sdk-web/src/config/configuration.ts +++ b/packages/client-sdk-web/src/config/configuration.ts @@ -1,4 +1,5 @@ import {MomentoLoggerFactory} from '@gomomento/sdk-core'; +import {TransportStrategy} from './transport'; export interface ConfigurationProps { /** @@ -6,11 +7,10 @@ export interface ConfigurationProps { */ loggerFactory: MomentoLoggerFactory; - // TODO /** * Configures low-level options for network interactions with the Momento service */ - // transportStrategy: TransportStrategy; + transportStrategy: TransportStrategy; } /** @@ -25,22 +25,55 @@ export interface Configuration { */ getLoggerFactory(): MomentoLoggerFactory; - // /** - // * @returns {TransportStrategy} the current configuration options for wire interactions with the Momento service - // */ - // getTransportStrategy(): TransportStrategy; - - // /** - // * Copy constructor for overriding TransportStrategy - // * @param {TransportStrategy} transportStrategy - // * @returns {Configuration} a new Configuration object with the specified TransportStrategy - // */ - // withTransportStrategy(transportStrategy: TransportStrategy): Configuration; - - // /** - // * Convenience copy constructor that updates the client-side timeout setting in the TransportStrategy - // * @param {number} clientTimeoutMillis - // * @returns {Configuration} a new Configuration object with its TransportStrategy updated to use the specified client timeout - // */ - // withClientTimeoutMillis(clientTimeoutMillis: number): Configuration; + /** + * @returns {TransportStrategy} the current configuration options for wire interactions with the Momento service + */ + getTransportStrategy(): TransportStrategy; + + /** + * Copy constructor for overriding TransportStrategy + * @param {TransportStrategy} transportStrategy + * @returns {Configuration} a new Configuration object with the specified TransportStrategy + */ + withTransportStrategy(transportStrategy: TransportStrategy): Configuration; + + /** + * Convenience copy constructor that updates the client-side timeout setting in the TransportStrategy + * @param {number} clientTimeoutMillis + * @returns {Configuration} a new Configuration object with its TransportStrategy updated to use the specified client timeout + */ + withClientTimeoutMillis(clientTimeoutMillis: number): Configuration; +} + +export class CacheConfiguration implements Configuration { + private readonly loggerFactory: MomentoLoggerFactory; + private readonly transportStrategy: TransportStrategy; + + constructor(props: ConfigurationProps) { + this.loggerFactory = props.loggerFactory; + this.transportStrategy = props.transportStrategy; + } + + getLoggerFactory(): MomentoLoggerFactory { + return this.loggerFactory; + } + + getTransportStrategy(): TransportStrategy { + return this.transportStrategy; + } + + withTransportStrategy(transportStrategy: TransportStrategy): Configuration { + return new CacheConfiguration({ + loggerFactory: this.loggerFactory, + transportStrategy: transportStrategy, + }); + } + + withClientTimeoutMillis(clientTimeout: number): Configuration { + return new CacheConfiguration({ + loggerFactory: this.loggerFactory, + transportStrategy: + this.transportStrategy.withClientTimeoutMillis(clientTimeout), + }); + } } diff --git a/packages/client-sdk-web/src/config/configurations.ts b/packages/client-sdk-web/src/config/configurations.ts new file mode 100644 index 000000000..56f2f4317 --- /dev/null +++ b/packages/client-sdk-web/src/config/configurations.ts @@ -0,0 +1,96 @@ +import {CacheConfiguration} from './configuration'; +import { + DefaultMomentoLoggerFactory, + MomentoLoggerFactory, +} from '@gomomento/sdk-core'; +import { + GrpcConfiguration, + StaticGrpcConfiguration, + StaticTransportStrategy, + TransportStrategy, +} from './transport'; + +const defaultLoggerFactory: MomentoLoggerFactory = + new DefaultMomentoLoggerFactory(); + +/** + * Laptop config provides defaults suitable for a medium-to-high-latency dev environment. + * @export + * @class Laptop + */ +export class Laptop extends CacheConfiguration { + /** + * Provides the latest recommended configuration for a laptop development environment. NOTE: this configuration may + * change in future releases to take advantage of improvements we identify for default configurations. + * @param {MomentoLoggerFactory} [loggerFactory=defaultLoggerFactory] + * @returns {CacheConfiguration} + */ + static latest( + loggerFactory: MomentoLoggerFactory = defaultLoggerFactory + ): CacheConfiguration { + return Laptop.v1(loggerFactory); + } + + /** + * Provides v1 recommended configuration for a laptop development environment. This configuration is guaranteed not + * to change in future releases of the Momento web SDK. + * @param {MomentoLoggerFactory} [loggerFactory=defaultLoggerFactory] + * @returns {CacheConfiguration} + */ + static v1( + loggerFactory: MomentoLoggerFactory = defaultLoggerFactory + ): CacheConfiguration { + const deadlineMillis = 5000; + const grpcConfig: GrpcConfiguration = new StaticGrpcConfiguration({ + deadlineMillis: deadlineMillis, + }); + const transportStrategy: TransportStrategy = new StaticTransportStrategy({ + grpcConfiguration: grpcConfig, + }); + return new Laptop({ + loggerFactory: loggerFactory, + transportStrategy: transportStrategy, + }); + } +} + +/** + * Browser config provides defaults suitable for use in a web browser. + * @export + * @class Browser + */ +export class Browser extends CacheConfiguration { + /** + * Provides the latest recommended configuration for an in-browser environment. NOTE: this configuration may + * change in future releases to take advantage of improvements we identify for default configurations. + * @param {MomentoLoggerFactory} [loggerFactory=defaultLoggerFactory] + * @returns {CacheConfiguration} + */ + static latest( + loggerFactory: MomentoLoggerFactory = defaultLoggerFactory + ): CacheConfiguration { + return Browser.v1(loggerFactory); + } + + /** + * Provides v1 recommended configuration for an in-browser environment. This configuration is guaranteed not + * to change in future releases of the Momento web SDK. + * @param {MomentoLoggerFactory} [loggerFactory=defaultLoggerFactory] + * @returns {CacheConfiguration} + */ + static v1( + loggerFactory: MomentoLoggerFactory = defaultLoggerFactory + ): CacheConfiguration { + const deadlineMillis = 5000; + const grpcConfig: GrpcConfiguration = new StaticGrpcConfiguration({ + deadlineMillis: deadlineMillis, + }); + const transportStrategy: TransportStrategy = new StaticTransportStrategy({ + grpcConfiguration: grpcConfig, + }); + return new Browser({ + loggerFactory: loggerFactory, + transportStrategy: transportStrategy, + }); + } +} diff --git a/packages/core/src/config/transport/grpc-configuration.ts b/packages/client-sdk-web/src/config/transport/grpc-configuration.ts similarity index 50% rename from packages/core/src/config/transport/grpc-configuration.ts rename to packages/client-sdk-web/src/config/transport/grpc-configuration.ts index 8abf4a68f..1ea225f6f 100644 --- a/packages/core/src/config/transport/grpc-configuration.ts +++ b/packages/client-sdk-web/src/config/transport/grpc-configuration.ts @@ -4,11 +4,6 @@ export interface GrpcConfigurationProps { * with a DeadlineExceeded error. */ deadlineMillis: number; - /** - * the maximum amount of memory, in megabytes, that a session is allowed to consume. Sessions that consume - * more than this amount will return a ResourceExhausted error. - */ - maxSessionMemoryMb: number; } /** @@ -29,17 +24,4 @@ export interface GrpcConfiguration { * @returns {GrpcConfiguration} a new GrpcConfiguration with the specified client-side deadline */ withDeadlineMillis(deadlineMillis: number): GrpcConfiguration; - - /** - * @returns {number} the maximum amount of memory, in megabytes, that a session is allowed to consume. Sessions that consume - * more than this amount will return a ResourceExhausted error. - */ - getMaxSessionMemoryMb(): number; - - /** - * Copy constructor for overriding the max session memory - * @param {number} maxSessionMemoryMb the desired maximum amount of memory, in megabytes, to allow a client session to consume - * @returns {GrpcConfiguration} a new GrpcConfiguration with the specified maximum memory - */ - withMaxSessionMemoryMb(maxSessionMemoryMb: number): GrpcConfiguration; } diff --git a/packages/core/src/config/transport/index.ts b/packages/client-sdk-web/src/config/transport/index.ts similarity index 100% rename from packages/core/src/config/transport/index.ts rename to packages/client-sdk-web/src/config/transport/index.ts diff --git a/packages/client-sdk-web/src/config/transport/transport-strategy.ts b/packages/client-sdk-web/src/config/transport/transport-strategy.ts new file mode 100644 index 000000000..5a9c497e4 --- /dev/null +++ b/packages/client-sdk-web/src/config/transport/transport-strategy.ts @@ -0,0 +1,72 @@ +import {GrpcConfiguration, GrpcConfigurationProps} from './grpc-configuration'; + +export interface TransportStrategy { + /** + * Configures the low-level gRPC settings for the Momento client's communication + * with the Momento server. + * @returns {GrpcConfiguration} + */ + getGrpcConfig(): GrpcConfiguration; + + /** + * Copy constructor for overriding the gRPC configuration + * @param {GrpcConfiguration} grpcConfig + * @returns {TransportStrategy} a new TransportStrategy with the specified gRPC config. + */ + withGrpcConfig(grpcConfig: GrpcConfiguration): TransportStrategy; + + /** + * Copy constructor to update the client-side timeout + * @param {number} clientTimeoutMillis + * @returns {TransportStrategy} a new TransportStrategy with the specified client timeout + */ + withClientTimeoutMillis(clientTimeoutMillis: number): TransportStrategy; +} + +export interface TransportStrategyProps { + /** + * low-level gRPC settings for communication with the Momento server + */ + grpcConfiguration: GrpcConfiguration; +} + +export class StaticGrpcConfiguration implements GrpcConfiguration { + private readonly deadlineMillis: number; + constructor(props: GrpcConfigurationProps) { + this.deadlineMillis = props.deadlineMillis; + } + + getDeadlineMillis(): number { + return this.deadlineMillis; + } + + withDeadlineMillis(deadlineMillis: number): StaticGrpcConfiguration { + return new StaticGrpcConfiguration({ + deadlineMillis: deadlineMillis, + }); + } +} + +export class StaticTransportStrategy implements TransportStrategy { + private readonly grpcConfig: GrpcConfiguration; + + constructor(props: TransportStrategyProps) { + this.grpcConfig = props.grpcConfiguration; + } + + getGrpcConfig(): GrpcConfiguration { + return this.grpcConfig; + } + + withGrpcConfig(grpcConfig: GrpcConfiguration): StaticTransportStrategy { + return new StaticTransportStrategy({ + grpcConfiguration: grpcConfig, + }); + } + + withClientTimeoutMillis(clientTimeout: number): StaticTransportStrategy { + return new StaticTransportStrategy({ + grpcConfiguration: this.grpcConfig.withDeadlineMillis(clientTimeout), + }); + } +} diff --git a/packages/client-sdk-web/src/index.ts b/packages/client-sdk-web/src/index.ts index dee38d525..daf12f96e 100644 --- a/packages/client-sdk-web/src/index.ts +++ b/packages/client-sdk-web/src/index.ts @@ -1,6 +1,7 @@ import {CacheClient} from './cache-client'; import {AuthClient} from './auth-client'; import {TopicClient} from './topic-client'; +import * as Configurations from './config/configurations'; // Cache Client Response Types import * as CacheGet from '@gomomento/sdk-core/dist/src/messages/responses/cache-get'; @@ -99,6 +100,7 @@ export { ItemType, SortedSetOrder, Configuration, + Configurations, CacheClient, AuthClient, CacheInfo, diff --git a/packages/client-sdk-web/src/internal/data-client.ts b/packages/client-sdk-web/src/internal/data-client.ts index a5dcabf65..6380a86aa 100644 --- a/packages/client-sdk-web/src/internal/data-client.ts +++ b/packages/client-sdk-web/src/internal/data-client.ts @@ -109,7 +109,11 @@ import { validateSortedSetScores, } from '@gomomento/sdk-core/dist/src/internal/utils'; import {normalizeSdkError} from '@gomomento/sdk-core/dist/src/errors'; -import {convertToB64String, createMetadata} from '../utils/web-client-utils'; +import { + convertToB64String, + createDeadline, + createMetadata, +} from '../utils/web-client-utils'; export interface DataClientProps { configuration: Configuration; @@ -131,6 +135,7 @@ export class DataClient< private readonly logger: MomentoLogger; private readonly authHeaders: {authorization: string}; private readonly defaultTtlSeconds: number; + private readonly deadlineMillis: number; /** * @param {DataClientProps} props @@ -147,6 +152,10 @@ export class DataClient< `Creating data client using endpoint: '${props.credentialProvider.getCacheEndpoint()}` ); + this.deadlineMillis = props.configuration + .getTransportStrategy() + .getGrpcConfig() + .getDeadlineMillis(); this.defaultTtlSeconds = props.defaultTtlSeconds; this.authHeaders = {authorization: props.credentialProvider.getAuthToken()}; this.clientWrapper = new cache.ScsClient( @@ -188,6 +197,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -270,6 +280,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -332,6 +343,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -388,6 +400,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -445,6 +458,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -487,6 +501,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { const theSet = resp.getFound(); @@ -543,6 +558,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, err => { if (err) { @@ -595,6 +611,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, err => { if (err) { @@ -669,6 +686,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -743,6 +761,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -814,6 +833,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp?.getMissing()) { @@ -901,6 +921,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -946,6 +967,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp?.getMissing()) { @@ -1001,6 +1023,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp?.getMissing()) { @@ -1054,6 +1077,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp?.getMissing()) { @@ -1128,6 +1152,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -1195,6 +1220,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -1247,6 +1273,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -1316,6 +1343,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -1386,6 +1414,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -1444,6 +1473,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp?.getMissing()) { @@ -1534,6 +1564,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { const found = resp?.getFound(); @@ -1597,6 +1628,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { const theDict = resp?.getFound(); @@ -1679,6 +1711,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -1739,6 +1772,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -1796,6 +1830,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -1885,6 +1920,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp && resp.getFound()) { @@ -2019,6 +2055,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -2123,6 +2160,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -2192,6 +2230,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -2276,6 +2315,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp?.getMissing()) { @@ -2360,6 +2400,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if ( @@ -2440,6 +2481,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { if (resp) { @@ -2502,6 +2544,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, err => { if (err) { @@ -2560,6 +2603,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, err => { if (err) { @@ -2601,6 +2645,7 @@ export class DataClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.deadlineMillis), }, (err, resp) => { const theType = resp.getFound(); diff --git a/packages/client-sdk-web/src/internal/pubsub-client.ts b/packages/client-sdk-web/src/internal/pubsub-client.ts index 19af1c9d2..221bdea5d 100644 --- a/packages/client-sdk-web/src/internal/pubsub-client.ts +++ b/packages/client-sdk-web/src/internal/pubsub-client.ts @@ -3,6 +3,7 @@ import * as cachepubsub_pb from '@gomomento/generated-types-webtext/dist/cachepu import {Configuration} from '../config/configuration'; import { CredentialProvider, + InvalidArgumentError, MomentoLogger, TopicItem, UnknownError, @@ -26,7 +27,11 @@ import { SendSubscribeOptions, PrepareSubscribeCallbackOptions, } from '@gomomento/sdk-core/dist/src/internal/clients/pubsub/AbstractPubsubClient'; -import {convertToB64String, createMetadata} from '../utils/web-client-utils'; +import { + convertToB64String, + createDeadline, + createMetadata, +} from '../utils/web-client-utils'; export class PubsubClient< REQ extends Request, @@ -35,8 +40,8 @@ export class PubsubClient< private readonly client: pubsub.PubsubClient; private readonly configuration: Configuration; protected readonly credentialProvider: CredentialProvider; - // private readonly unaryRequestTimeoutMs: number; - // private static readonly DEFAULT_REQUEST_TIMEOUT_MS: number = 5 * 1000; + private readonly requestTimeoutMs: number; + private static readonly DEFAULT_REQUEST_TIMEOUT_MS: number = 5 * 1000; protected readonly logger: MomentoLogger; private readonly authHeaders: {authorization: string}; private readonly unaryInterceptors: UnaryInterceptor[]; @@ -51,16 +56,13 @@ export class PubsubClient< this.credentialProvider = props.credentialProvider; this.logger = this.configuration.getLoggerFactory().getLogger(this); - // TODO: - // TODO: uncomment after Configuration plumbing is in place . . . - // TODO - // const grpcConfig = this.configuration - // .getTransportStrategy() - // .getGrpcConfig(); - // - // this.validateRequestTimeout(grpcConfig.getDeadlineMillis()); - // this.unaryRequestTimeoutMs = - // grpcConfig.getDeadlineMillis() || PubsubClient.DEFAULT_REQUEST_TIMEOUT_MS; + const grpcConfig = this.configuration + .getTransportStrategy() + .getGrpcConfig(); + + this.validateRequestTimeout(grpcConfig.getDeadlineMillis()); + this.requestTimeoutMs = + grpcConfig.getDeadlineMillis() || PubsubClient.DEFAULT_REQUEST_TIMEOUT_MS; this.logger.debug( `Creating topic client using endpoint: '${this.credentialProvider.getCacheEndpoint()}'` ); @@ -85,17 +87,14 @@ export class PubsubClient< return endpoint; } - // TODO: - // TODO: uncomment after Configuration plumbing is in place . . . - // TODO - // private validateRequestTimeout(timeout?: number) { - // this.logger.debug(`Request timeout ms: ${String(timeout)}`); - // if (timeout !== undefined && timeout <= 0) { - // throw new InvalidArgumentError( - // 'request timeout must be greater than zero.' - // ); - // } - // } + private validateRequestTimeout(timeout?: number) { + this.logger.debug(`Request timeout ms: ${String(timeout)}`); + if (timeout !== undefined && timeout <= 0) { + throw new InvalidArgumentError( + 'request timeout must be greater than zero.' + ); + } + } protected async sendPublish( cacheName: string, @@ -121,6 +120,7 @@ export class PubsubClient< { ...this.authHeaders, ...metadata, + ...createDeadline(this.requestTimeoutMs), }, (err, resp) => { if (resp) { @@ -256,8 +256,6 @@ export class PubsubClient< private initializeUnaryInterceptors( headers: Header[] - // configuration: Configuration, - // requestTimeoutMs: number ): UnaryInterceptor[] { return [ new HeaderInterceptorProvider( diff --git a/packages/client-sdk-web/src/utils/web-client-utils.ts b/packages/client-sdk-web/src/utils/web-client-utils.ts index 1dc9c46c2..9306d2f74 100644 --- a/packages/client-sdk-web/src/utils/web-client-utils.ts +++ b/packages/client-sdk-web/src/utils/web-client-utils.ts @@ -10,3 +10,8 @@ export function convertToB64String(v: string | Uint8Array): string { export function createMetadata(cacheName: string): {cache: string} { return {cache: cacheName}; } + +export function createDeadline(timeoutMillis: number): {deadline: string} { + const deadline = Date.now() + timeoutMillis; + return {deadline: deadline.toString()}; +} diff --git a/packages/client-sdk-web/test/integration/integration-setup.ts b/packages/client-sdk-web/test/integration/integration-setup.ts index 8d5491aa2..befd8f108 100644 --- a/packages/client-sdk-web/test/integration/integration-setup.ts +++ b/packages/client-sdk-web/test/integration/integration-setup.ts @@ -1,31 +1,36 @@ -import {AuthClient, CacheClient, TopicClient} from '../../src'; import { deleteCacheIfExists, testCacheName, } from '@gomomento/common-integration-tests'; import { + AuthClient, CreateCache, - CredentialProvider, + Configurations, DeleteCache, - NoopMomentoLoggerFactory, -} from '@gomomento/sdk-core'; + CacheClient, + CredentialProvider, + TopicClient, +} from '../../src'; import {ITopicClient} from '@gomomento/sdk-core/dist/src/internal/clients'; +import {CacheClientProps} from '../../src/cache-client-props'; const credsProvider = CredentialProvider.fromEnvironmentVariable({ environmentVariableName: 'TEST_AUTH_TOKEN', }); -function momentoClientForTesting() { - return new CacheClient({ - credentialProvider: credsProvider, - }); +export const IntegrationTestCacheClientProps: CacheClientProps = { + configuration: Configurations.Laptop.latest(), + credentialProvider: credsProvider, + defaultTtlSeconds: 1111, +}; + +function momentoClientForTesting(): CacheClient { + return new CacheClient(IntegrationTestCacheClientProps); } -function momentoTopicClientForTesting(): ITopicClient { +function momentoTopicClientForTesting(): TopicClient { return new TopicClient({ - configuration: { - getLoggerFactory: () => new NoopMomentoLoggerFactory(), - }, + configuration: Configurations.Laptop.latest(), credentialProvider: credsProvider, }); } diff --git a/packages/client-sdk-web/test/integration/ping.test.ts b/packages/client-sdk-web/test/integration/ping.test.ts index d5d3f4227..2c656da97 100644 --- a/packages/client-sdk-web/test/integration/ping.test.ts +++ b/packages/client-sdk-web/test/integration/ping.test.ts @@ -1,19 +1,19 @@ -import { - CredentialProvider, - MomentoLoggerFactory, - NoopMomentoLoggerFactory, -} from '@gomomento/sdk-core'; -import {CacheClient} from '../../src'; +import {CredentialProvider} from '@gomomento/sdk-core'; +import {CacheClient, Configurations} from '../../src'; import {PingClient} from '../../src/internal/ping-client'; import {expectWithMessage} from '@gomomento/common-integration-tests'; +import {CacheClientProps} from '../../src/cache-client-props'; describe('ping service', () => { it('ping should work', async () => { - const cacheClient = new CacheClient({ + const cacheClientProps: CacheClientProps = { + configuration: Configurations.Laptop.latest(), credentialProvider: CredentialProvider.fromEnvironmentVariable({ environmentVariableName: 'TEST_AUTH_TOKEN', }), - }); + defaultTtlSeconds: 1111, + }; + const cacheClient = new CacheClient(cacheClientProps); await cacheClient.ping(); }); it('should fail on bad URL', async () => { @@ -22,11 +22,7 @@ describe('ping service', () => { console.error = jest.fn(); const pingClient = new PingClient({ endpoint: 'bad.url', - configuration: { - getLoggerFactory(): MomentoLoggerFactory { - return new NoopMomentoLoggerFactory(); - }, - }, + configuration: Configurations.Laptop.latest(), }); try { await pingClient.ping(); diff --git a/packages/client-sdk-web/test/unit/config/transport/transport-strategy.test.ts b/packages/client-sdk-web/test/unit/config/transport/transport-strategy.test.ts new file mode 100644 index 000000000..2c5296def --- /dev/null +++ b/packages/client-sdk-web/test/unit/config/transport/transport-strategy.test.ts @@ -0,0 +1,53 @@ +import { + StaticGrpcConfiguration, + StaticTransportStrategy, +} from '../../../../src/config/transport'; + +describe('StaticGrpcConfiguration', () => { + const testDeadlineMillis = 90210; + const testGrpcConfiguration = new StaticGrpcConfiguration({ + deadlineMillis: testDeadlineMillis, + }); + + it('should support overriding deadline millis', () => { + const newDeadlineMillis = 42; + const configWithNewDeadline = + testGrpcConfiguration.withDeadlineMillis(newDeadlineMillis); + expect(configWithNewDeadline.getDeadlineMillis()).toEqual( + newDeadlineMillis + ); + }); +}); + +describe('StaticTransportStrategy', () => { + const testDeadlineMillis = 90210; + const testGrpcConfiguration = new StaticGrpcConfiguration({ + deadlineMillis: testDeadlineMillis, + }); + + const testTransportStrategy = new StaticTransportStrategy({ + grpcConfiguration: testGrpcConfiguration, + }); + + it('should support overriding grpc config', () => { + const newDeadlineMillis = 42; + const newGrpcConfig = new StaticGrpcConfiguration({ + deadlineMillis: newDeadlineMillis, + }); + const strategyWithNewGrpcConfig = + testTransportStrategy.withGrpcConfig(newGrpcConfig); + expect(strategyWithNewGrpcConfig.getGrpcConfig()).toEqual(newGrpcConfig); + }); + + it('should support overriding client timeout', () => { + const newClientTimeout = 42; + const expectedGrpcConfig = new StaticGrpcConfiguration({ + deadlineMillis: newClientTimeout, + }); + const strategyWithNewClientTimeout = + testTransportStrategy.withClientTimeoutMillis(newClientTimeout); + expect(strategyWithNewClientTimeout.getGrpcConfig()).toEqual( + expectedGrpcConfig + ); + }); +}); diff --git a/packages/core/src/config/transport/transport-strategy.ts b/packages/core/src/config/transport/transport-strategy.ts deleted file mode 100644 index 5c4d8167f..000000000 --- a/packages/core/src/config/transport/transport-strategy.ts +++ /dev/null @@ -1,134 +0,0 @@ -import {GrpcConfiguration, GrpcConfigurationProps} from './grpc-configuration'; - -export interface TransportStrategyProps { - /** - * low-level gRPC settings for communication with the Momento server - */ - grpcConfiguration: GrpcConfiguration; - /** - * The maximum duration for which a connection may remain idle before being replaced. This - * setting can be used to force re-connection of a client if it has been idle for too long. - * In environments such as AWS lambda, if the lambda is suspended for too long the connection - * may be closed by the load balancer, resulting in an error on the subsequent request. If - * this setting is set to a duration less than the load balancer timeout, we can ensure that - * the connection will be refreshed to avoid errors. - * @returns {number} - */ - maxIdleMillis: number; -} - -/** - * Configures the network options for communicating with the Momento service. - * @export - * @interface TransportStrategy - */ -export interface TransportStrategy { - /** - * Configures the low-level gRPC settings for the Momento client's communication - * with the Momento server. - * @returns {GrpcConfiguration} - */ - getGrpcConfig(): GrpcConfiguration; - - /** - * Copy constructor for overriding the gRPC configuration - * @param {GrpcConfiguration} grpcConfig - * @returns {TransportStrategy} a new TransportStrategy with the specified gRPC config. - */ - withGrpcConfig(grpcConfig: GrpcConfiguration): TransportStrategy; - - /** - * Copy constructor to update the client-side timeout - * @param {number} clientTimeoutMillis - * @returns {TransportStrategy} a new TransportStrategy with the specified client timeout - */ - withClientTimeoutMillis(clientTimeoutMillis: number): TransportStrategy; - - /** - * The maximum duration for which a connection may remain idle before being replaced. This - * setting can be used to force re-connection of a client if it has been idle for too long. - * In environments such as AWS lambda, if the lambda is suspended for too long the connection - * may be closed by the load balancer, resulting in an error on the subsequent request. If - * this setting is set to a duration less than the load balancer timeout, we can ensure that - * the connection will be refreshed to avoid errors. - * @returns {number} - */ - getMaxIdleMillis(): number; - - /** - * Copy constructor to update the max idle connection timeout. (See {getMaxIdleMillis}.) - * @param {number} maxIdleMillis - * @returns {TransportStrategy} a new TransportStrategy with the specified max idle connection timeout. - */ - withMaxIdleMillis(maxIdleMillis: number): TransportStrategy; -} - -export class StaticGrpcConfiguration implements GrpcConfiguration { - private readonly deadlineMillis: number; - private readonly maxSessionMemoryMb: number; - constructor(props: GrpcConfigurationProps) { - this.deadlineMillis = props.deadlineMillis; - this.maxSessionMemoryMb = props.maxSessionMemoryMb; - } - - getDeadlineMillis(): number { - return this.deadlineMillis; - } - - getMaxSessionMemoryMb(): number { - return this.maxSessionMemoryMb; - } - - withDeadlineMillis(deadlineMillis: number): StaticGrpcConfiguration { - return new StaticGrpcConfiguration({ - deadlineMillis: deadlineMillis, - maxSessionMemoryMb: this.maxSessionMemoryMb, - }); - } - - withMaxSessionMemoryMb(maxSessionMemoryMb: number): StaticGrpcConfiguration { - return new StaticGrpcConfiguration({ - deadlineMillis: this.deadlineMillis, - maxSessionMemoryMb: maxSessionMemoryMb, - }); - } -} - -export class StaticTransportStrategy implements TransportStrategy { - private readonly grpcConfig: GrpcConfiguration; - private readonly maxIdleMillis: number; - - constructor(props: TransportStrategyProps) { - this.grpcConfig = props.grpcConfiguration; - this.maxIdleMillis = props.maxIdleMillis; - } - - getGrpcConfig(): GrpcConfiguration { - return this.grpcConfig; - } - - withGrpcConfig(grpcConfig: GrpcConfiguration): StaticTransportStrategy { - return new StaticTransportStrategy({ - grpcConfiguration: grpcConfig, - maxIdleMillis: this.maxIdleMillis, - }); - } - - getMaxIdleMillis(): number { - return this.maxIdleMillis; - } - - withMaxIdleMillis(maxIdleMillis: number): TransportStrategy { - return new StaticTransportStrategy({ - grpcConfiguration: this.grpcConfig, - maxIdleMillis: maxIdleMillis, - }); - } - - withClientTimeoutMillis(clientTimeout: number): StaticTransportStrategy { - return new StaticTransportStrategy({ - grpcConfiguration: this.grpcConfig.withDeadlineMillis(clientTimeout), - maxIdleMillis: this.maxIdleMillis, - }); - } -}