diff --git a/packages/client-sdk-nodejs-compression-zstd/test/integration/integration-setup.ts b/packages/client-sdk-nodejs-compression-zstd/test/integration/integration-setup.ts index 80d51ca59..f37041b6d 100644 --- a/packages/client-sdk-nodejs-compression-zstd/test/integration/integration-setup.ts +++ b/packages/client-sdk-nodejs-compression-zstd/test/integration/integration-setup.ts @@ -16,7 +16,7 @@ export const deleteCacheIfExists = async ( ) => { const deleteResponse = await momento.deleteCache(cacheName); if (deleteResponse instanceof DeleteCache.Error) { - if (deleteResponse.errorCode() !== MomentoErrorCode.NOT_FOUND_ERROR) { + if (deleteResponse.errorCode() !== MomentoErrorCode.CACHE_NOT_FOUND_ERROR) { throw deleteResponse.innerException(); } } diff --git a/packages/client-sdk-nodejs-compression/test/integration/integration-setup.ts b/packages/client-sdk-nodejs-compression/test/integration/integration-setup.ts index 80d51ca59..f37041b6d 100644 --- a/packages/client-sdk-nodejs-compression/test/integration/integration-setup.ts +++ b/packages/client-sdk-nodejs-compression/test/integration/integration-setup.ts @@ -16,7 +16,7 @@ export const deleteCacheIfExists = async ( ) => { const deleteResponse = await momento.deleteCache(cacheName); if (deleteResponse instanceof DeleteCache.Error) { - if (deleteResponse.errorCode() !== MomentoErrorCode.NOT_FOUND_ERROR) { + if (deleteResponse.errorCode() !== MomentoErrorCode.CACHE_NOT_FOUND_ERROR) { throw deleteResponse.innerException(); } } diff --git a/packages/client-sdk-nodejs/package-lock.json b/packages/client-sdk-nodejs/package-lock.json index ec36d57a6..099def96e 100644 --- a/packages/client-sdk-nodejs/package-lock.json +++ b/packages/client-sdk-nodejs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { - "@gomomento/generated-types": "0.112.1", + "@gomomento/generated-types": "0.113.0", "@gomomento/sdk-core": "file:../core", "@grpc/grpc-js": "1.10.9", "@types/google-protobuf": "3.15.10", @@ -813,9 +813,9 @@ "link": true }, "node_modules/@gomomento/generated-types": { - "version": "0.112.1", - "resolved": "https://registry.npmjs.org/@gomomento/generated-types/-/generated-types-0.112.1.tgz", - "integrity": "sha512-le50ESGkRz6dUSqV0CmQXm6fIdnYJgj5c9iEM68xJ4nqqkkkXsSOPgCvRZQPRzdz3v+c3OR4/kS0zhAXEIL16w==", + "version": "0.113.0", + "resolved": "https://registry.npmjs.org/@gomomento/generated-types/-/generated-types-0.113.0.tgz", + "integrity": "sha512-7DJdcNWzCT5dpp1W+Vb77RZfamCt/SbtvoOcDtSDVU/wAsK7y7Yeo88DkgqiD2sCHv/u5X3wgLye51dlCsbgDw==", "dependencies": { "@grpc/grpc-js": "1.10.9", "google-protobuf": "3.21.2", @@ -1840,10 +1840,25 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk/node_modules/acorn": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, "engines": { "node": ">=0.4.0" } @@ -2365,9 +2380,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001633", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001633.tgz", - "integrity": "sha512-6sT0yf/z5jqf8tISAgpJDrmwOpLsrpnyCdD/lOZKvKkkJK4Dn0X5i7KF7THEZhOq+30bmhwBlNEaqPUiHiKtZg==", + "version": "1.0.30001636", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", + "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", "dev": true, "funding": [ { @@ -2749,9 +2764,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.802", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.802.tgz", - "integrity": "sha512-TnTMUATbgNdPXVSHsxvNVSG0uEd6cSZsANjm8c9HbvflZVVn1yTRcmVXYT1Ma95/ssB/Dcd30AHweH2TE+dNpA==", + "version": "1.4.807", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.807.tgz", + "integrity": "sha512-kSmJl2ZwhNf/bcIuCH/imtNOKlpkLDn2jqT5FJ+/0CXjhnFaOa9cOe9gHKKy71eM49izwuQjZhKk+lWQ1JxB7A==", "dev": true }, "node_modules/emittery": { @@ -4138,12 +4153,15 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6986,9 +7004,9 @@ } }, "node_modules/ts-node/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "dev": true, "bin": { "acorn": "bin/acorn" diff --git a/packages/client-sdk-nodejs/package.json b/packages/client-sdk-nodejs/package.json index 6a6d06008..1d6eb2aac 100644 --- a/packages/client-sdk-nodejs/package.json +++ b/packages/client-sdk-nodejs/package.json @@ -16,6 +16,7 @@ "prebuild": "eslint . --ext .ts", "test": "jest --testPathIgnorePatterns auth-client.test.ts --maxWorkers 1", "integration-test-auth": "jest auth-client.test.ts --maxWorkers 1", + "integration-test-store": "jest storage.test.ts --maxWorkers 1", "unit-test": "jest unit", "integration-test-leaderboard": "jest leaderboard --maxWorkers 1", "integration-test": "jest integration --testPathIgnorePatterns \"auth-client.test.ts|leaderboard.test.ts\" --maxWorkers 1", @@ -52,7 +53,7 @@ "uuid": "8.3.2" }, "dependencies": { - "@gomomento/generated-types": "0.112.1", + "@gomomento/generated-types": "0.113.0", "@gomomento/sdk-core": "file:../core", "@grpc/grpc-js": "1.10.9", "@types/google-protobuf": "3.15.10", diff --git a/packages/client-sdk-nodejs/src/config/storage-configuration.ts b/packages/client-sdk-nodejs/src/config/storage-configuration.ts new file mode 100644 index 000000000..459eb5849 --- /dev/null +++ b/packages/client-sdk-nodejs/src/config/storage-configuration.ts @@ -0,0 +1,82 @@ +import {MomentoLoggerFactory} from '../'; +import {StorageTransportStrategy} from './transport/storage'; + +/** + * Configuration options for Momento StorageClient + * + * @export + * @interface StorageConfiguration + */ +export interface StorageConfiguration { + /** + * @returns {MomentoLoggerFactory} the current configuration options for logging verbosity and format + */ + getLoggerFactory(): MomentoLoggerFactory; + + /** + * @returns {StorageTransportStrategy} the current configuration options for wire interactions with the Momento service + */ + getTransportStrategy(): StorageTransportStrategy; + + /** + * Convenience copy constructor that updates the client-side timeout setting in the TransportStrategy + * @param {number} clientTimeoutMillis + * @returns {StorageConfiguration} a new Configuration object with its TransportStrategy updated to use the specified client timeout + */ + withClientTimeoutMillis(clientTimeoutMillis: number): StorageConfiguration; + + /** + * Copy constructor for overriding TransportStrategy + * @param {StorageTransportStrategy} transportStrategy + * @returns {Configuration} a new Configuration object with the specified TransportStrategy + */ + withTransportStrategy( + transportStrategy: StorageTransportStrategy + ): StorageConfiguration; +} + +export interface StorageConfigurationProps { + /** + * Configures logging verbosity and format + */ + loggerFactory: MomentoLoggerFactory; + /** + * Configures low-level options for network interactions with the Momento service + */ + transportStrategy: StorageTransportStrategy; +} + +export class StorageClientConfiguration implements StorageConfiguration { + private readonly loggerFactory: MomentoLoggerFactory; + private readonly transportStrategy: StorageTransportStrategy; + + constructor(props: StorageConfigurationProps) { + this.loggerFactory = props.loggerFactory; + this.transportStrategy = props.transportStrategy; + } + + getLoggerFactory(): MomentoLoggerFactory { + return this.loggerFactory; + } + + getTransportStrategy(): StorageTransportStrategy { + return this.transportStrategy; + } + + withClientTimeoutMillis(clientTimeoutMillis: number): StorageConfiguration { + return new StorageClientConfiguration({ + loggerFactory: this.loggerFactory, + transportStrategy: + this.transportStrategy.withClientTimeoutMillis(clientTimeoutMillis), + }); + } + + withTransportStrategy( + transportStrategy: StorageTransportStrategy + ): StorageConfiguration { + return new StorageClientConfiguration({ + loggerFactory: this.loggerFactory, + transportStrategy: transportStrategy, + }); + } +} diff --git a/packages/client-sdk-nodejs/src/config/storage-configurations.ts b/packages/client-sdk-nodejs/src/config/storage-configurations.ts new file mode 100644 index 000000000..26763b3ae --- /dev/null +++ b/packages/client-sdk-nodejs/src/config/storage-configurations.ts @@ -0,0 +1,39 @@ +import {MomentoLoggerFactory} from '@gomomento/sdk-core'; +import {DefaultMomentoLoggerFactory} from './logging/default-momento-logger'; +import { + StorageClientConfiguration, + StorageConfiguration, +} from './storage-configuration'; +import { + StaticStorageGrpcConfiguration, + StaticStorageTransportStrategy, +} from './transport/storage'; + +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 StorageClientConfiguration { + /** + * 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 {StorageConfiguration} + */ + static latest( + loggerFactory: MomentoLoggerFactory = defaultLoggerFactory + ): StorageConfiguration { + return new StorageClientConfiguration({ + loggerFactory: loggerFactory, + transportStrategy: new StaticStorageTransportStrategy({ + grpcConfiguration: new StaticStorageGrpcConfiguration({ + deadlineMillis: 5000, + }), + }), + }); + } +} diff --git a/packages/client-sdk-nodejs/src/config/transport/storage/grpc-configuration.ts b/packages/client-sdk-nodejs/src/config/transport/storage/grpc-configuration.ts new file mode 100644 index 000000000..67ac8b455 --- /dev/null +++ b/packages/client-sdk-nodejs/src/config/transport/storage/grpc-configuration.ts @@ -0,0 +1,27 @@ +export interface StorageGrpcConfigurationProps { + /** + * number of milliseconds the client is willing to wait for an RPC to complete before it is terminated + * with a DeadlineExceeded error. + */ + deadlineMillis: number; +} + +/** + * Encapsulates gRPC configuration tunables. + * @export + * @interface StorageGrpcConfiguration + */ +export interface StorageGrpcConfiguration { + /** + * @returns {number} number of milliseconds the client is willing to wait for an RPC to complete before it is terminated + * with a DeadlineExceeded error. + */ + getDeadlineMillis(): number; + + /** + * Copy constructor for overriding the client-side deadline + * @param {number} deadlineMillis + * @returns {StorageGrpcConfiguration} a new StorageGrpcConfiguration with the specified client-side deadline + */ + withDeadlineMillis(deadlineMillis: number): StorageGrpcConfiguration; +} diff --git a/packages/client-sdk-nodejs/src/config/transport/storage/index.ts b/packages/client-sdk-nodejs/src/config/transport/storage/index.ts new file mode 100644 index 000000000..274180861 --- /dev/null +++ b/packages/client-sdk-nodejs/src/config/transport/storage/index.ts @@ -0,0 +1,2 @@ +export * from './grpc-configuration'; +export * from './transport-strategy'; diff --git a/packages/client-sdk-nodejs/src/config/transport/storage/transport-strategy.ts b/packages/client-sdk-nodejs/src/config/transport/storage/transport-strategy.ts new file mode 100644 index 000000000..e3c45f01a --- /dev/null +++ b/packages/client-sdk-nodejs/src/config/transport/storage/transport-strategy.ts @@ -0,0 +1,89 @@ +import { + StorageGrpcConfiguration, + StorageGrpcConfigurationProps, +} from './grpc-configuration'; + +export interface StorageTransportStrategy { + /** + * Configures the low-level gRPC settings for the Momento client's communication + * with the Momento server. + * @returns {StorageGrpcConfiguration} + */ + getGrpcConfig(): StorageGrpcConfiguration; + + /** + * Copy constructor for overriding the gRPC configuration + * @param {TopicGrpcConfiguration} grpcConfig + * @returns {TopicTransportStrategy} a new StorageTransportStrategy with the specified gRPC config. + */ + withGrpcConfig( + grpcConfig: StorageGrpcConfiguration + ): StorageTransportStrategy; + + /** + * Copy constructor to update the client-side timeout + * @param {number} clientTimeoutMillis + * @returns {StorageTransportStrategy} a new StorageTransportStrategy with the specified client timeout + */ + withClientTimeoutMillis( + clientTimeoutMillis: number + ): StorageTransportStrategy; +} + +export interface StorageTransportStrategyProps { + /** + * low-level gRPC settings for communication with the Momento server + */ + grpcConfiguration: StorageGrpcConfiguration; +} + +export class StaticStorageGrpcConfiguration + implements StorageGrpcConfiguration +{ + private readonly deadlineMillis: number; + + constructor(props: StorageGrpcConfigurationProps) { + this.deadlineMillis = props.deadlineMillis; + } + + getDeadlineMillis(): number { + return this.deadlineMillis; + } + + withDeadlineMillis(deadlineMillis: number): StorageGrpcConfiguration { + return new StaticStorageGrpcConfiguration({ + deadlineMillis: deadlineMillis, + }); + } +} + +export class StaticStorageTransportStrategy + implements StorageTransportStrategy +{ + private readonly grpcConfig: StorageGrpcConfiguration; + + constructor(props: StorageTransportStrategyProps) { + this.grpcConfig = props.grpcConfiguration; + } + + getGrpcConfig(): StorageGrpcConfiguration { + return this.grpcConfig; + } + + withGrpcConfig( + grpcConfig: StorageGrpcConfiguration + ): StorageTransportStrategy { + return new StaticStorageTransportStrategy({ + grpcConfiguration: grpcConfig, + }); + } + + withClientTimeoutMillis( + clientTimeoutMillis: number + ): StorageTransportStrategy { + return new StaticStorageTransportStrategy({ + grpcConfiguration: + this.grpcConfig.withDeadlineMillis(clientTimeoutMillis), + }); + } +} diff --git a/packages/client-sdk-nodejs/src/config/transport/topics/grpc-configuration.ts b/packages/client-sdk-nodejs/src/config/transport/topics/grpc-configuration.ts index f2c830ab8..ea253098b 100644 --- a/packages/client-sdk-nodejs/src/config/transport/topics/grpc-configuration.ts +++ b/packages/client-sdk-nodejs/src/config/transport/topics/grpc-configuration.ts @@ -1,6 +1,6 @@ export interface TopicGrpcConfigurationProps { /** - * The number of internal clients a cache client will create to communicate with Momento. More of them allows + * The number of internal clients a topic client will create to communicate with Momento. More of them allows * more concurrent requests, at the cost of more open connections and the latency of setting up each client. */ numClients?: number; @@ -13,7 +13,7 @@ export interface TopicGrpcConfigurationProps { */ export interface TopicGrpcConfiguration { /** - * @returns {number} the number of internal clients a cache client will create to communicate with Momento. More of + * @returns {number} the number of internal clients a topic client will create to communicate with Momento. More of * them will allow for more concurrent requests. */ getNumClients(): number; diff --git a/packages/client-sdk-nodejs/src/errors/cache-service-error-mapper.ts b/packages/client-sdk-nodejs/src/errors/cache-service-error-mapper.ts index 663865835..8cbe5b8bd 100644 --- a/packages/client-sdk-nodejs/src/errors/cache-service-error-mapper.ts +++ b/packages/client-sdk-nodejs/src/errors/cache-service-error-mapper.ts @@ -1,7 +1,6 @@ import {Status} from '@grpc/grpc-js/build/src/constants'; -import {ServiceError} from '@grpc/grpc-js'; +import {Metadata, ServiceError} from '@grpc/grpc-js'; import { - NotFoundError, InternalServerError, InvalidArgumentError, PermissionError, @@ -10,17 +9,25 @@ import { TimeoutError, AuthenticationError, LimitExceededError, - AlreadyExistsError, SdkError, UnknownServiceError, ServerUnavailableError, UnknownError, FailedPreconditionError, } from '../../src'; +import { + CacheNotFoundError, + StoreItemNotFoundError, + StoreNotFoundError, +} from '@gomomento/sdk-core/dist/src/errors'; import { ICacheServiceErrorMapper, ResolveOrRejectErrorOptions, } from '@gomomento/sdk-core/dist/src/errors/ICacheServiceErrorMapper'; +import { + CacheAlreadyExistsError, + StoreAlreadyExistsError, +} from '@gomomento/sdk-core'; export class CacheServiceErrorMapper implements ICacheServiceErrorMapper @@ -57,7 +64,7 @@ export class CacheServiceErrorMapper const errParams: [ string, number | undefined, - object | undefined, + Metadata | undefined, string | undefined ] = [ err?.message || 'Unable to process request', @@ -76,8 +83,26 @@ export class CacheServiceErrorMapper return new UnknownServiceError(...errParams); case Status.UNAVAILABLE: return new ServerUnavailableError(...errParams); - case Status.NOT_FOUND: - return new NotFoundError(...errParams); + case Status.NOT_FOUND: { + let errCause = errParams[2]?.get('err')?.[0]; + // TODO: Remove this once the error message is standardized on the server side + const errorMessage = errParams[0]?.toString(); + const isStoreNotFound = + errorMessage?.includes('Store with name:') && + errorMessage?.includes("doesn't exist"); + // If errCause is not already set to 'store_not_found', check for store_not_found error + if (!errCause && isStoreNotFound) { + errCause = 'store_not_found'; + } + switch (errCause) { + case 'element_not_found': + return new StoreItemNotFoundError(...errParams); + case 'store_not_found': + return new StoreNotFoundError(...errParams); + default: + return new CacheNotFoundError(...errParams); + } + } case Status.OUT_OF_RANGE: case Status.UNIMPLEMENTED: return new BadRequestError(...errParams); @@ -93,8 +118,24 @@ export class CacheServiceErrorMapper return new AuthenticationError(...errParams); case Status.RESOURCE_EXHAUSTED: return new LimitExceededError(...errParams); - case Status.ALREADY_EXISTS: - return new AlreadyExistsError(...errParams); + case Status.ALREADY_EXISTS: { + let errCause = errParams[2]?.get('err')?.[0]; + // TODO: Remove this once the error message is standardized on the server side + const errorMessage = errParams[0]?.toString(); + const isStoreAlreadyExists = + errorMessage?.includes('Store with name:') && + errorMessage?.includes('already exists'); + // If errCause is not already set to 'store_already_exists', check for store_already_exists error + if (!errCause && isStoreAlreadyExists) { + errCause = 'store_already_exists'; + } + switch (errCause) { + case 'store_already_exists': + return new StoreAlreadyExistsError(...errParams); + default: + return new CacheAlreadyExistsError(...errParams); + } + } default: return new UnknownError(...errParams); } diff --git a/packages/client-sdk-nodejs/src/index.ts b/packages/client-sdk-nodejs/src/index.ts index 805583923..72fdb333b 100644 --- a/packages/client-sdk-nodejs/src/index.ts +++ b/packages/client-sdk-nodejs/src/index.ts @@ -1,7 +1,9 @@ import {CacheClient, SimpleCacheClient} from './cache-client'; import {TopicClient} from './topic-client'; +import {PreviewStorageClient} from './preview-storage-client'; import * as Configurations from './config/configurations'; import * as TopicConfigurations from './config/topic-configurations'; +import * as StorageConfigurations from './config/storage-configurations'; import * as LeaderboardConfigurations from './config/leaderboard-configurations'; import * as BatchUtils from './batchutils/batch-functions'; import * as WebhookUtils from './webhookutils'; @@ -75,6 +77,17 @@ import * as TopicPublish from '@gomomento/sdk-core/dist/src/messages/responses/t import * as TopicSubscribe from '@gomomento/sdk-core/dist/src/messages/responses/topic-subscribe'; import {TopicItem} from '@gomomento/sdk-core/dist/src/messages/responses/topic-item'; +// Storage Response Types +import { + StorageDelete, + StoragePut, + StorageGet, + CreateStore, + DeleteStore, + ListStores, +} from '@gomomento/sdk-core/dist/src/messages/responses/storage'; +import {StoreInfo} from '@gomomento/sdk-core/dist/src/messages/store-info'; + // AuthClient Response Types import {AuthClient} from './auth-client'; import * as GenerateApiKey from '@gomomento/sdk-core/dist/src/messages/responses/generate-api-key'; @@ -100,7 +113,8 @@ import { EnvMomentoTokenProvider, MomentoErrorCode, SdkError, - AlreadyExistsError, + CacheAlreadyExistsError, + StoreAlreadyExistsError, AuthenticationError, CancelledError, FailedPreconditionError, @@ -112,7 +126,9 @@ import { TimeoutError, BadRequestError, PermissionError, - NotFoundError, + CacheNotFoundError, + StoreNotFoundError, + StoreItemNotFoundError, UnknownError, MomentoLogger, MomentoLoggerFactory, @@ -155,6 +171,7 @@ import { WebhookDestinationType, ReadConcern, CompressionLevel, + IStorageClient, } from '@gomomento/sdk-core'; import {Configuration, CacheConfiguration} from './config/configuration'; @@ -167,6 +184,10 @@ import { LeaderboardClientConfiguration, } from './config/leaderboard-configuration'; import {PreviewLeaderboardClient} from './preview-leaderboard-client'; +import { + StorageConfiguration, + StorageClientConfiguration, +} from './config/storage-configuration'; export { DefaultMomentoLoggerFactory, @@ -215,6 +236,18 @@ export { TopicGrpcConfigurationProps, } from './config/transport/topics/grpc-configuration'; +export { + StaticStorageGrpcConfiguration, + StaticStorageTransportStrategy, + StorageTransportStrategy, + StorageTransportStrategyProps, +} from './config/transport/storage/transport-strategy'; + +export { + StorageGrpcConfiguration, + StorageGrpcConfigurationProps, +} from './config/transport/storage/grpc-configuration'; + export { Middleware, MiddlewareRequestHandler, @@ -347,6 +380,19 @@ export { TopicPublish, TopicSubscribe, SubscribeCallOptions, + // Storage + StorageConfigurations, + StorageConfiguration, + StorageClientConfiguration, + StoragePut, + StorageGet, + StorageDelete, + CreateStore, + DeleteStore, + ListStores, + StoreInfo, + PreviewStorageClient, + IStorageClient, // Webhooks PostUrlWebhookDestination, Webhook, @@ -383,7 +429,8 @@ export { // Errors MomentoErrorCode, SdkError, - AlreadyExistsError, + CacheAlreadyExistsError, + StoreAlreadyExistsError, AuthenticationError, CancelledError, FailedPreconditionError, @@ -395,7 +442,9 @@ export { TimeoutError, BadRequestError, PermissionError, - NotFoundError, + CacheNotFoundError, + StoreNotFoundError, + StoreItemNotFoundError, UnknownError, // Logging MomentoLogger, diff --git a/packages/client-sdk-nodejs/src/internal/cache-control-client.ts b/packages/client-sdk-nodejs/src/internal/cache-control-client.ts index ada619ede..3cd715479 100644 --- a/packages/client-sdk-nodejs/src/internal/cache-control-client.ts +++ b/packages/client-sdk-nodejs/src/internal/cache-control-client.ts @@ -2,7 +2,6 @@ import {control} from '@gomomento/generated-types'; import grpcControl = control.control_client; import {Header, HeaderInterceptorProvider} from './grpc/headers-interceptor'; import {ClientTimeoutInterceptor} from './grpc/client-timeout-interceptor'; -import {Status} from '@grpc/grpc-js/build/src/constants'; import {CacheServiceErrorMapper} from '../errors/cache-service-error-mapper'; import {ChannelCredentials, Interceptor} from '@grpc/grpc-js'; import { @@ -13,6 +12,7 @@ import { CredentialProvider, MomentoLogger, CacheInfo, + MomentoErrorCode, } from '..'; import {version} from '../../package.json'; import {IdleGrpcClientWrapper} from './grpc/idle-grpc-client-wrapper'; @@ -96,7 +96,11 @@ export class CacheControlClient { {interceptors: this.interceptors}, (err, _resp) => { if (err) { - if (err.code === Status.ALREADY_EXISTS) { + const sdkError = this.cacheServiceErrorMapper.convertError(err); + if ( + sdkError.errorCode() === + MomentoErrorCode.CACHE_ALREADY_EXISTS_ERROR + ) { resolve(new CreateCache.AlreadyExists()); } else { this.cacheServiceErrorMapper.resolveOrRejectError({ diff --git a/packages/client-sdk-nodejs/src/internal/storage-client-props-with-config.ts b/packages/client-sdk-nodejs/src/internal/storage-client-props-with-config.ts new file mode 100644 index 000000000..b041cc77f --- /dev/null +++ b/packages/client-sdk-nodejs/src/internal/storage-client-props-with-config.ts @@ -0,0 +1,6 @@ +import {StorageConfiguration} from '../config/storage-configuration'; +import {StorageClientProps} from '../storage-client-props'; + +export interface StorageClientPropsWithConfig extends StorageClientProps { + configuration: StorageConfiguration; +} diff --git a/packages/client-sdk-nodejs/src/internal/storage-control-client.ts b/packages/client-sdk-nodejs/src/internal/storage-control-client.ts new file mode 100644 index 000000000..3f2c41551 --- /dev/null +++ b/packages/client-sdk-nodejs/src/internal/storage-control-client.ts @@ -0,0 +1,151 @@ +import {control} from '@gomomento/generated-types'; +import grpcControl = control.control_client; +import {Header, HeaderInterceptorProvider} from './grpc/headers-interceptor'; +import {ClientTimeoutInterceptor} from './grpc/client-timeout-interceptor'; +import {CacheServiceErrorMapper} from '../errors/cache-service-error-mapper'; +import {ChannelCredentials, Interceptor} from '@grpc/grpc-js'; +import {MomentoLogger, StoreInfo, ListStores, MomentoErrorCode} from '..'; +import {version} from '../../package.json'; +import {validateStoreName} from '@gomomento/sdk-core/dist/src/internal/utils'; +import {CreateStore, DeleteStore} from '@gomomento/sdk-core'; +import {StorageClientPropsWithConfig} from './storage-client-props-with-config'; + +export class StorageControlClient { + private readonly clientWrapper: grpcControl.ScsControlClient; + private readonly interceptors: Interceptor[]; + private static readonly REQUEST_TIMEOUT_MS: number = 60 * 1000; + private readonly logger: MomentoLogger; + private readonly cacheServiceErrorMapper: CacheServiceErrorMapper; + + /** + * @param {StorageClientProps} props + */ + constructor(props: StorageClientPropsWithConfig) { + this.logger = props.configuration.getLoggerFactory().getLogger(this); + this.cacheServiceErrorMapper = new CacheServiceErrorMapper(false); + const headers = [ + new Header('Authorization', props.credentialProvider.getAuthToken()), + new Header('Agent', `nodejs:store:${version}`), + ]; + this.interceptors = [ + new HeaderInterceptorProvider(headers).createHeadersInterceptor(), + ClientTimeoutInterceptor(StorageControlClient.REQUEST_TIMEOUT_MS), + ]; + this.logger.debug( + `Creating storage control client using endpoint: '${props.credentialProvider.getControlEndpoint()}` + ); + + this.clientWrapper = new grpcControl.ScsControlClient( + props.credentialProvider.getControlEndpoint(), + props.credentialProvider.isControlEndpointSecure() + ? ChannelCredentials.createSsl() + : ChannelCredentials.createInsecure() + ); + } + close() { + this.logger.debug('Closing storage control client'); + this.clientWrapper.close(); + } + + public async createStore(name: string): Promise { + try { + validateStoreName(name); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new CreateStore.Error(err) + ); + } + this.logger.debug(`Creating store: ${name}`); + const request = new grpcControl._CreateStoreRequest({ + store_name: name, + }); + return await new Promise((resolve, reject) => { + this.clientWrapper.CreateStore( + request, + {interceptors: this.interceptors}, + (err, _resp) => { + if (err) { + const sdkError = this.cacheServiceErrorMapper.convertError(err); + if ( + sdkError.errorCode() === + MomentoErrorCode.STORE_ALREADY_EXISTS_ERROR + ) { + resolve(new CreateStore.AlreadyExists()); + } else { + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new CreateStore.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } + } else { + resolve(new CreateStore.Success()); + } + } + ); + }); + } + + public async deleteStore(name: string): Promise { + try { + validateStoreName(name); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new DeleteStore.Error(err) + ); + } + const request = new grpcControl._DeleteStoreRequest({ + store_name: name, + }); + this.logger.debug(`Deleting store: ${name}`); + return await new Promise((resolve, reject) => { + this.clientWrapper.DeleteStore( + request, + {interceptors: this.interceptors}, + (err, _resp) => { + if (err) { + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new DeleteStore.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } else { + resolve(new DeleteStore.Success()); + } + } + ); + }); + } + + public async listStores(): Promise { + const request = new grpcControl._ListStoresRequest(); + request.next_token = ''; + this.logger.debug("Issuing 'listStores' request"); + return await new Promise((resolve, reject) => { + this.clientWrapper.ListStores( + request, + {interceptors: this.interceptors}, + (err, resp) => { + if (err || !resp) { + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new ListStores.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } else { + const stores = resp.store.map(store => { + const storeName = store.store_name; + return new StoreInfo(storeName); + }); + resolve(new ListStores.Success(stores)); + } + } + ); + }); + } +} diff --git a/packages/client-sdk-nodejs/src/internal/storage-data-client.ts b/packages/client-sdk-nodejs/src/internal/storage-data-client.ts new file mode 100644 index 000000000..e84c68dc6 --- /dev/null +++ b/packages/client-sdk-nodejs/src/internal/storage-data-client.ts @@ -0,0 +1,345 @@ +import { + CredentialProvider, + InvalidArgumentError, + MomentoErrorCode, + MomentoLogger, + MomentoLoggerFactory, + StorageDelete, + StorageGet, + StoragePut, + UnknownError, +} from '@gomomento/sdk-core'; +import {validateStoreName} from '@gomomento/sdk-core/dist/src/internal/utils'; +import {store} from '@gomomento/generated-types/dist/store'; +import {Header, HeaderInterceptorProvider} from './grpc/headers-interceptor'; +import {ClientTimeoutInterceptor} from './grpc/client-timeout-interceptor'; +import { + ChannelCredentials, + Interceptor, + Metadata, + ServiceError, +} from '@grpc/grpc-js'; +import {version} from '../../package.json'; +import {grpcChannelOptionsFromGrpcConfig} from './grpc/grpc-channel-options'; +import {IStorageDataClient} from '@gomomento/sdk-core/dist/src/internal/clients'; +import {StorageConfiguration} from '../config/storage-configuration'; +import {StorageClientPropsWithConfig} from './storage-client-props-with-config'; +import {StaticGrpcConfiguration} from '../config/transport/cache'; +import {CacheServiceErrorMapper} from '../errors/cache-service-error-mapper'; + +export class StorageDataClient implements IStorageDataClient { + private readonly configuration: StorageConfiguration; + private readonly credentialProvider: CredentialProvider; + private readonly cacheServiceErrorMapper: CacheServiceErrorMapper; + private readonly logger: MomentoLogger; + private readonly requestTimeoutMs: number; + private readonly client: store.StoreClient; + private readonly interceptors: Interceptor[]; + private static readonly DEFAULT_MAX_SESSION_MEMORY_MB: number = 256; + + /** + * @param {StorageClientPropsWithConfig} props + */ + constructor(props: StorageClientPropsWithConfig) { + this.configuration = props.configuration; + this.credentialProvider = props.credentialProvider; + this.cacheServiceErrorMapper = new CacheServiceErrorMapper(false); + this.logger = this.configuration.getLoggerFactory().getLogger(this); + this.requestTimeoutMs = this.configuration + .getTransportStrategy() + .getGrpcConfig() + .getDeadlineMillis(); + this.validateRequestTimeout(this.requestTimeoutMs); + this.logger.debug( + `Creating leaderboard client using endpoint: '${this.credentialProvider.getStorageEndpoint()}'` + ); + + // NOTE: This is hard-coded for now but we may want to expose it via StorageConfiguration in the + // future, as we do with some of the other clients. + const grpcConfig = new StaticGrpcConfiguration({ + deadlineMillis: this.configuration + .getTransportStrategy() + .getGrpcConfig() + .getDeadlineMillis(), + maxSessionMemoryMb: StorageDataClient.DEFAULT_MAX_SESSION_MEMORY_MB, + }); + const channelOptions = grpcChannelOptionsFromGrpcConfig(grpcConfig); + + this.client = new store.StoreClient( + this.credentialProvider.getStorageEndpoint(), + this.credentialProvider.isStorageEndpointSecure() + ? ChannelCredentials.createSsl() + : ChannelCredentials.createInsecure(), + channelOptions + ); + this.interceptors = this.initializeInterceptors( + this.configuration.getLoggerFactory() + ); + } + + close() { + this.logger.debug('Closing storage data clients'); + this.client.close(); + } + + 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 validateStoreNameOrThrowError(storeName: string) { + try { + validateStoreName(storeName); + return; + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new StoragePut.Error(err) + ); + } + } + + private initializeInterceptors( + _loggerFactory: MomentoLoggerFactory + ): Interceptor[] { + const headers = [ + new Header('Authorization', this.credentialProvider.getAuthToken()), + new Header('Agent', `nodejs:store:${version}`), + ]; + return [ + new HeaderInterceptorProvider(headers).createHeadersInterceptor(), + ClientTimeoutInterceptor(this.requestTimeoutMs), + ]; + } + + private createMetadata(storeName: string): Metadata { + const metadata = new Metadata(); + metadata.set('store', storeName); + return metadata; + } + + public async get( + storeName: string, + key: string + ): Promise { + try { + validateStoreName(storeName); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new StorageGet.Error(err) + ); + } + this.logger.trace( + `Issuing 'get' request; store: ${storeName}, key: ${key}` + ); + return await this.sendGet(storeName, key); + } + + private async sendGet( + storeName: string, + key: string + ): Promise { + const request = new store._StoreGetRequest({ + key: key, + }); + const metadata = this.createMetadata(storeName); + return await new Promise((resolve, reject) => { + this.client.Get( + request, + metadata, + { + interceptors: this.interceptors, + }, + (err: ServiceError | null, resp) => { + const value = resp?.value?.value; + if (value) { + switch (value) { + case 'double_value': { + return resolve( + StorageGet.Success.ofDouble(resp.value.double_value) + ); + } + case 'string_value': { + return resolve( + StorageGet.Success.ofString(resp.value.string_value) + ); + } + case 'bytes_value': { + return resolve( + StorageGet.Success.ofBytes(resp.value.bytes_value) + ); + } + case 'integer_value': { + return resolve( + StorageGet.Success.ofInt(resp.value.integer_value) + ); + } + case 'none': { + return resolve( + new StorageGet.Error( + new UnknownError( + 'StorageGet responded with an unknown result' + ) + ) + ); + } + } + } else { + const sdkError = this.cacheServiceErrorMapper.convertError(err); + if ( + sdkError.errorCode() === + MomentoErrorCode.STORE_ITEM_NOT_FOUND_ERROR + ) { + return resolve(new StorageGet.Success(undefined)); + } + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new StorageGet.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } + } + ); + }); + } + + public async putInt( + storeName: string, + key: string, + value: number + ): Promise { + this.validateStoreNameOrThrowError(storeName); + this.logger.trace( + `Issuing 'put' request; store: ${storeName}, key: ${key}` + ); + const storeValue = new store._StoreValue({integer_value: value}); + return await this.sendPut(storeName, key, storeValue); + } + + public async putDouble( + storeName: string, + key: string, + value: number + ): Promise { + this.validateStoreNameOrThrowError(storeName); + this.logger.trace( + `Issuing 'put' request; store: ${storeName}, key: ${key}` + ); + const storeValue = new store._StoreValue({double_value: value}); + return await this.sendPut(storeName, key, storeValue); + } + + public async putString( + storeName: string, + key: string, + value: string + ): Promise { + this.validateStoreNameOrThrowError(storeName); + this.logger.trace( + `Issuing 'put' request; store: ${storeName}, key: ${key}` + ); + const storeValue = new store._StoreValue({string_value: value}); + return await this.sendPut(storeName, key, storeValue); + } + + public async putBytes( + storeName: string, + key: string, + value: Uint8Array + ): Promise { + this.validateStoreNameOrThrowError(storeName); + this.logger.trace( + `Issuing 'put' request; store: ${storeName}, key: ${key}` + ); + const storeValue = new store._StoreValue({bytes_value: value}); + return await this.sendPut(storeName, key, storeValue); + } + + private async sendPut( + storeName: string, + key: string, + storeValue: store._StoreValue + ): Promise { + const request = new store._StorePutRequest({ + key: key, + value: storeValue, + }); + const metadata = this.createMetadata(storeName); + return await new Promise((resolve, reject) => { + this.client.Put( + request, + metadata, + { + interceptors: this.interceptors, + }, + (err: ServiceError | null, resp) => { + if (resp) { + resolve(new StoragePut.Success()); + } else { + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new StoragePut.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } + } + ); + }); + } + + public async delete( + storeName: string, + key: string + ): Promise { + try { + validateStoreName(storeName); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new StorageDelete.Error(err) + ); + } + this.logger.trace( + `Issuing 'delete' request; store: ${storeName}, key: ${key}` + ); + return await this.sendDelete(storeName, key); + } + + private async sendDelete( + storeName: string, + key: string + ): Promise { + const request = new store._StoreDeleteRequest({ + key: key, + }); + const metadata = this.createMetadata(storeName); + return await new Promise((resolve, reject) => { + this.client.Delete( + request, + metadata, + { + interceptors: this.interceptors, + }, + (err: ServiceError | null, resp) => { + if (resp) { + resolve(new StorageDelete.Success()); + } else { + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new StorageDelete.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } + } + ); + }); + } +} diff --git a/packages/client-sdk-nodejs/src/preview-storage-client.ts b/packages/client-sdk-nodejs/src/preview-storage-client.ts new file mode 100644 index 000000000..87cec74b8 --- /dev/null +++ b/packages/client-sdk-nodejs/src/preview-storage-client.ts @@ -0,0 +1,65 @@ +import { + AbstractStorageClient, + IStorageControlClient, + IStorageDataClient, +} from '@gomomento/sdk-core/dist/src/internal/clients'; +import {IStorageClient} from '@gomomento/sdk-core/dist/src/clients/IStorageClient'; +import {StorageClientProps} from './storage-client-props'; +import {StorageClientPropsWithConfig} from './internal/storage-client-props-with-config'; +import {StorageControlClient} from './internal/storage-control-client'; +import {StorageDataClient} from './internal/storage-data-client'; +import {StorageConfiguration} from './config/storage-configuration'; +import {StorageConfigurations} from './index'; + +/** + * A client for interacting with the Momento Storage service. + * Warning: This client is in preview and may change in future releases. + */ +export class PreviewStorageClient + extends AbstractStorageClient + implements IStorageClient +{ + constructor(props: StorageClientProps) { + const configuration = + props.configuration ?? getDefaultStorageConfiguration(); + const propsWithConfiguration: StorageClientPropsWithConfig = { + ...props, + configuration, + }; + + const controlClient: IStorageControlClient = createControlClient( + propsWithConfiguration + ); + const dataClient: IStorageDataClient = createDataClient( + propsWithConfiguration + ); + super([dataClient], controlClient); + } + + close(): void { + this.dataClients.forEach(client => client.close()); + this.controlClient.close(); + } +} + +function createControlClient( + props: StorageClientPropsWithConfig +): IStorageControlClient { + return new StorageControlClient({ + configuration: props.configuration, + credentialProvider: props.credentialProvider, + }); +} + +function createDataClient( + props: StorageClientPropsWithConfig +): IStorageDataClient { + return new StorageDataClient({ + configuration: props.configuration, + credentialProvider: props.credentialProvider, + }); +} + +function getDefaultStorageConfiguration(): StorageConfiguration { + return StorageConfigurations.Laptop.latest(); +} diff --git a/packages/client-sdk-nodejs/src/storage-client-props.ts b/packages/client-sdk-nodejs/src/storage-client-props.ts new file mode 100644 index 000000000..fb4753c96 --- /dev/null +++ b/packages/client-sdk-nodejs/src/storage-client-props.ts @@ -0,0 +1,7 @@ +import {CredentialProvider} from '@gomomento/sdk-core'; +import {StorageConfiguration} from './config/storage-configuration'; + +export interface StorageClientProps { + credentialProvider: CredentialProvider; + configuration?: StorageConfiguration; +} diff --git a/packages/client-sdk-nodejs/test/integration/integration-setup.ts b/packages/client-sdk-nodejs/test/integration/integration-setup.ts index a3eeb8150..a393d0d9f 100644 --- a/packages/client-sdk-nodejs/test/integration/integration-setup.ts +++ b/packages/client-sdk-nodejs/test/integration/integration-setup.ts @@ -11,6 +11,8 @@ import { TopicClient, PreviewLeaderboardClient, LeaderboardConfigurations, + PreviewStorageClient, + StorageConfigurations, } from '../../src'; import {ICacheClient} from '@gomomento/sdk-core/dist/src/clients/ICacheClient'; import {ITopicClient} from '@gomomento/sdk-core/dist/src/clients/ITopicClient'; @@ -23,7 +25,7 @@ export const deleteCacheIfExists = async ( ) => { const deleteResponse = await momento.deleteCache(cacheName); if (deleteResponse instanceof DeleteCache.Error) { - if (deleteResponse.errorCode() !== MomentoErrorCode.NOT_FOUND_ERROR) { + if (deleteResponse.errorCode() !== MomentoErrorCode.CACHE_NOT_FOUND_ERROR) { throw deleteResponse.innerException(); } } @@ -74,6 +76,10 @@ function sessionCredsProvider(): CredentialProvider { endpoint: credsProvider().getTokenEndpoint(), secureConnection: credsProvider().isTokenEndpointSecure(), }, + storageEndpoint: { + endpoint: credsProvider().getStorageEndpoint(), + secureConnection: credsProvider().isStorageEndpointSecure(), + }, }, }); } @@ -131,6 +137,13 @@ function momentoTopicClientForTesting(): TopicClient { }); } +function momentoStorageClientForTesting(): PreviewStorageClient { + return new PreviewStorageClient({ + configuration: StorageConfigurations.Laptop.latest(), + credentialProvider: integrationTestCacheClientProps().credentialProvider, + }); +} + function momentoTopicClientWithThrowOnErrorsForTesting(): TopicClient { return new TopicClient({ configuration: @@ -222,6 +235,18 @@ export function SetupTopicIntegrationTest(): { }; } +export function SetupStorageIntegrationTest(): { + storageClient: PreviewStorageClient; + integrationTestStoreName: string; +} { + const {integrationTestCacheName} = SetupIntegrationTest(); + const storageClient = momentoStorageClientForTesting(); + return { + storageClient, + integrationTestStoreName: integrationTestCacheName, + }; +} + export function SetupLeaderboardIntegrationTest(): { leaderboardClient: PreviewLeaderboardClient; leaderboardClientWithThrowOnErrors: PreviewLeaderboardClient; diff --git a/packages/client-sdk-nodejs/test/integration/shared/storage.test.ts b/packages/client-sdk-nodejs/test/integration/shared/storage.test.ts new file mode 100644 index 000000000..90db8082c --- /dev/null +++ b/packages/client-sdk-nodejs/test/integration/shared/storage.test.ts @@ -0,0 +1,5 @@ +import {runStorageServiceTests} from '@gomomento/common-integration-tests'; +import {SetupStorageIntegrationTest} from '../integration-setup'; + +const {storageClient, integrationTestStoreName} = SetupStorageIntegrationTest(); +runStorageServiceTests(storageClient, integrationTestStoreName); diff --git a/packages/client-sdk-nodejs/test/unit/cache-service-error-mapper.test.ts b/packages/client-sdk-nodejs/test/unit/cache-service-error-mapper.test.ts index 68547cba6..777032d71 100644 --- a/packages/client-sdk-nodejs/test/unit/cache-service-error-mapper.test.ts +++ b/packages/client-sdk-nodejs/test/unit/cache-service-error-mapper.test.ts @@ -2,7 +2,6 @@ import {Status} from '@grpc/grpc-js/build/src/constants'; import {CacheServiceErrorMapper} from '../../src/errors/cache-service-error-mapper'; import {Metadata, ServiceError} from '@grpc/grpc-js'; import { - AlreadyExistsError, AuthenticationError, BadRequestError, CancelledError, @@ -10,13 +9,13 @@ import { InternalServerError, InvalidArgumentError, LimitExceededError, - NotFoundError, PermissionError, SdkError, ServerUnavailableError, TimeoutError, UnknownServiceError, } from '../../src'; +import {CacheAlreadyExistsError, CacheNotFoundError} from '@gomomento/sdk-core'; const generateServiceError = (status: Status): ServiceError => { return { @@ -92,7 +91,7 @@ describe('CacheServiceErrorMapper', () => { expect(resolved).toBeInstanceOf(BadRequestError); }); }); - it('should return cache not found error when grpc status is NOT_FOUND', () => { + xit('should return cache not found error when grpc status is NOT_FOUND', () => { const serviceError = generateServiceError(Status.NOT_FOUND); cacheServiceErrorMapper.resolveOrRejectError({ err: serviceError, @@ -100,7 +99,7 @@ describe('CacheServiceErrorMapper', () => { resolveFn: resolveFn, rejectFn: rejectFn, }); - expect(resolved).toBeInstanceOf(NotFoundError); + expect(resolved).toBeInstanceOf(CacheNotFoundError); }); it('should return unavailable error when grpc status is UNAVAILABLE', () => { const serviceError = generateServiceError(Status.UNAVAILABLE); @@ -178,7 +177,7 @@ describe('CacheServiceErrorMapper', () => { }); expect(resolved).toBeInstanceOf(LimitExceededError); }); - it('should return already exists error when grpc status is ALREADY_EXISTS', () => { + xit('should return already exists error when grpc status is ALREADY_EXISTS', () => { const serviceError = generateServiceError(Status.ALREADY_EXISTS); cacheServiceErrorMapper.resolveOrRejectError({ err: serviceError, @@ -186,7 +185,7 @@ describe('CacheServiceErrorMapper', () => { resolveFn: resolveFn, rejectFn: rejectFn, }); - expect(resolved).toBeInstanceOf(AlreadyExistsError); + expect(resolved).toBeInstanceOf(CacheAlreadyExistsError); }); describe('when throwOnErrors is true', () => { diff --git a/packages/client-sdk-web/package-lock.json b/packages/client-sdk-web/package-lock.json index 25807588e..3675cf103 100644 --- a/packages/client-sdk-web/package-lock.json +++ b/packages/client-sdk-web/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.1", "license": "Apache-2.0", "dependencies": { - "@gomomento/generated-types-webtext": "0.107.4", + "@gomomento/generated-types-webtext": "0.113.0", "@gomomento/sdk-core": "file:../core", "@types/google-protobuf": "3.15.6", "google-protobuf": "3.21.2", @@ -45,7 +45,6 @@ } }, "../common-integration-tests": { - "name": "@gomomento/common-integration-tests", "version": "0.0.1", "dev": true, "license": "Apache-2.0", @@ -77,7 +76,6 @@ } }, "../core": { - "name": "@gomomento/sdk-core", "version": "0.0.1", "license": "Apache-2.0", "dependencies": { @@ -129,30 +127,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", - "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.24.5", - "@babel/helpers": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -168,12 +166,12 @@ } }, "node_modules/@babel/core/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -190,12 +188,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.24.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -205,13 +203,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -230,62 +228,66 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -295,86 +297,86 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dev": true, "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -455,9 +457,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -527,12 +529,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -629,12 +631,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", - "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -644,26 +646,26 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -671,19 +673,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -692,12 +694,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -714,13 +716,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -771,9 +773,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -813,9 +815,9 @@ "link": true }, "node_modules/@gomomento/generated-types-webtext": { - "version": "0.107.4", - "resolved": "https://registry.npmjs.org/@gomomento/generated-types-webtext/-/generated-types-webtext-0.107.4.tgz", - "integrity": "sha512-roY251w2SnlcZtDZEJdcHe91RcI04gdPdP5gk0mDMjTcW8Q2ZIRxVvbNXVMzunTgokhSdFXm/ivF5iskc71AVw==", + "version": "0.113.0", + "resolved": "https://registry.npmjs.org/@gomomento/generated-types-webtext/-/generated-types-webtext-0.113.0.tgz", + "integrity": "sha512-+g7WyDyaakP9T+5nw0pYNQ5INuLGNmr0uzdX9bEeEpwFd2pDrPxFP6HBfvSOD1HJ+4tuUhc2mAA3/kQ5el7xhA==", "dependencies": { "google-protobuf": "3.21.2", "grpc-web": "1.4.2" @@ -843,6 +845,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.0", @@ -857,6 +860,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -1376,9 +1380,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" @@ -1718,9 +1722,9 @@ } }, "node_modules/acorn-globals/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1739,10 +1743,25 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk/node_modules/acorn": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, "engines": { "node": ">=0.4.0" } @@ -2161,9 +2180,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, "funding": [ { @@ -2180,10 +2199,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "update-browserslist-db": "^1.0.16" }, "bin": { "browserslist": "cli.js" @@ -2257,9 +2276,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001617", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz", - "integrity": "sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==", + "version": "1.0.30001636", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", + "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", "dev": true, "funding": [ { @@ -2531,9 +2550,9 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2690,9 +2709,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.762", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.762.tgz", - "integrity": "sha512-rrFvGweLxPwwSwJOjIopy3Vr+J3cIPtZzuc74bmlvmBIgQO3VYJDvVrlj94iKZ3ukXUH64Ex31hSfRTLqvjYJQ==", + "version": "1.4.807", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.807.tgz", + "integrity": "sha512-kSmJl2ZwhNf/bcIuCH/imtNOKlpkLDn2jqT5FJ+/0CXjhnFaOa9cOe9gHKKy71eM49izwuQjZhKk+lWQ1JxB7A==", "dev": true }, "node_modules/emittery": { @@ -3713,6 +3732,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -4044,6 +4064,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -4133,12 +4154,15 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4987,12 +5011,12 @@ } }, "node_modules/jest-message-util/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -5439,9 +5463,9 @@ } }, "node_modules/jsdom/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -5634,12 +5658,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -5749,9 +5773,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.9.tgz", - "integrity": "sha512-2f3F0SEEer8bBu0dsNCFF50N0cTThV1nWFYcEYFZttdW0lDAoybv9cQoK7X7/68Z89S7FoRrVjP1LPX4XRf9vg==", + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", + "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==", "dev": true }, "node_modules/object-inspect": { @@ -6016,9 +6040,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -6334,6 +6358,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -6753,9 +6778,9 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3", @@ -6935,9 +6960,9 @@ } }, "node_modules/ts-node/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -7144,9 +7169,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz", - "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "funding": [ { @@ -7164,7 +7189,7 @@ ], "dependencies": { "escalade": "^3.1.2", - "picocolors": "^1.0.0" + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -7391,9 +7416,9 @@ } }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" diff --git a/packages/client-sdk-web/package.json b/packages/client-sdk-web/package.json index 5f431ab4c..c94b023fb 100644 --- a/packages/client-sdk-web/package.json +++ b/packages/client-sdk-web/package.json @@ -18,6 +18,7 @@ "unit-test": "jest unit", "integration-test-auth": "jest --env=jsdom auth-client.test.ts --maxWorkers 1", "integration-test-leaderboard": "jest --env=jsdom leaderboard --maxWorkers 1", + "integration-test-store": "jest --env=jsdom storage.test.ts --maxWorkers 1", "integration-test-jsdom": "jest integration --env=jsdom --testMatch \"**/dictionary.test.ts|**/ping.test.ts|*/topic-client.test.ts|leaderboard.test.ts\" --maxWorkers 1", "integration-test-happy-dom": "jest integration --env=@happy-dom/jest-environment --testPathIgnorePatterns \"dictionary.test.ts|ping.test.ts|topic-client.test.ts|auth-client.test.ts|leaderboard.test.ts\" --maxWorkers 1", "integration-test": "npm run integration-test-happy-dom && npm run integration-test-jsdom", @@ -55,7 +56,7 @@ "xhr2": "0.2.1" }, "dependencies": { - "@gomomento/generated-types-webtext": "0.107.4", + "@gomomento/generated-types-webtext": "0.113.0", "@gomomento/sdk-core": "file:../core", "@types/google-protobuf": "3.15.6", "google-protobuf": "3.21.2", diff --git a/packages/client-sdk-web/src/config/storage-configuration.ts b/packages/client-sdk-web/src/config/storage-configuration.ts new file mode 100644 index 000000000..ea066ea73 --- /dev/null +++ b/packages/client-sdk-web/src/config/storage-configuration.ts @@ -0,0 +1,33 @@ +import {MomentoLoggerFactory} from '@gomomento/sdk-core'; + +export interface StorageConfigurationProps { + /** + * Configures logging verbosity and format + */ + loggerFactory: MomentoLoggerFactory; +} + +/** + * Configuration options for Momento StorageClient + * + * @export + * @interface StorageConfiguration + */ +export interface StorageConfiguration { + /** + * @returns {MomentoLoggerFactory} the current configuration options for logging verbosity and format + */ + getLoggerFactory(): MomentoLoggerFactory; +} + +export class StorageClientConfiguration implements StorageConfiguration { + private readonly loggerFactory: MomentoLoggerFactory; + + constructor(props: StorageConfigurationProps) { + this.loggerFactory = props.loggerFactory; + } + + getLoggerFactory(): MomentoLoggerFactory { + return this.loggerFactory; + } +} diff --git a/packages/client-sdk-web/src/config/storage-configurations.ts b/packages/client-sdk-web/src/config/storage-configurations.ts new file mode 100644 index 000000000..b460aa3c8 --- /dev/null +++ b/packages/client-sdk-web/src/config/storage-configurations.ts @@ -0,0 +1,30 @@ +import {MomentoLoggerFactory} from '@gomomento/sdk-core'; +import {DefaultMomentoLoggerFactory} from './logging/default-momento-logger'; +import { + StorageClientConfiguration, + StorageConfiguration, +} from './storage-configuration'; + +const defaultLoggerFactory: MomentoLoggerFactory = + new DefaultMomentoLoggerFactory(); + +/** + * Default config provides defaults suitable for most environments; prioritizes success of publishing and receiving messages. + * @export + * @class Default + */ +export class Default extends StorageClientConfiguration { + /** + * Provides the latest recommended configuration for a default environment. NOTE: this configuration may + * change in future releases to take advantage of improvements we identify for default configurations. + * @param {MomentoLoggerFactory} [loggerFactory=defaultLoggerFactory] + * @returns {StorageConfiguration} + */ + static latest( + loggerFactory: MomentoLoggerFactory = defaultLoggerFactory + ): StorageConfiguration { + return new StorageClientConfiguration({ + loggerFactory: loggerFactory, + }); + } +} diff --git a/packages/client-sdk-web/src/errors/cache-service-error-mapper.ts b/packages/client-sdk-web/src/errors/cache-service-error-mapper.ts index 4b314dc67..7759b50aa 100644 --- a/packages/client-sdk-web/src/errors/cache-service-error-mapper.ts +++ b/packages/client-sdk-web/src/errors/cache-service-error-mapper.ts @@ -1,5 +1,4 @@ import { - NotFoundError, InternalServerError, InvalidArgumentError, PermissionError, @@ -8,18 +7,24 @@ import { TimeoutError, AuthenticationError, LimitExceededError, - AlreadyExistsError, SdkError, UnknownServiceError, ServerUnavailableError, UnknownError, FailedPreconditionError, } from '../../src'; -import {RpcError, StatusCode} from 'grpc-web'; +import {Metadata, RpcError, StatusCode} from 'grpc-web'; import { ICacheServiceErrorMapper, ResolveOrRejectErrorOptions, } from '@gomomento/sdk-core/dist/src/errors/ICacheServiceErrorMapper'; +import { + CacheAlreadyExistsError, + CacheNotFoundError, + StoreItemNotFoundError, + StoreAlreadyExistsError, + StoreNotFoundError, +} from '@gomomento/sdk-core'; export class CacheServiceErrorMapper implements ICacheServiceErrorMapper @@ -55,7 +60,7 @@ export class CacheServiceErrorMapper const errParams: [ string, number | undefined, - object | undefined, + Metadata | undefined, string | undefined ] = [ err?.message || 'Unable to process request', @@ -74,8 +79,29 @@ export class CacheServiceErrorMapper return new UnknownServiceError(...errParams); case StatusCode.UNAVAILABLE: return new ServerUnavailableError(...errParams); - case StatusCode.NOT_FOUND: - return new NotFoundError(...errParams); + case StatusCode.NOT_FOUND: { + let errCause = ''; + // TODO: Remove this once the error message is standardized on the server side + const errorMessage = errParams[0]?.toString(); + const isStoreNotFound = + errorMessage?.includes('Store with name:') && + errorMessage?.includes("doesn't exist"); + if (isStoreNotFound) { + errCause = 'store_not_found'; + } + const isElementNotFound = errorMessage?.includes('Element not found'); + if (isElementNotFound) { + errCause = 'element_not_found'; + } + switch (errCause) { + case 'element_not_found': + return new StoreItemNotFoundError(...errParams); + case 'store_not_found': + return new StoreNotFoundError(...errParams); + default: + return new CacheNotFoundError(...errParams); + } + } case StatusCode.OUT_OF_RANGE: case StatusCode.UNIMPLEMENTED: return new BadRequestError(...errParams); @@ -91,8 +117,24 @@ export class CacheServiceErrorMapper return new AuthenticationError(...errParams); case StatusCode.RESOURCE_EXHAUSTED: return new LimitExceededError(...errParams); - case StatusCode.ALREADY_EXISTS: - return new AlreadyExistsError(...errParams); + case StatusCode.ALREADY_EXISTS: { + let errCause = ''; + // TODO: Remove this once the error message is standardized on the server side + const errorMessage = errParams[0]?.toString(); + const isStoreAlreadyExists = + errorMessage?.includes('Store with name:') && + errorMessage?.includes('already exists'); + // If errCause is not already set to 'store_already_exists', check for store_already_exists error + if (!errCause && isStoreAlreadyExists) { + errCause = 'store_already_exists'; + } + switch (errCause) { + case 'store_already_exists': + return new StoreAlreadyExistsError(...errParams); + default: + return new CacheAlreadyExistsError(...errParams); + } + } default: return new UnknownError(...errParams); } diff --git a/packages/client-sdk-web/src/index.ts b/packages/client-sdk-web/src/index.ts index 9dc401a83..20ba0b0d2 100644 --- a/packages/client-sdk-web/src/index.ts +++ b/packages/client-sdk-web/src/index.ts @@ -2,9 +2,11 @@ import {CacheClient} from './cache-client'; import {AuthClient} from './auth-client'; import {TopicClient} from './topic-client'; import {PreviewLeaderboardClient} from './preview-leaderboard-client'; +import {PreviewStorageClient} from './preview-storage-client'; import * as Configurations from './config/configurations'; import * as TopicConfigurations from './config/topic-configurations'; import * as LeaderboardConfigurations from './config/leaderboard-configurations'; +import * as StorageConfigurations from './config/storage-configurations'; // Cache Client Response Types import * as CacheGet from '@gomomento/sdk-core/dist/src/messages/responses/cache-get'; @@ -91,7 +93,8 @@ import { EnvMomentoTokenProvider, MomentoErrorCode, SdkError, - AlreadyExistsError, + CacheAlreadyExistsError, + StoreAlreadyExistsError, AuthenticationError, CancelledError, FailedPreconditionError, @@ -103,7 +106,9 @@ import { TimeoutError, BadRequestError, PermissionError, - NotFoundError, + CacheNotFoundError, + StoreNotFoundError, + StoreItemNotFoundError, UnknownError, MomentoLogger, MomentoLoggerFactory, @@ -145,6 +150,7 @@ import { RotateWebhookSecret, WebhookDestinationType, ReadConcern, + StoreInfo, } from '@gomomento/sdk-core'; import {Configuration} from './config/configuration'; @@ -159,6 +165,8 @@ import { LeaderboardConfiguration, } from './config/leaderboard-configuration'; +export * from '@gomomento/sdk-core/dist/src/messages/responses/storage'; + export { DefaultMomentoLoggerFactory, DefaultMomentoLogger, @@ -293,7 +301,8 @@ export { ExpiresIn, MomentoErrorCode, SdkError, - AlreadyExistsError, + CacheAlreadyExistsError, + StoreAlreadyExistsError, AuthenticationError, CancelledError, FailedPreconditionError, @@ -305,7 +314,9 @@ export { TimeoutError, BadRequestError, PermissionError, - NotFoundError, + CacheNotFoundError, + StoreNotFoundError, + StoreItemNotFoundError, UnknownError, MomentoLogger, MomentoLoggerFactory, @@ -323,4 +334,8 @@ export { RotateWebhookSecret, WebhookDestinationType, ReadConcern, + // Storage + StoreInfo, + StorageConfigurations, + PreviewStorageClient, }; diff --git a/packages/client-sdk-web/src/internal/cache-control-client.ts b/packages/client-sdk-web/src/internal/cache-control-client.ts index f600852b0..be8d92e29 100644 --- a/packages/client-sdk-web/src/internal/cache-control-client.ts +++ b/packages/client-sdk-web/src/internal/cache-control-client.ts @@ -7,9 +7,10 @@ import { MomentoLogger, CacheFlush, CacheInfo, + MomentoErrorCode, } from '..'; import {Configuration} from '../config/configuration'; -import {Request, StatusCode, UnaryResponse} from 'grpc-web'; +import {Request, UnaryResponse} from 'grpc-web'; import { _CreateCacheRequest, _DeleteCacheRequest, @@ -93,7 +94,11 @@ export class CacheControlClient< this.clientMetadataProvider.createClientMetadata(), (err, _resp) => { if (err) { - if (err.code === StatusCode.ALREADY_EXISTS) { + const sdkError = this.cacheServiceErrorMapper.convertError(err); + if ( + sdkError.errorCode() === + MomentoErrorCode.CACHE_ALREADY_EXISTS_ERROR + ) { resolve(new CreateCache.AlreadyExists()); } else { this.cacheServiceErrorMapper.resolveOrRejectError({ diff --git a/packages/client-sdk-web/src/internal/cache-data-client.ts b/packages/client-sdk-web/src/internal/cache-data-client.ts index cc768be61..72c3303cb 100644 --- a/packages/client-sdk-web/src/internal/cache-data-client.ts +++ b/packages/client-sdk-web/src/internal/cache-data-client.ts @@ -96,8 +96,6 @@ import { _ListRetainRequest, _SetDifferenceRequest, _SetFetchRequest, - _SetIfNotExistsRequest, - _SetIfNotExistsResponse, _SetIfRequest, _SetIfResponse, _SetRequest, diff --git a/packages/client-sdk-web/src/internal/client-metadata-provider.ts b/packages/client-sdk-web/src/internal/client-metadata-provider.ts index 3d94e9cbe..e21205a1f 100644 --- a/packages/client-sdk-web/src/internal/client-metadata-provider.ts +++ b/packages/client-sdk-web/src/internal/client-metadata-provider.ts @@ -39,7 +39,6 @@ export class ClientMetadataProvider { metadata['Runtime-Version'] = `javascript-web:${getBrowserName( navigator.userAgent )}`; - console.log('web metadata:', metadata); } if (this.readConcern) { metadata['read-concern'] = this.readConcern; diff --git a/packages/client-sdk-web/src/internal/storage-control-client.ts b/packages/client-sdk-web/src/internal/storage-control-client.ts new file mode 100644 index 000000000..879d89ce1 --- /dev/null +++ b/packages/client-sdk-web/src/internal/storage-control-client.ts @@ -0,0 +1,171 @@ +import {control} from '@gomomento/generated-types-webtext'; +import { + CredentialProvider, + MomentoLogger, + CreateStore, + DeleteStore, + ListStores, + StoreInfo, + MomentoErrorCode, +} from '..'; +import {Request, UnaryResponse} from 'grpc-web'; +import { + _CreateStoreRequest, + _DeleteStoreRequest, + _ListStoresRequest, +} from '@gomomento/generated-types-webtext/dist/controlclient_pb'; +import {IStorageControlClient} from '@gomomento/sdk-core/dist/src/internal/clients'; +import {validateStoreName} from '@gomomento/sdk-core/dist/src/internal/utils'; +import {getWebControlEndpoint} from '../utils/web-client-utils'; +import {ClientMetadataProvider} from './client-metadata-provider'; +import {StorageConfiguration} from '../config/storage-configuration'; +import {CacheServiceErrorMapper} from '../errors/cache-service-error-mapper'; + +export interface StorageClientClientProps { + configuration: StorageConfiguration; + credentialProvider: CredentialProvider; +} + +export class StorageControlClient< + REQ extends Request, + RESP extends UnaryResponse +> implements IStorageControlClient +{ + private readonly clientWrapper: control.ScsControlClient; + private readonly logger: MomentoLogger; + private readonly cacheServiceErrorMapper: CacheServiceErrorMapper; + + private readonly clientMetadataProvider: ClientMetadataProvider; + + /** + * @param {ControlClientProps} props + */ + constructor(props: StorageClientClientProps) { + this.logger = props.configuration.getLoggerFactory().getLogger(this); + this.cacheServiceErrorMapper = new CacheServiceErrorMapper(false); + this.logger.debug( + `Creating storage control client using endpoint: '${getWebControlEndpoint( + props.credentialProvider + )}` + ); + + this.clientMetadataProvider = new ClientMetadataProvider({ + authToken: props.credentialProvider.getAuthToken(), + clientType: 'store', + }); + this.clientWrapper = new control.ScsControlClient( + // Note: all web SDK requests are routed to a `web.` subdomain to allow us flexibility on the server + getWebControlEndpoint(props.credentialProvider), + null, + {} + ); + } + + close() { + this.logger.debug('Closing cache control client'); + // do nothing as gRPC web version doesn't expose a close() yet. + // this is needed as we have added close to `IControlClient` extended + // by both nodejs and web SDKs + } + + public async createStore(name: string): Promise { + try { + validateStoreName(name); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new CreateStore.Error(err) + ); + } + this.logger.debug(`Creating store: ${name}`); + const request = new _CreateStoreRequest(); + request.setStoreName(name); + + return await new Promise((resolve, reject) => { + this.clientWrapper.createStore( + request, + this.clientMetadataProvider.createClientMetadata(), + (err, _resp) => { + if (err) { + const sdkError = this.cacheServiceErrorMapper.convertError(err); + if ( + sdkError.errorCode() === + MomentoErrorCode.STORE_ALREADY_EXISTS_ERROR + ) { + return resolve(new CreateStore.AlreadyExists()); + } else { + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new CreateStore.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } + } else { + resolve(new CreateStore.Success()); + } + } + ); + }); + } + + public async deleteStore(name: string): Promise { + try { + validateStoreName(name); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new DeleteStore.Error(err) + ); + } + const request = new _DeleteStoreRequest(); + request.setStoreName(name); + this.logger.debug(`Deleting store: ${name}`); + return await new Promise((resolve, reject) => { + this.clientWrapper.deleteStore( + request, + this.clientMetadataProvider.createClientMetadata(), + (err, _resp) => { + if (err) { + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new DeleteStore.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } else { + resolve(new DeleteStore.Success()); + } + } + ); + }); + } + + public async listStores(): Promise { + const request = new _ListStoresRequest(); + request.setNextToken(''); + this.logger.debug("Issuing 'listStores' request"); + return await new Promise((resolve, reject) => { + this.clientWrapper.listStores( + request, + this.clientMetadataProvider.createClientMetadata(), + (err, resp) => { + if (err) { + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new ListStores.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } else { + const stores = resp.getStoreList().map(store => { + const storeName = store.getStoreName(); + return new StoreInfo(storeName); + }); + resolve(new ListStores.Success(stores)); + } + } + ); + }); + } +} diff --git a/packages/client-sdk-web/src/internal/storage-data-client.ts b/packages/client-sdk-web/src/internal/storage-data-client.ts new file mode 100644 index 000000000..472db6de3 --- /dev/null +++ b/packages/client-sdk-web/src/internal/storage-data-client.ts @@ -0,0 +1,342 @@ +import {store} from '@gomomento/generated-types-webtext'; +import { + StorageGet, + StoragePut, + StorageDelete, + CredentialProvider, + MomentoLogger, + UnknownError, + MomentoErrorCode, +} from '..'; +import {Request, UnaryResponse} from 'grpc-web'; +import { + _StoreDeleteRequest, + _StoreGetRequest, + _StorePutRequest, + _StoreValue, +} from '@gomomento/generated-types-webtext/dist/store_pb'; +import {IStorageDataClient} from '@gomomento/sdk-core/dist/src/internal/clients'; +import {validateStoreName} from '@gomomento/sdk-core/dist/src/internal/utils'; +import { + convertToB64String, + createStorageMetadata, + getWebStorageEndpoint, +} from '../utils/web-client-utils'; +import {ClientMetadataProvider} from './client-metadata-provider'; +import ValueCase = _StoreValue.ValueCase; +import {StorageConfiguration} from '../config/storage-configuration'; +import {CacheServiceErrorMapper} from '../errors/cache-service-error-mapper'; + +export interface StorageDataClientProps { + configuration: StorageConfiguration; + credentialProvider: CredentialProvider; +} + +export class StorageDataClient< + REQ extends Request, + RESP extends UnaryResponse +> implements IStorageDataClient +{ + private readonly clientWrapper: store.StoreClient; + private readonly cacheServiceErrorMapper: CacheServiceErrorMapper; + private readonly logger: MomentoLogger; + private readonly clientMetadataProvider: ClientMetadataProvider; + // TODO make this part of configuration + private readonly deadlineMillis: number = 10000; + + /** + * @param {DataClientProps} props + */ + constructor(props: StorageDataClientProps) { + this.cacheServiceErrorMapper = new CacheServiceErrorMapper(false); + this.logger = props.configuration.getLoggerFactory().getLogger(this); + this.logger.debug( + `Creating storage data client using endpoint: '${getWebStorageEndpoint( + props.credentialProvider + )}` + ); + + this.clientMetadataProvider = new ClientMetadataProvider({ + authToken: props.credentialProvider.getAuthToken(), + clientType: 'store', + }); + this.clientWrapper = new store.StoreClient( + // Note: all web SDK requests are routed to a `web.` subdomain to allow us flexibility on the server + getWebStorageEndpoint(props.credentialProvider), + null + ); + } + + close() { + this.logger.debug('Closing cache control client'); + // do nothing as gRPC web version doesn't expose a close() yet. + // this is needed as we have added close to `IControlClient` extended + // by both nodejs and web SDKs + } + + public async get( + storeName: string, + key: string + ): Promise { + try { + validateStoreName(storeName); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new StorageGet.Error(err) + ); + } + this.logger.trace(`Issuing 'get' request; key: ${key.toString()}`); + const result = await this.sendGet(storeName, convertToB64String(key)); + this.logger.trace(`'get' request result: ${result.toString()}`); + return result; + } + + private async sendGet( + storeName: string, + key: string + ): Promise { + const request = new _StoreGetRequest(); + request.setKey(key); + + return await new Promise((resolve, reject) => { + this.clientWrapper.get( + request, + { + ...this.clientMetadataProvider.createClientMetadata(), + ...createStorageMetadata(storeName, this.deadlineMillis), + }, + (err, resp) => { + const value = resp?.getValue() as _StoreValue; + if (resp) { + switch (value?.getValueCase()) { + case undefined: + case ValueCase.VALUE_NOT_SET: { + return resolve( + new StorageGet.Error( + new UnknownError( + 'StorageGet responded with an unknown result' + ) + ) + ); + } + case ValueCase.BYTES_VALUE: { + return resolve( + StorageGet.Success.ofBytes(value.getBytesValue_asU8()) + ); + } + case ValueCase.STRING_VALUE: { + return resolve( + StorageGet.Success.ofString(value.getStringValue()) + ); + } + case ValueCase.INTEGER_VALUE: { + return resolve( + StorageGet.Success.ofInt(value.getIntegerValue()) + ); + } + case ValueCase.DOUBLE_VALUE: { + return resolve( + StorageGet.Success.ofDouble(value.getDoubleValue()) + ); + } + } + } else { + const sdkError = this.cacheServiceErrorMapper.convertError(err); + if ( + sdkError.errorCode() === + MomentoErrorCode.STORE_ITEM_NOT_FOUND_ERROR + ) { + return resolve(new StorageGet.Success(undefined)); + } + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new StorageGet.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } + } + ); + }); + } + + public async putInt( + storeName: string, + key: string, + value: number + ): Promise { + try { + validateStoreName(storeName); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new StoragePut.Error(err) + ); + } + this.logger.trace(`Issuing 'put' request; key: ${key.toString()}`); + const storeValue = new _StoreValue(); + storeValue.setIntegerValue(value); + const result = await this.sendPut( + storeName, + convertToB64String(key), + storeValue + ); + this.logger.trace(`'put' request result: ${result.toString()}`); + return result; + } + + public async putDouble( + storeName: string, + key: string, + value: number + ): Promise { + try { + validateStoreName(storeName); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new StoragePut.Error(err) + ); + } + this.logger.trace(`Issuing 'put' request; key: ${key.toString()}`); + const storeValue = new _StoreValue(); + storeValue.setDoubleValue(value); + const result = await this.sendPut( + storeName, + convertToB64String(key), + storeValue + ); + this.logger.trace(`'put' request result: ${result.toString()}`); + return result; + } + + public async putBytes( + storeName: string, + key: string, + value: Uint8Array + ): Promise { + try { + validateStoreName(storeName); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new StoragePut.Error(err) + ); + } + this.logger.trace(`Issuing 'put' request; key: ${key.toString()}`); + const storeValue = new _StoreValue(); + storeValue.setBytesValue(value); + const result = await this.sendPut( + storeName, + convertToB64String(key), + storeValue + ); + this.logger.trace(`'put' request result: ${result.toString()}`); + return result; + } + + public async putString( + storeName: string, + key: string, + value: string + ): Promise { + try { + validateStoreName(storeName); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new StoragePut.Error(err) + ); + } + this.logger.trace(`Issuing 'put' request; key: ${key.toString()}`); + const storeValue = new _StoreValue(); + storeValue.setStringValue(value); + const result = await this.sendPut( + storeName, + convertToB64String(key), + storeValue + ); + this.logger.trace(`'put' request result: ${result.toString()}`); + return result; + } + + private async sendPut( + storeName: string, + key: string, + storeValue: _StoreValue + ): Promise { + const request = new _StorePutRequest(); + request.setKey(key); + request.setValue(storeValue); + return await new Promise((resolve, reject) => { + this.clientWrapper.put( + request, + { + ...this.clientMetadataProvider.createClientMetadata(), + ...createStorageMetadata(storeName, this.deadlineMillis), + }, + (err, _resp) => { + if (err) { + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new StoragePut.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } else { + resolve(new StoragePut.Success()); + } + } + ); + }); + } + + public async delete( + storeName: string, + key: string + ): Promise { + try { + validateStoreName(storeName); + } catch (err) { + return this.cacheServiceErrorMapper.returnOrThrowError( + err as Error, + err => new StorageDelete.Error(err) + ); + } + this.logger.trace(`Issuing 'delete' request; key: ${key.toString()}`); + const result = await this.sendDelete(storeName, convertToB64String(key)); + this.logger.trace(`'delete' request result: ${result.toString()}`); + return result; + } + + private async sendDelete( + storeName: string, + key: string + ): Promise { + const request = new _StoreDeleteRequest(); + request.setKey(key); + + return await new Promise((resolve, reject) => { + this.clientWrapper.delete( + request, + { + ...this.clientMetadataProvider.createClientMetadata(), + ...createStorageMetadata(storeName, this.deadlineMillis), + }, + (err, _resp) => { + if (err) { + this.cacheServiceErrorMapper.resolveOrRejectError({ + err: err, + errorResponseFactoryFn: e => new StorageDelete.Error(e), + resolveFn: resolve, + rejectFn: reject, + }); + } else { + resolve(new StorageDelete.Success()); + } + } + ); + }); + } +} diff --git a/packages/client-sdk-web/src/preview-storage-client.ts b/packages/client-sdk-web/src/preview-storage-client.ts new file mode 100644 index 000000000..54fa30c7d --- /dev/null +++ b/packages/client-sdk-web/src/preview-storage-client.ts @@ -0,0 +1,81 @@ +import { + AbstractStorageClient, + IStorageControlClient, + IStorageDataClient, +} from '@gomomento/sdk-core/dist/src/internal/clients'; +import {StorageConfigurations} from './index'; +import {IStorageClient} from '@gomomento/sdk-core'; +import {StorageConfiguration} from './config/storage-configuration'; +import {StorageDataClient} from './internal/storage-data-client'; +import {StorageClientProps} from './storage-client-props'; +import {StorageControlClient} from './internal/storage-control-client'; + +interface StorageClientPropsWithConfiguration extends StorageClientProps { + configuration: StorageConfiguration; +} + +export class PreviewStorageClient + extends AbstractStorageClient + implements IStorageClient +{ + private readonly _configuration: StorageConfiguration; + + constructor(props: StorageClientProps) { + const configuration = + props.configuration ?? getDefaultStorageClientConfiguration(); + const propsWithConfiguration: StorageClientPropsWithConfiguration = { + ...props, + configuration, + }; + const controlClient: IStorageControlClient = createControlClient( + propsWithConfiguration + ); + const dataClient: IStorageDataClient = createDataClient( + propsWithConfiguration + ); + super([dataClient], controlClient); + } + + public close() { + this.controlClient.close(); + this.dataClients.forEach(client => client.close()); + } + + /** + * The configuration used by this client. + * + * @readonly + * @type {StorageConfiguration} the configuration used by this client + * @memberof PreviewStorageClient + */ + public get configuration(): StorageConfiguration { + return this._configuration; + } +} + +function createControlClient( + props: StorageClientPropsWithConfiguration +): IStorageControlClient { + return new StorageControlClient({ + configuration: props.configuration, + credentialProvider: props.credentialProvider, + }); +} + +function createDataClient( + props: StorageClientPropsWithConfiguration +): IStorageDataClient { + return new StorageDataClient({ + configuration: props.configuration, + credentialProvider: props.credentialProvider, + }); +} + +function getDefaultStorageClientConfiguration(): StorageConfiguration { + const config = StorageConfigurations.Default.latest(); + const logger = config.getLoggerFactory().getLogger('StorageClient'); + logger.info( + 'No configuration provided to StorageClient. Using default configuration. For production use, consider specifying an explicit configuration.' + ); + return config; +} diff --git a/packages/client-sdk-web/src/storage-client-props.ts b/packages/client-sdk-web/src/storage-client-props.ts new file mode 100644 index 000000000..478967891 --- /dev/null +++ b/packages/client-sdk-web/src/storage-client-props.ts @@ -0,0 +1,13 @@ +import {CredentialProvider} from '.'; +import {StorageConfiguration} from './config/storage-configuration'; + +export interface StorageClientProps { + /** + * Configuration settings for the storage client + */ + configuration?: StorageConfiguration; + /** + * controls how the client will get authentication information for connecting to the Momento service + */ + credentialProvider: CredentialProvider; +} 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 821b8d17c..3c621dd74 100644 --- a/packages/client-sdk-web/src/utils/web-client-utils.ts +++ b/packages/client-sdk-web/src/utils/web-client-utils.ts @@ -36,6 +36,14 @@ export function createCallMetadata( return {cache: cacheName, deadline: deadline.toString()}; } +export function createStorageMetadata( + storeName: string, + timeoutMillis: number +): {store: string; deadline: string} { + const deadline = Date.now() + timeoutMillis; + return {store: storeName, deadline: deadline.toString()}; +} + export function getWebControlEndpoint( credentialProvider: CredentialProvider ): string { @@ -54,6 +62,15 @@ export function getWebCacheEndpoint( return withProtocolPrefix(`web.${credentialProvider.getCacheEndpoint()}`); } +export function getWebStorageEndpoint( + credentialProvider: CredentialProvider +): string { + if (credentialProvider.areEndpointsOverridden()) { + return withProtocolPrefix(credentialProvider.getStorageEndpoint()); + } + return withProtocolPrefix(`web.${credentialProvider.getStorageEndpoint()}`); +} + export function getWebTokenEndpoint( credentialProvider: CredentialProvider ): string { diff --git a/packages/client-sdk-web/test/integration/integration-setup.ts b/packages/client-sdk-web/test/integration/integration-setup.ts index 48e51d046..c63735b7a 100644 --- a/packages/client-sdk-web/test/integration/integration-setup.ts +++ b/packages/client-sdk-web/test/integration/integration-setup.ts @@ -9,6 +9,7 @@ import { DeleteCache, CredentialProvider, ReadConcern, + IStorageClient, } from '@gomomento/sdk-core'; import { CacheClient, @@ -18,6 +19,8 @@ import { TopicConfigurations, PreviewLeaderboardClient, LeaderboardConfigurations, + StorageConfigurations, + PreviewStorageClient, } from '../../src'; import {ITopicClient} from '@gomomento/sdk-core/dist/src/clients/ITopicClient'; import {ICacheClient} from '@gomomento/sdk-core/dist/src/clients/ICacheClient'; @@ -41,6 +44,9 @@ export function credsProvider(): CredentialProvider { tokenEndpoint: { endpoint: 'https://localhost:9001', }, + storageEndpoint: { + endpoint: 'https://localhost:9001', + }, }, }); } else { @@ -71,6 +77,10 @@ function sessionCredsProvider(): CredentialProvider { endpoint: credsProvider().getTokenEndpoint(), secureConnection: credsProvider().isTokenEndpointSecure(), }, + storageEndpoint: { + endpoint: credsProvider().getStorageEndpoint(), + secureConnection: credsProvider().isStorageEndpointSecure(), + }, }, }); } @@ -112,6 +122,13 @@ function momentoTopicClientForTesting(): TopicClient { }); } +function momentoStorageClientForTesting(): PreviewStorageClient { + return new PreviewStorageClient({ + configuration: StorageConfigurations.Default.latest(), + credentialProvider: credsProvider(), + }); +} + function momentoTopicClientWithThrowOnErrorsForTesting(): TopicClient { return new TopicClient({ configuration: TopicConfigurations.Default.latest().withThrowOnErrors(true), @@ -215,6 +232,18 @@ export function SetupTopicIntegrationTest(): { }; } +export function SetupStorageIntegrationTest(): { + storageClient: IStorageClient; + integrationTestStoreName: string; +} { + const {integrationTestCacheName} = SetupIntegrationTest(); + const storageClient = momentoStorageClientForTesting(); + return { + storageClient, + integrationTestStoreName: integrationTestCacheName, + }; +} + export function SetupLeaderboardIntegrationTest(): { leaderboardClient: PreviewLeaderboardClient; leaderboardClientWithThrowOnErrors: PreviewLeaderboardClient; diff --git a/packages/client-sdk-web/test/integration/shared/storage.test.ts b/packages/client-sdk-web/test/integration/shared/storage.test.ts new file mode 100644 index 000000000..bfdfc72ac --- /dev/null +++ b/packages/client-sdk-web/test/integration/shared/storage.test.ts @@ -0,0 +1,6 @@ +import {runStorageServiceTests} from '@gomomento/common-integration-tests'; +import {SetupStorageIntegrationTest} from '../integration-setup'; + +const {storageClient, integrationTestStoreName} = SetupStorageIntegrationTest(); + +runStorageServiceTests(storageClient, integrationTestStoreName); diff --git a/packages/client-sdk-web/test/unit/utils/web-client-utils.test.ts b/packages/client-sdk-web/test/unit/utils/web-client-utils.test.ts index 45427a472..1a774f3f1 100644 --- a/packages/client-sdk-web/test/unit/utils/web-client-utils.test.ts +++ b/packages/client-sdk-web/test/unit/utils/web-client-utils.test.ts @@ -9,6 +9,7 @@ import { convertToStringFromB64String, getWebCacheEndpoint, getWebControlEndpoint, + getWebStorageEndpoint, getWebTokenEndpoint, } from '../../../src/utils/web-client-utils'; @@ -62,6 +63,9 @@ describe('getWeb*Endpoint', () => { tokenEndpoint: { endpoint: 'some-token-endpoint', }, + storageEndpoint: { + endpoint: 'some-storage-endpoint', + }, }, }); const webControlEndpoint = getWebControlEndpoint(credProvider); @@ -70,6 +74,8 @@ describe('getWeb*Endpoint', () => { expect(webCacheEndpoint).toEqual('https://some-cache-endpoint'); const webTokenEndpoint = getWebTokenEndpoint(credProvider); expect(webTokenEndpoint).toEqual('https://some-token-endpoint'); + const storageEndpoint = getWebStorageEndpoint(credProvider); + expect(storageEndpoint).toEqual('https://some-storage-endpoint'); }); it('works with overridden endpoints that already have the protocol', () => { const credProvider = CredentialProvider.fromString({ @@ -84,6 +90,9 @@ describe('getWeb*Endpoint', () => { tokenEndpoint: { endpoint: 'http://some-token-endpoint', }, + storageEndpoint: { + endpoint: 'http://some-storage-endpoint', + }, }, }); const webControlEndpoint = getWebControlEndpoint(credProvider); @@ -92,6 +101,8 @@ describe('getWeb*Endpoint', () => { expect(webCacheEndpoint).toEqual('https://some-cache-endpoint'); const webTokenEndpoint = getWebTokenEndpoint(credProvider); expect(webTokenEndpoint).toEqual('http://some-token-endpoint'); + const storageEndpoint = getWebStorageEndpoint(credProvider); + expect(storageEndpoint).toEqual('http://some-storage-endpoint'); }); describe('leaves port intact for overridden endpoints', () => { it("with overrides that don't contain protocol", () => { @@ -107,6 +118,9 @@ describe('getWeb*Endpoint', () => { tokenEndpoint: { endpoint: 'some-token-endpoint:9001', }, + storageEndpoint: { + endpoint: 'some-storage-endpoint:9001', + }, }, }); const webControlEndpoint = getWebControlEndpoint(credProvider); @@ -115,6 +129,8 @@ describe('getWeb*Endpoint', () => { expect(webCacheEndpoint).toEqual('https://some-cache-endpoint:9001'); const webTokenEndpoint = getWebTokenEndpoint(credProvider); expect(webTokenEndpoint).toEqual('https://some-token-endpoint:9001'); + const storageEndpoint = getWebStorageEndpoint(credProvider); + expect(storageEndpoint).toEqual('https://some-storage-endpoint:9001'); }); it('with overrides that do contain protocol', () => { const credProvider = CredentialProvider.fromString({ @@ -129,6 +145,9 @@ describe('getWeb*Endpoint', () => { tokenEndpoint: { endpoint: 'http://some-token-endpoint:9001', }, + storageEndpoint: { + endpoint: 'http://some-storage-endpoint:9001', + }, }, }); const webControlEndpoint = getWebControlEndpoint(credProvider); @@ -137,6 +156,8 @@ describe('getWeb*Endpoint', () => { expect(webCacheEndpoint).toEqual('https://some-cache-endpoint:9001'); const webTokenEndpoint = getWebTokenEndpoint(credProvider); expect(webTokenEndpoint).toEqual('http://some-token-endpoint:9001'); + const storageEndpoint = getWebStorageEndpoint(credProvider); + expect(storageEndpoint).toEqual('http://some-storage-endpoint:9001'); }); }); diff --git a/packages/common-integration-tests/package-lock.json b/packages/common-integration-tests/package-lock.json index 8298411b1..a485a9a67 100644 --- a/packages/common-integration-tests/package-lock.json +++ b/packages/common-integration-tests/package-lock.json @@ -36,7 +36,6 @@ } }, "../core": { - "name": "@gomomento/sdk-core", "version": "0.0.1", "license": "Apache-2.0", "dependencies": { @@ -88,30 +87,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", - "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.24.5", - "@babel/helpers": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -127,12 +126,12 @@ } }, "node_modules/@babel/core/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -149,12 +148,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.24.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -164,13 +163,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -189,62 +188,66 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -254,86 +257,86 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dev": true, "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -414,9 +417,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -486,12 +489,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -588,12 +591,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", - "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -603,26 +606,26 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -630,19 +633,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -651,12 +654,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -673,13 +676,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -730,9 +733,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -775,6 +778,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.0", @@ -789,6 +793,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -1267,9 +1272,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" @@ -1579,10 +1584,25 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk/node_modules/acorn": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "dev": true, + "bin": { + "acorn": "bin/acorn" + }, "engines": { "node": ">=0.4.0" } @@ -1983,9 +2003,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, "funding": [ { @@ -2002,10 +2022,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "update-browserslist-db": "^1.0.16" }, "bin": { "browserslist": "cli.js" @@ -2079,9 +2099,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001617", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz", - "integrity": "sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==", + "version": "1.0.30001636", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", + "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", "dev": true, "funding": [ { @@ -2297,9 +2317,9 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2428,9 +2448,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.762", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.762.tgz", - "integrity": "sha512-rrFvGweLxPwwSwJOjIopy3Vr+J3cIPtZzuc74bmlvmBIgQO3VYJDvVrlj94iKZ3ukXUH64Ex31hSfRTLqvjYJQ==", + "version": "1.4.807", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.807.tgz", + "integrity": "sha512-kSmJl2ZwhNf/bcIuCH/imtNOKlpkLDn2jqT5FJ+/0CXjhnFaOa9cOe9gHKKy71eM49izwuQjZhKk+lWQ1JxB7A==", "dev": true }, "node_modules/emittery": { @@ -3348,6 +3368,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -3604,6 +3625,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -4300,12 +4322,12 @@ } }, "node_modules/jest-message-util/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -4774,12 +4796,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -5117,9 +5139,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -5417,6 +5439,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -5812,9 +5835,9 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3", @@ -5967,9 +5990,9 @@ } }, "node_modules/ts-node/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -6167,9 +6190,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz", - "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "funding": [ { @@ -6187,7 +6210,7 @@ ], "dependencies": { "escalade": "^3.1.2", - "picocolors": "^1.0.0" + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" diff --git a/packages/common-integration-tests/src/common-int-test-utils.ts b/packages/common-integration-tests/src/common-int-test-utils.ts index d181a4dd1..6ae2e9d70 100644 --- a/packages/common-integration-tests/src/common-int-test-utils.ts +++ b/packages/common-integration-tests/src/common-int-test-utils.ts @@ -37,6 +37,10 @@ export function testCacheName(): string { return process.env.TEST_CACHE_NAME || `js-integration-test-default-${v4()}`; } +export function testStoreName(): string { + return process.env.TEST_STORE_NAME || `js-integration-test-default-${v4()}`; +} + /** * Returns a unique index name for use in integration tests. * @param meaningfulIdentifier Required suffix to identify the test for debugging purposes. @@ -74,7 +78,7 @@ export const deleteCacheIfExists = async ( } const deleteResponse = await client.deleteCache(cacheName); if (deleteResponse instanceof DeleteCache.Error) { - if (deleteResponse.errorCode() !== MomentoErrorCode.NOT_FOUND_ERROR) { + if (deleteResponse.errorCode() !== MomentoErrorCode.CACHE_NOT_FOUND_ERROR) { throw deleteResponse.innerException(); } } diff --git a/packages/common-integration-tests/src/create-delete-list-cache.ts b/packages/common-integration-tests/src/create-delete-list-cache.ts index 90f1d1993..485b69011 100644 --- a/packages/common-integration-tests/src/create-delete-list-cache.ts +++ b/packages/common-integration-tests/src/create-delete-list-cache.ts @@ -36,7 +36,7 @@ export function runCreateDeleteListCacheTests(cacheClient: ICacheClient) { }, `expected ERROR but got ${deleteResponse.toString()}`); if (deleteResponse instanceof DeleteCache.Error) { expect(deleteResponse.errorCode()).toEqual( - MomentoErrorCode.NOT_FOUND_ERROR + MomentoErrorCode.CACHE_NOT_FOUND_ERROR ); } }); @@ -131,7 +131,7 @@ export function runCreateDeleteListCacheTests(cacheClient: ICacheClient) { }, `expected ERROR but got ${flushResponse.toString()}`); if (flushResponse instanceof CacheFlush.Error) { expect(flushResponse.errorCode()).toEqual( - MomentoErrorCode.NOT_FOUND_ERROR + MomentoErrorCode.CACHE_NOT_FOUND_ERROR ); } }); diff --git a/packages/common-integration-tests/src/index.ts b/packages/common-integration-tests/src/index.ts index dd6d5c0f9..17fd8a2ca 100644 --- a/packages/common-integration-tests/src/index.ts +++ b/packages/common-integration-tests/src/index.ts @@ -13,3 +13,4 @@ export * from './update-ttl'; export * from './leaderboard-client'; export * from './webhooks'; export * from './batch-get-set'; +export * from './storage'; diff --git a/packages/common-integration-tests/src/storage.ts b/packages/common-integration-tests/src/storage.ts new file mode 100644 index 000000000..1aa8357a6 --- /dev/null +++ b/packages/common-integration-tests/src/storage.ts @@ -0,0 +1,259 @@ +import { + CreateStoreResponse, + DeleteStoreResponse, + IStorageClient, + ListStoresResponse, + MomentoErrorCode, + StorageDeleteResponse, + StorageGetResponse, + StoragePutResponse, +} from '@gomomento/sdk-core'; +import {testStoreName} from './common-int-test-utils'; +import {v4} from 'uuid'; + +export function runStorageServiceTests( + storageClient: IStorageClient, + testingStoreName: string +) { + describe('#create list and delete stores', () => { + it('creates a store, lists it and makes sure it exists, and then deletes it', async () => { + const storeName = testStoreName(); + const createResponse = await storageClient.createStore(storeName); + switch (createResponse.type) { + // this is the expected response + case CreateStoreResponse.Success: { + break; + } + case CreateStoreResponse.AlreadyExists: { + throw new Error( + 'store already exists, this should not happen in this test' + ); + } + case CreateStoreResponse.Error: { + throw new Error( + `failed to create store, expected store to be able to be created, error: ${createResponse.message()} exception: ${createResponse.toString()}` + ); + } + } + + const listResponse = await storageClient.listStores(); + switch (listResponse.type) { + case ListStoresResponse.Success: { + const storeNames = listResponse + .stores() + .map(store => store.getName()); + expect(storeNames).toContain(storeName); + break; + } + case ListStoresResponse.Error: { + throw new Error( + `failed to list stores: ${listResponse.message()} ${listResponse.toString()}` + ); + } + } + + const deleteResponse = await storageClient.deleteStore(storeName); + switch (deleteResponse.type) { + case DeleteStoreResponse.Success: { + break; + } + case DeleteStoreResponse.Error: { + throw new Error( + `failed to delete store: ${deleteResponse.message()} ${deleteResponse.toString()}` + ); + } + } + }); + it('should return AlreadyExists response if trying to create a store that already exists', async () => { + const storeName = testStoreName(); + const createResponse = await storageClient.createStore(storeName); + switch (createResponse.type) { + // this is the expected response + case CreateStoreResponse.Success: { + break; + } + case CreateStoreResponse.AlreadyExists: { + break; + } + case CreateStoreResponse.Error: { + throw new Error( + `failed to create store, expected store to be able to happen, error: ${createResponse.message()} exception: ${createResponse.toString()}` + ); + } + } + const alreadyExistResponse = await storageClient.createStore(storeName); + switch (alreadyExistResponse.type) { + case CreateStoreResponse.AlreadyExists: { + break; + } + case CreateStoreResponse.Error: { + throw new Error( + `failed to create store, expected AlreadyExists response, error: ${alreadyExistResponse.message()} exception: ${alreadyExistResponse.toString()}` + ); + } + case CreateStoreResponse.Success: { + throw new Error( + 'store already exists, we should not be able to create it again' + ); + } + } + await storageClient.deleteStore(storeName); + }); + }); + describe('#store get put and delete', () => { + it('put get and delete key in a store', async () => { + const key = v4(); + const createResponse = await storageClient.createStore(testingStoreName); + switch (createResponse.type) { + // this is the expected response + case CreateStoreResponse.Success: { + break; + } + case CreateStoreResponse.AlreadyExists: { + break; + } + case CreateStoreResponse.Error: { + throw new Error( + `failed to create store, expected store to be able to happen, error: ${createResponse.message()} exception: ${createResponse.toString()}` + ); + } + } + + // put/get an int value + const intValue = 42; + const putIntResponse = await storageClient.putInt( + testingStoreName, + key, + intValue + ); + switch (putIntResponse.type) { + case StoragePutResponse.Success: { + break; + } + case StoragePutResponse.Error: { + throw new Error( + `failed to put key: ${putIntResponse.message()} ${putIntResponse.toString()}` + ); + } + } + const getIntResponse = await storageClient.get(testingStoreName, key); + expect(getIntResponse.type).toEqual(StorageGetResponse.Success); + expect(getIntResponse.value()?.int()).toEqual(intValue); + + // put/get a double value + const doubleValue = 42.42; + const putDoubleResponse = await storageClient.putDouble( + testingStoreName, + key, + doubleValue + ); + switch (putDoubleResponse.type) { + case StoragePutResponse.Success: { + break; + } + case StoragePutResponse.Error: { + throw new Error( + `failed to put key: ${putDoubleResponse.message()} ${putDoubleResponse.toString()}` + ); + } + } + const getDoubleResponse = await storageClient.get(testingStoreName, key); + expect(getDoubleResponse.type).toEqual(StorageGetResponse.Success); + expect(getDoubleResponse.value()?.double()).toEqual(doubleValue); + + // put/get a string value + const stringValue = v4(); + const putStringResponse = await storageClient.putString( + testingStoreName, + key, + stringValue + ); + switch (putStringResponse.type) { + case StoragePutResponse.Success: { + break; + } + case StoragePutResponse.Error: { + throw new Error( + `failed to put key: ${putStringResponse.message()} ${putStringResponse.toString()}` + ); + } + } + const getStringResponse = await storageClient.get(testingStoreName, key); + expect(getStringResponse.type).toEqual(StorageGetResponse.Success); + expect(getStringResponse.value()?.string()).toEqual(stringValue); + + // put/get a bytes value + const bytesValue = new Uint8Array([1, 2, 3, 4]); + const putBytesResponse = await storageClient.putBytes( + testingStoreName, + key, + bytesValue + ); + switch (putBytesResponse.type) { + case StoragePutResponse.Success: { + break; + } + case StoragePutResponse.Error: { + throw new Error( + `failed to put key: ${putBytesResponse.message()} ${putBytesResponse.toString()}` + ); + } + } + const getBytesResponse = await storageClient.get(testingStoreName, key); + expect(getBytesResponse.type).toEqual(StorageGetResponse.Success); + expect(getBytesResponse.value()?.bytes()).toEqual(bytesValue); + + const deleteResponse = await storageClient.delete(testingStoreName, key); + switch (deleteResponse.type) { + case StorageDeleteResponse.Success: { + break; + } + case StorageDeleteResponse.Error: { + throw new Error( + `failed to delete key in store: ${deleteResponse.message()} ${deleteResponse.toString()}` + ); + } + } + }); + it('should return an undefined value for a key that doesnt exist', async () => { + const key = v4(); + const createResponse = await storageClient.createStore(testingStoreName); + switch (createResponse.type) { + // this is the expected response + case CreateStoreResponse.Success: { + break; + } + case CreateStoreResponse.AlreadyExists: { + break; + } + case CreateStoreResponse.Error: { + throw new Error( + `failed to create store, expected store to be able to happen, error: ${createResponse.message()} exception: ${createResponse.toString()}` + ); + } + } + const getResponse = await storageClient.get(testingStoreName, key); + expect(getResponse.type).toEqual(StorageGetResponse.Success); + expect(getResponse.value()).toBeUndefined(); + }); + it('should return store not found error for deleting a store that doesnt exist', async () => { + const storeName = testStoreName(); + const deleteResponse = await storageClient.deleteStore(storeName); + switch (deleteResponse.type) { + case DeleteStoreResponse.Error: { + expect(deleteResponse.errorCode()).toEqual( + MomentoErrorCode.STORE_NOT_FOUND_ERROR + ); + break; + } + default: { + throw new Error( + `expected StoreGetResponse.Error but got ${ + deleteResponse.type + } toString: ${deleteResponse.toString()}` + ); + } + } + }); + }); +} diff --git a/packages/common-integration-tests/src/topic-client.ts b/packages/common-integration-tests/src/topic-client.ts index cec409b6e..9d9ff6d30 100644 --- a/packages/common-integration-tests/src/topic-client.ts +++ b/packages/common-integration-tests/src/topic-client.ts @@ -7,7 +7,7 @@ import { TopicPublish, TopicSubscribe, SubscribeCallOptions, - NotFoundError, + CacheNotFoundError, } from '@gomomento/sdk-core'; import { expectWithMessage, @@ -52,7 +52,7 @@ export function runTopicClientTests( const response = await topicClient.publish(v4(), 'topic', 'value'); expectWithMessage(() => { expect((response as IResponseError).errorCode()).toEqual( - MomentoErrorCode.NOT_FOUND_ERROR + MomentoErrorCode.CACHE_NOT_FOUND_ERROR ); }, `expected NOT_FOUND_ERROR but got ${response.toString()}`); expect((response as IResponseError).message()).toContain( @@ -98,7 +98,7 @@ export function runTopicClientTests( ); expectWithMessage(() => { expect((response as IResponseError).errorCode()).toEqual( - MomentoErrorCode.NOT_FOUND_ERROR + MomentoErrorCode.CACHE_NOT_FOUND_ERROR ); }, `expected NOT_FOUND_ERROR but got ${response.toString()}`); }); @@ -250,7 +250,7 @@ export function runTopicClientTests( subscription: TopicSubscribe.Subscription ) => { expect(error.errorCode()).toEqual( - MomentoErrorCode.NOT_FOUND_ERROR + MomentoErrorCode.CACHE_NOT_FOUND_ERROR ); expect(subscription.isSubscribed).toBeFalse(); }, @@ -297,7 +297,7 @@ export function runTopicClientTests( it('should throw when publishing to a cache that does not exist', async () => { await expect(async () => { await topicClientWithThrowOnErrors.publish(v4(), 'topic', 'value'); - }).rejects.toThrow(NotFoundError); + }).rejects.toThrow(CacheNotFoundError); }); }); } diff --git a/packages/core/package-lock.json b/packages/core/package-lock.json index de64d99e4..50e8ecc3d 100644 --- a/packages/core/package-lock.json +++ b/packages/core/package-lock.json @@ -57,30 +57,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", - "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.24.5", - "@babel/helpers": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -96,12 +96,12 @@ } }, "node_modules/@babel/core/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -118,12 +118,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.24.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -133,13 +133,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -158,62 +158,66 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -223,86 +227,86 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dev": true, "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -383,9 +387,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -455,12 +459,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -557,12 +561,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", - "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -572,26 +576,26 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -599,19 +603,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -620,12 +624,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -642,13 +646,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -699,9 +703,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -740,6 +744,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.0", @@ -754,6 +759,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -1300,9 +1306,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" @@ -1606,10 +1612,25 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk/node_modules/acorn": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "dev": true, + "bin": { + "acorn": "bin/acorn" + }, "engines": { "node": ">=0.4.0" } @@ -2029,9 +2050,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, "funding": [ { @@ -2048,10 +2069,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "update-browserslist-db": "^1.0.16" }, "bin": { "browserslist": "cli.js" @@ -2148,9 +2169,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001617", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz", - "integrity": "sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==", + "version": "1.0.30001636", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", + "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", "dev": true, "funding": [ { @@ -2372,9 +2393,9 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2503,9 +2524,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.762", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.762.tgz", - "integrity": "sha512-rrFvGweLxPwwSwJOjIopy3Vr+J3cIPtZzuc74bmlvmBIgQO3VYJDvVrlj94iKZ3ukXUH64Ex31hSfRTLqvjYJQ==", + "version": "1.4.807", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.807.tgz", + "integrity": "sha512-kSmJl2ZwhNf/bcIuCH/imtNOKlpkLDn2jqT5FJ+/0CXjhnFaOa9cOe9gHKKy71eM49izwuQjZhKk+lWQ1JxB7A==", "dev": true }, "node_modules/emittery": { @@ -3470,6 +3491,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -3745,6 +3767,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -3834,12 +3857,15 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4649,12 +4675,12 @@ } }, "node_modules/jest-message-util/node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -5239,12 +5265,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -5603,9 +5629,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -5903,6 +5929,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -6298,9 +6325,9 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3", @@ -6463,9 +6490,9 @@ } }, "node_modules/ts-node/node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -6663,9 +6690,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz", - "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "funding": [ { @@ -6683,7 +6710,7 @@ ], "dependencies": { "escalade": "^3.1.2", - "picocolors": "^1.0.0" + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" diff --git a/packages/core/src/auth/credential-provider.ts b/packages/core/src/auth/credential-provider.ts index 6b1d5c43c..0f34e3b29 100644 --- a/packages/core/src/auth/credential-provider.ts +++ b/packages/core/src/auth/credential-provider.ts @@ -55,7 +55,7 @@ export abstract class CredentialProvider { abstract getControlEndpoint(): string; /** - * @returns {boolean} true if connecting to the control plane endpoint connection without TLS; false if using TLS + * @returns {boolean} true if connecting to the control plane endpoint connection with TLS; false if not using TLS */ abstract isControlEndpointSecure(): boolean; @@ -65,17 +65,27 @@ export abstract class CredentialProvider { abstract getCacheEndpoint(): string; /** - * @returns {boolean} true if connecting to the data plane endpoint connection without TLS; false if using TLS + * @returns {boolean} true if connecting to the data plane endpoint connection with TLS; false if not using TLS */ abstract isCacheEndpointSecure(): boolean; + /** + * @returns {string} The host which the Momento client will connect to for Momento storage operations + */ + abstract getStorageEndpoint(): string; + + /** + * @returns {boolean} true if connecting to the storage endpoint connection with TLS; false if not using TLS + */ + abstract isStorageEndpointSecure(): boolean; + /** * @returns {string} The host which the Momento client will connect to for Momento token operations */ abstract getTokenEndpoint(): string; /** - * @returns {boolean} true if connecting to the token endpoint connection without TLS; false if using TLS + * @returns {boolean} true if connecting to the token endpoint connection with TLS; false if not using TLS */ abstract isTokenEndpointSecure(): boolean; @@ -120,6 +130,10 @@ abstract class CredentialProviderBase implements CredentialProvider { abstract isControlEndpointSecure(): boolean; + abstract getStorageEndpoint(): string; + + abstract isStorageEndpointSecure(): boolean; + abstract getTokenEndpoint(): string; abstract isTokenEndpointSecure(): boolean; @@ -201,6 +215,11 @@ export class StringMomentoTokenProvider extends CredentialProviderBase { 'Malformed token; unable to determine token endpoint. Depending on the type of token you are using, you may need to specify the tokenEndpoint explicitly.' ); } + if (decodedToken.storageEndpoint === undefined) { + throw new Error( + 'Malformed token; unable to determine storage endpoint. Depending on the type of token you are using, you may need to specify the storageEndpoint explicitly.' + ); + } this.allEndpoints = { controlEndpoint: { endpoint: decodedToken.controlEndpoint, @@ -211,6 +230,9 @@ export class StringMomentoTokenProvider extends CredentialProviderBase { tokenEndpoint: { endpoint: decodedToken.tokenEndpoint, }, + storageEndpoint: { + endpoint: decodedToken.storageEndpoint, + }, }; } else if (isAllEndpoints(props.endpointOverrides)) { this.endpointsOverridden = true; @@ -265,6 +287,17 @@ export class StringMomentoTokenProvider extends CredentialProviderBase { return this.allEndpoints.tokenEndpoint.secureConnection; } + getStorageEndpoint(): string { + return this.allEndpoints.storageEndpoint.endpoint; + } + + isStorageEndpointSecure(): boolean { + if (this.allEndpoints.storageEndpoint.secureConnection === undefined) { + return true; + } + return this.allEndpoints.storageEndpoint.secureConnection; + } + areEndpointsOverridden(): boolean { return this.endpointsOverridden; } @@ -280,6 +313,7 @@ export class StringMomentoTokenProvider extends CredentialProviderBase { cacheEndpoint: momentoLocalOverride, controlEndpoint: momentoLocalOverride, tokenEndpoint: momentoLocalOverride, + storageEndpoint: momentoLocalOverride, }, }); } diff --git a/packages/core/src/clients/IStorageClient.ts b/packages/core/src/clients/IStorageClient.ts new file mode 100644 index 000000000..e3cb8cc64 --- /dev/null +++ b/packages/core/src/clients/IStorageClient.ts @@ -0,0 +1,38 @@ +import { + CreateStore, + ListStores, + DeleteStore, + StoragePut, + StorageGet, + StorageDelete, +} from '../index'; + +export interface IStorageClient { + createStore(storeName: string): Promise; + listStores(): Promise; + deleteStore(cache: string): Promise; + get(storeName: string, key: string): Promise; + putInt( + storeName: string, + key: string, + value: number + ): Promise; + putDouble( + storeName: string, + key: string, + value: number + ): Promise; + putString( + storeName: string, + key: string, + value: string + ): Promise; + putBytes( + storeName: string, + key: string, + value: Uint8Array + ): Promise; + delete(storeName: string, key: string): Promise; + + close(): void; +} diff --git a/packages/core/src/errors/errors.ts b/packages/core/src/errors/errors.ts index 0b6a56807..11c401e99 100644 --- a/packages/core/src/errors/errors.ts +++ b/packages/core/src/errors/errors.ts @@ -4,9 +4,19 @@ export enum MomentoErrorCode { // Service returned an unknown response UNKNOWN_SERVICE_ERROR = 'UNKNOWN_SERVICE_ERROR', // Cache with specified name already exists + CACHE_ALREADY_EXISTS_ERROR = 'ALREADY_EXISTS_ERROR', + /** @deprecated use CACHE_ALREADY_EXISTS_ERROR instead */ ALREADY_EXISTS_ERROR = 'ALREADY_EXISTS_ERROR', + // Store with specified name already exists + STORE_ALREADY_EXISTS_ERROR = 'ALREADY_EXISTS_ERROR', // Cache with specified name doesn't exist + CACHE_NOT_FOUND_ERROR = 'NOT_FOUND_ERROR', + /** @deprecated use CACHE_NOT_FOUND_ERROR instead */ NOT_FOUND_ERROR = 'NOT_FOUND_ERROR', + // Store with specified name doesn't exist + STORE_NOT_FOUND_ERROR = 'STORE_NOT_FOUND_ERROR', + // Item with specified key doesn't exist + STORE_ITEM_NOT_FOUND_ERROR = 'STORE_ITEM_NOT_FOUND_ERROR', // An unexpected error occurred while trying to fulfill the request INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', // Insufficient permissions to perform operation @@ -87,12 +97,23 @@ export abstract class SdkError extends Error { * either delete the existing cache and make a new one, or change the name of the cache you are trying to create to * one that doesn't already exist */ -export class AlreadyExistsError extends SdkError { - override _errorCode = MomentoErrorCode.ALREADY_EXISTS_ERROR; +export class CacheAlreadyExistsError extends SdkError { + override _errorCode = MomentoErrorCode.CACHE_ALREADY_EXISTS_ERROR; override _messageWrapper = 'A cache with the specified name already exists. To resolve this error, either delete the existing cache and make a new one, or use a different name'; } +/** + * Error that occurs when trying to create a store with the same name as an existing cache. To resolve this error, + * either delete the existing store and make a new one, or change the name of the store you are trying to create to + * one that doesn't already exist + */ +export class StoreAlreadyExistsError extends SdkError { + override _errorCode = MomentoErrorCode.STORE_ALREADY_EXISTS_ERROR; + override _messageWrapper = + 'A store with the specified name already exists. To resolve this error, either delete the existing store and make a new one, or use a different name'; +} + /** * Error when authentication with Cache Service fails */ @@ -166,12 +187,31 @@ export class LimitExceededError extends SdkError { * Error that occurs when trying to get a cache that doesn't exist. To resolve, make sure that the cache you are trying * to get exists. If it doesn't create it first and then try again */ -export class NotFoundError extends SdkError { - override _errorCode = MomentoErrorCode.NOT_FOUND_ERROR; +export class CacheNotFoundError extends SdkError { + override _errorCode = MomentoErrorCode.CACHE_NOT_FOUND_ERROR; override _messageWrapper = 'A cache with the specified name does not exist. To resolve this error, make sure you have created the cache before attempting to use it'; } +/** + * Error that occurs when trying to get a store that doesn't exist. To resolve, make sure that the store you are trying + * to get exists. If it doesn't create it first and then try again. + */ +export class StoreNotFoundError extends SdkError { + override _errorCode = MomentoErrorCode.STORE_NOT_FOUND_ERROR; + override _messageWrapper = + 'A store with the specified name does not exist. To resolve this error, make sure you have created the store before attempting to use it'; +} + +/** + * Error that occurs when trying to get an item from store that doesn't exist. To resolve, make sure that the item you are trying + * to get exists. If it doesn't create it first and then try again. + */ +export class StoreItemNotFoundError extends SdkError { + override _errorCode = MomentoErrorCode.STORE_ITEM_NOT_FOUND_ERROR; + override _messageWrapper = 'An item with the specified key does not exist'; +} + /** * Insufficient permissions to perform an operation on Cache Service */ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1fa6f769f..ec601212b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -79,6 +79,10 @@ export * as webhook from './messages/responses/webhook'; export * from './messages/responses/webhook'; export {Webhook, WebhookId} from './messages/webhook'; +// StoreClient Response Types +export * from './messages/responses/storage'; +export {StoreInfo} from './messages/store-info'; + import {CacheInfo} from './messages/cache-info'; import { SubscribeCallOptions, @@ -103,7 +107,8 @@ import { import { MomentoErrorCode, SdkError, - AlreadyExistsError, + CacheAlreadyExistsError, + StoreAlreadyExistsError, AuthenticationError, CancelledError, ConnectionError, @@ -116,7 +121,9 @@ import { TimeoutError, BadRequestError, PermissionError, - NotFoundError, + CacheNotFoundError, + StoreItemNotFoundError, + StoreNotFoundError, UnknownError, } from './errors'; @@ -145,6 +152,8 @@ export { IncrementOptions, } from './clients/ICacheClient'; +export {IStorageClient} from './clients/IStorageClient'; + export {IMomentoCache} from './clients/IMomentoCache'; export {ILeaderboardClient} from './clients/ILeaderboardClient'; @@ -290,7 +299,8 @@ export { // Errors MomentoErrorCode, SdkError, - AlreadyExistsError, + CacheAlreadyExistsError, + StoreAlreadyExistsError, AuthenticationError, CancelledError, ConnectionError, @@ -303,6 +313,8 @@ export { TimeoutError, BadRequestError, PermissionError, - NotFoundError, + CacheNotFoundError, + StoreItemNotFoundError, + StoreNotFoundError, UnknownError, }; diff --git a/packages/core/src/internal/clients/index.ts b/packages/core/src/internal/clients/index.ts index 852117b86..05cd007d0 100644 --- a/packages/core/src/internal/clients/index.ts +++ b/packages/core/src/internal/clients/index.ts @@ -2,3 +2,4 @@ export * from './cache'; export * from './auth'; export * from './pubsub'; export * from './leaderboard'; +export * from './storage'; diff --git a/packages/core/src/internal/clients/pubsub/AbstractPubsubClient.ts b/packages/core/src/internal/clients/pubsub/AbstractPubsubClient.ts index b3522e362..47e487b01 100644 --- a/packages/core/src/internal/clients/pubsub/AbstractPubsubClient.ts +++ b/packages/core/src/internal/clients/pubsub/AbstractPubsubClient.ts @@ -220,7 +220,7 @@ export abstract class AbstractPubsubClient // Another special case is when the cache is not found. // This happens here if the user deletes the cache in the middle of // a subscription. - if (momentoError.errorCode() === MomentoErrorCode.NOT_FOUND_ERROR) { + if (momentoError.errorCode() === MomentoErrorCode.CACHE_NOT_FOUND_ERROR) { this.logger.trace( 'Stream ended due to cache not found error on topic: %s', options.topicName diff --git a/packages/core/src/internal/clients/storage/AbstractStorageClient.ts b/packages/core/src/internal/clients/storage/AbstractStorageClient.ts new file mode 100644 index 000000000..09d1209c7 --- /dev/null +++ b/packages/core/src/internal/clients/storage/AbstractStorageClient.ts @@ -0,0 +1,91 @@ +import { + CreateStore, + DeleteStore, + ListStores, + StorageGet, + StoragePut, + StorageDelete, +} from '../../../index'; +import {IStorageDataClient} from './IStorageDataClient'; +import {IStorageClient} from '../../../clients/IStorageClient'; +import {IStorageControlClient} from './IStorageControlClient'; + +export abstract class AbstractStorageClient implements IStorageClient { + protected readonly dataClients: IStorageDataClient[]; + protected readonly controlClient: IStorageControlClient; + private nextDataClientIndex: number; + + protected constructor( + dataClients: IStorageDataClient[], + controlClient: IStorageControlClient + ) { + this.dataClients = dataClients; + this.controlClient = controlClient; + + // We round-robin the requests through all of our clients. Since javascript + // is single-threaded, we don't have to worry about thread safety on this + // index variable. + this.nextDataClientIndex = 0; + } + + createStore(storeName: string): Promise { + return this.controlClient.createStore(storeName); + } + + listStores(): Promise { + return this.controlClient.listStores(); + } + + deleteStore(storeName: string): Promise { + return this.controlClient.deleteStore(storeName); + } + + get(storeName: string, key: string): Promise { + return this.getNextDataClient().get(storeName, key); + } + + putInt( + storeName: string, + key: string, + value: number + ): Promise { + return this.getNextDataClient().putInt(storeName, key, value); + } + + putDouble( + storeName: string, + key: string, + value: number + ): Promise { + return this.getNextDataClient().putDouble(storeName, key, value); + } + + putString( + storeName: string, + key: string, + value: string + ): Promise { + return this.getNextDataClient().putString(storeName, key, value); + } + + putBytes( + storeName: string, + key: string, + value: Uint8Array + ): Promise { + return this.getNextDataClient().putBytes(storeName, key, value); + } + + delete(storeName: string, key: string): Promise { + return this.getNextDataClient().delete(storeName, key); + } + + private getNextDataClient(): IStorageDataClient { + const client = this.dataClients[this.nextDataClientIndex]; + this.nextDataClientIndex = + (this.nextDataClientIndex + 1) % this.dataClients.length; + return client; + } + + abstract close(): void; +} diff --git a/packages/core/src/internal/clients/storage/IStorageControlClient.ts b/packages/core/src/internal/clients/storage/IStorageControlClient.ts new file mode 100644 index 000000000..0c16aca5d --- /dev/null +++ b/packages/core/src/internal/clients/storage/IStorageControlClient.ts @@ -0,0 +1,8 @@ +import {CreateStore, DeleteStore, ListStores} from '../../../index'; + +export interface IStorageControlClient { + createStore(storeName: string): Promise; + deleteStore(storeName: string): Promise; + listStores(): Promise; + close(): void; +} diff --git a/packages/core/src/internal/clients/storage/IStorageDataClient.ts b/packages/core/src/internal/clients/storage/IStorageDataClient.ts new file mode 100644 index 000000000..ccc6c6c27 --- /dev/null +++ b/packages/core/src/internal/clients/storage/IStorageDataClient.ts @@ -0,0 +1,27 @@ +import {StorageGet, StoragePut, StorageDelete} from '../../../index'; + +export interface IStorageDataClient { + get(storeName: string, key: string): Promise; + putInt( + storeName: string, + key: string, + value: number + ): Promise; + putDouble( + storeName: string, + key: string, + value: number + ): Promise; + putString( + storeName: string, + key: string, + value: string + ): Promise; + putBytes( + storeName: string, + key: string, + value: Uint8Array + ): Promise; + delete(storeName: string, key: string): Promise; + close(): void; +} diff --git a/packages/core/src/internal/clients/storage/index.ts b/packages/core/src/internal/clients/storage/index.ts new file mode 100644 index 000000000..159d8a5da --- /dev/null +++ b/packages/core/src/internal/clients/storage/index.ts @@ -0,0 +1,4 @@ +export * from './AbstractStorageClient'; +export * from './IStorageControlClient'; +export * from './IStorageDataClient'; +export * from '../../../clients/IStorageClient'; diff --git a/packages/core/src/internal/utils/auth.ts b/packages/core/src/internal/utils/auth.ts index d18a1967d..41f3ab0c4 100644 --- a/packages/core/src/internal/utils/auth.ts +++ b/packages/core/src/internal/utils/auth.ts @@ -31,6 +31,7 @@ interface TokenAndEndpoints { controlEndpoint: string | undefined; cacheEndpoint: string | undefined; tokenEndpoint: string | undefined; + storageEndpoint: string | undefined; authToken: string; } @@ -43,6 +44,7 @@ export interface AllEndpoints { controlEndpoint: Endpoint; cacheEndpoint: Endpoint; tokenEndpoint: Endpoint; + storageEndpoint: Endpoint; } export function populateAllEndpointsFromBaseEndpoint( @@ -65,6 +67,10 @@ export function populateAllEndpointsFromBaseEndpoint( endpoint: `${prefix}token.${endpointOverride.baseEndpoint}`, secureConnection: endpointOverride.secureConnection, }, + storageEndpoint: { + endpoint: `${prefix}storage.${endpointOverride.baseEndpoint}`, + secureConnection: endpointOverride.secureConnection, + }, }; } @@ -97,6 +103,7 @@ export const decodeAuthToken = (token?: string): TokenAndEndpoints => { controlEndpoint: endpoints.controlEndpoint.endpoint, cacheEndpoint: endpoints.cacheEndpoint.endpoint, tokenEndpoint: endpoints.tokenEndpoint.endpoint, + storageEndpoint: endpoints.storageEndpoint.endpoint, authToken: base64DecodedToken.api_key, }; } else { @@ -108,6 +115,7 @@ export const decodeAuthToken = (token?: string): TokenAndEndpoints => { controlEndpoint: decodedLegacyToken.cp, cacheEndpoint: decodedLegacyToken.c, tokenEndpoint: decodedLegacyToken.c, + storageEndpoint: decodedLegacyToken.c, authToken: token, }; } diff --git a/packages/core/src/internal/utils/validators.ts b/packages/core/src/internal/utils/validators.ts index f349dbd40..5b3cbe603 100644 --- a/packages/core/src/internal/utils/validators.ts +++ b/packages/core/src/internal/utils/validators.ts @@ -2,6 +2,12 @@ import {InvalidArgumentError} from '../../errors'; import {ExpiresIn} from '../../utils'; import {decodeFromBase64, encodeToBase64} from './string'; +export function validateStoreName(name: string) { + if (isEmpty(name)) { + throw new InvalidArgumentError('store name must not be empty'); + } +} + export function validateCacheName(name: string) { if (isEmpty(name)) { throw new InvalidArgumentError('cache name must not be empty'); diff --git a/packages/core/src/messages/responses/enums/index.ts b/packages/core/src/messages/responses/enums/index.ts index 308364721..bdc8dc327 100644 --- a/packages/core/src/messages/responses/enums/index.ts +++ b/packages/core/src/messages/responses/enums/index.ts @@ -2,3 +2,4 @@ export * from './auth'; export * from './cache'; export * from './topics'; export * from './leaderboard'; +export * from './store'; diff --git a/packages/core/src/messages/responses/enums/store/control/index.ts b/packages/core/src/messages/responses/enums/store/control/index.ts new file mode 100644 index 000000000..ba52ca906 --- /dev/null +++ b/packages/core/src/messages/responses/enums/store/control/index.ts @@ -0,0 +1,15 @@ +export enum CreateStoreResponse { + Success = 'Success', + AlreadyExists = 'AlreadyExists', + Error = 'Error', +} + +export enum ListStoresResponse { + Success = 'Success', + Error = 'Error', +} + +export enum DeleteStoreResponse { + Success = 'Success', + Error = 'Error', +} diff --git a/packages/core/src/messages/responses/enums/store/index.ts b/packages/core/src/messages/responses/enums/store/index.ts new file mode 100644 index 000000000..418dcaf67 --- /dev/null +++ b/packages/core/src/messages/responses/enums/store/index.ts @@ -0,0 +1,2 @@ +export * from './control'; +export * from './scalar'; diff --git a/packages/core/src/messages/responses/enums/store/scalar/index.ts b/packages/core/src/messages/responses/enums/store/scalar/index.ts new file mode 100644 index 000000000..750382d84 --- /dev/null +++ b/packages/core/src/messages/responses/enums/store/scalar/index.ts @@ -0,0 +1,14 @@ +export enum StorageGetResponse { + Success = 'Success', + Error = 'Error', +} + +export enum StoragePutResponse { + Success = 'Success', + Error = 'Error', +} + +export enum StorageDeleteResponse { + Success = 'Success', + Error = 'Error', +} diff --git a/packages/core/src/messages/responses/storage/control/create-store.ts b/packages/core/src/messages/responses/storage/control/create-store.ts new file mode 100644 index 000000000..80f3a3c3d --- /dev/null +++ b/packages/core/src/messages/responses/storage/control/create-store.ts @@ -0,0 +1,42 @@ +import {CreateStoreResponse} from '../../enums'; +import {BaseResponseError, BaseResponseSuccess} from '../../response-base'; +import {SdkError} from '../../../../errors'; + +interface IResponse { + readonly type: CreateStoreResponse; +} + +/** + * Indicates a successful create store request. + */ +export class Success extends BaseResponseSuccess implements IResponse { + readonly type: CreateStoreResponse.Success = CreateStoreResponse.Success; +} + +/** + * Indicates that the store already exists. + */ +export class AlreadyExists extends BaseResponseSuccess implements IResponse { + readonly type: CreateStoreResponse.AlreadyExists = + CreateStoreResponse.AlreadyExists; +} + +/** + * Indicates that an error occurred during the create store request. + * + * This response object includes the following fields that you can use to determine + * how you would like to handle the error: + * + * - `errorCode()` - a unique Momento error code indicating the type of error that occurred. + * - `message()` - a human-readable description of the error + * - `innerException()` - the original error that caused the failure; can be re-thrown. + */ +export class Error extends BaseResponseError implements IResponse { + constructor(_innerException: SdkError) { + super(_innerException); + } + + readonly type: CreateStoreResponse.Error = CreateStoreResponse.Error; +} + +export type Response = Success | AlreadyExists | Error; diff --git a/packages/core/src/messages/responses/storage/control/delete-store.ts b/packages/core/src/messages/responses/storage/control/delete-store.ts new file mode 100644 index 000000000..a686cf301 --- /dev/null +++ b/packages/core/src/messages/responses/storage/control/delete-store.ts @@ -0,0 +1,34 @@ +import {DeleteStoreResponse} from '../../enums'; +import {BaseResponseError, BaseResponseSuccess} from '../../response-base'; +import {SdkError} from '../../../../errors'; + +interface IResponse { + readonly type: DeleteStoreResponse; +} + +/** + * Indicates a successful delete store request. + */ +export class Success extends BaseResponseSuccess implements IResponse { + readonly type: DeleteStoreResponse.Success = DeleteStoreResponse.Success; +} + +/** + * Indicates that an error occurred during the delete store request. + * + * This response object includes the following fields that you can use to determine + * how you would like to handle the error: + * + * - `errorCode()` - a unique Momento error code indicating the type of error that occurred. + * - `message()` - a human-readable description of the error + * - `innerException()` - the original error that caused the failure; can be re-thrown. + */ +export class Error extends BaseResponseError implements IResponse { + constructor(_innerException: SdkError) { + super(_innerException); + } + + readonly type: DeleteStoreResponse.Error = DeleteStoreResponse.Error; +} + +export type Response = Success | Error; diff --git a/packages/core/src/messages/responses/storage/control/index.ts b/packages/core/src/messages/responses/storage/control/index.ts new file mode 100644 index 000000000..51e44f1a1 --- /dev/null +++ b/packages/core/src/messages/responses/storage/control/index.ts @@ -0,0 +1,3 @@ +export * as DeleteStore from './delete-store'; +export * as CreateStore from './create-store'; +export * as ListStores from './list-stores'; diff --git a/packages/core/src/messages/responses/storage/control/list-stores.ts b/packages/core/src/messages/responses/storage/control/list-stores.ts new file mode 100644 index 000000000..b34a96574 --- /dev/null +++ b/packages/core/src/messages/responses/storage/control/list-stores.ts @@ -0,0 +1,54 @@ +import {ListStoresResponse} from '../../enums'; +import {BaseResponseError, BaseResponseSuccess} from '../../response-base'; +import {StoreInfo} from '../../../store-info'; +import {SdkError} from '../../../../errors'; + +interface IResponse { + readonly type: ListStoresResponse; +} + +/** + * Indicates a successful list stores request. + */ +export class Success extends BaseResponseSuccess implements IResponse { + readonly type: ListStoresResponse.Success = ListStoresResponse.Success; + private readonly _stores: StoreInfo[]; + + constructor(stores: StoreInfo[]) { + super(); + this._stores = stores; + } + + /** + * An array of StoreInfo, containing information about each store. + * @returns {StoreInfo[]} + */ + public stores(): StoreInfo[] { + return this._stores; + } + + public override toString() { + const _stores = this._stores.map(storeInfo => storeInfo.getName()); + return super.toString() + ': ' + _stores.join(', '); + } +} + +/** + * Indicates that an error occurred during the list stores request. + * + * This response object includes the following fields that you can use to determine + * how you would like to handle the error: + * + * - `errorCode()` - a unique Momento error code indicating the type of error that occurred. + * - `message()` - a human-readable description of the error + * - `innerException()` - the original error that caused the failure; can be re-thrown. + */ +export class Error extends BaseResponseError implements IResponse { + constructor(_innerException: SdkError) { + super(_innerException); + } + + readonly type: ListStoresResponse.Error = ListStoresResponse.Error; +} + +export type Response = Success | Error; diff --git a/packages/core/src/messages/responses/storage/index.ts b/packages/core/src/messages/responses/storage/index.ts new file mode 100644 index 000000000..418dcaf67 --- /dev/null +++ b/packages/core/src/messages/responses/storage/index.ts @@ -0,0 +1,2 @@ +export * from './control'; +export * from './scalar'; diff --git a/packages/core/src/messages/responses/storage/scalar/index.ts b/packages/core/src/messages/responses/storage/scalar/index.ts new file mode 100644 index 000000000..b333eec6b --- /dev/null +++ b/packages/core/src/messages/responses/storage/scalar/index.ts @@ -0,0 +1,3 @@ +export * as StorageDelete from './storage-delete'; +export * as StorageGet from './storage-get'; +export * as StoragePut from './storage-put'; diff --git a/packages/core/src/messages/responses/storage/scalar/storage-delete.ts b/packages/core/src/messages/responses/storage/scalar/storage-delete.ts new file mode 100644 index 000000000..e6f4426dd --- /dev/null +++ b/packages/core/src/messages/responses/storage/scalar/storage-delete.ts @@ -0,0 +1,34 @@ +import {StorageDeleteResponse} from '../../enums'; +import {BaseResponseError, BaseResponseSuccess} from '../../response-base'; +import {SdkError} from '../../../../errors'; + +interface IResponse { + readonly type: StorageDeleteResponse; +} + +/** + * Indicates a Successful store delete request. + */ +export class Success extends BaseResponseSuccess implements IResponse { + readonly type: StorageDeleteResponse.Success = StorageDeleteResponse.Success; +} + +/** + * Indicates that an error occurred during the store delete request. + * + * This response object includes the following fields that you can use to determine + * how you would like to handle the error: + * + * - `errorCode()` - a unique Momento error code indicating the type of error that occurred. + * - `message()` - a human-readable description of the error + * - `innerException()` - the original error that caused the failure; can be re-thrown. + */ +export class Error extends BaseResponseError implements IResponse { + constructor(_innerException: SdkError) { + super(_innerException); + } + + readonly type: StorageDeleteResponse.Error = StorageDeleteResponse.Error; +} + +export type Response = Success | Error; diff --git a/packages/core/src/messages/responses/storage/scalar/storage-get.ts b/packages/core/src/messages/responses/storage/scalar/storage-get.ts new file mode 100644 index 000000000..b4fc4b8a9 --- /dev/null +++ b/packages/core/src/messages/responses/storage/scalar/storage-get.ts @@ -0,0 +1,98 @@ +import {StorageGetResponse} from '../../enums'; +import {BaseResponseError, ResponseBase} from '../../response-base'; +import {SdkError} from '../../../../errors'; +import {StorageValue} from './storage-value'; + +interface IResponse { + readonly type: StorageGetResponse; + value(): StorageValue | undefined; +} + +/** + * Indicates that the store get request was successful. + * + * This response object includes the following fields that you can use to determine + * how you would like to handle the successful response: + * + * - `value()` - the value associated with the key that was retrieved from the cache. + */ +export class Success extends ResponseBase implements IResponse { + readonly type: StorageGetResponse.Success = StorageGetResponse.Success; + private readonly _value: StorageValue | undefined; + + /** + * Creates an instance of the Success response. + * @param {StorageValue | undefined} value - The value associated with the key that was retrieved from the cache. + */ + constructor(value: StorageValue | undefined) { + super(); + this._value = value; + } + + /** + * Creates a Success response with an integer value. + * @param {number} value - The integer value to be stored. + * @returns {Success} - A Success response object containing the integer value. + */ + static ofInt(value: number): Success { + return new Success(StorageValue.ofInt(value)); + } + + /** + * Creates a Success response with a double value. + * @param {number} value - The double value to be stored. + * @returns {Success} - A Success response object containing the double value. + */ + static ofDouble(value: number): Success { + return new Success(StorageValue.ofDouble(value)); + } + + /** + * Creates a Success response with a string value. + * @param {string} value - The string value to be stored. + * @returns {Success} - A Success response object containing the string value. + */ + static ofString(value: string): Success { + return new Success(StorageValue.ofString(value)); + } + + /** + * Creates a Success response with a byte array value. + * @param {Uint8Array} value - The byte array value to be stored. + * @returns {Success} - A Success response object containing the byte array value. + */ + static ofBytes(value: Uint8Array): Success { + return new Success(StorageValue.ofBytes(value)); + } + + /** + * Retrieves the value associated with the key that was retrieved from the cache. + * @returns {StorageValue | undefined} - The value associated with the key, or undefined if no value is present. + */ + value(): StorageValue | undefined { + return this._value; + } +} + +/** + * Indicates that an error occurred during the cache get request. + * + * This response object includes the following fields that you can use to determine + * how you would like to handle the error: + * + * - `errorCode()` - a unique Momento error code indicating the type of error that occurred. + * - `message()` - a human-readable description of the error + * - `innerException()` - the original error that caused the failure; can be re-thrown. + */ +export class Error extends BaseResponseError implements IResponse { + readonly type: StorageGetResponse.Error = StorageGetResponse.Error; + constructor(_innerException: SdkError) { + super(_innerException); + } + + value(): undefined { + return undefined; + } +} + +export type Response = Success | Error; diff --git a/packages/core/src/messages/responses/storage/scalar/storage-put.ts b/packages/core/src/messages/responses/storage/scalar/storage-put.ts new file mode 100644 index 000000000..b9184860b --- /dev/null +++ b/packages/core/src/messages/responses/storage/scalar/storage-put.ts @@ -0,0 +1,34 @@ +import {StoragePutResponse} from '../../enums'; +import {BaseResponseError, BaseResponseSuccess} from '../../response-base'; +import {SdkError} from '../../../../errors'; + +interface IResponse { + readonly type: StoragePutResponse; +} + +/** + * Indicates a Successful store set request. + */ +export class Success extends BaseResponseSuccess implements IResponse { + readonly type: StoragePutResponse.Success = StoragePutResponse.Success; +} + +/** + * Indicates that an error occurred during the store set request. + * + * This response object includes the following fields that you can use to determine + * how you would like to handle the error: + * + * - `errorCode()` - a unique Momento error code indicating the type of error that occurred. + * - `message()` - a human-readable description of the error + * - `innerException()` - the original error that caused the failure; can be re-thrown. + */ +export class Error extends BaseResponseError implements IResponse { + constructor(_innerException: SdkError) { + super(_innerException); + } + + readonly type: StoragePutResponse.Error = StoragePutResponse.Error; +} + +export type Response = Success | Error; diff --git a/packages/core/src/messages/responses/storage/scalar/storage-value.ts b/packages/core/src/messages/responses/storage/scalar/storage-value.ts new file mode 100644 index 000000000..a071a9ce9 --- /dev/null +++ b/packages/core/src/messages/responses/storage/scalar/storage-value.ts @@ -0,0 +1,93 @@ +/** + * Represents a value stored in the cache, which can be an integer, double, string, or byte array. + */ +export class StorageValue { + private readonly _valueInt: number | undefined = undefined; + private readonly _valueDouble: number | undefined = undefined; + private readonly _valueString: string | undefined = undefined; + private readonly _valueBytes: Uint8Array | undefined = undefined; + + /** + * Creates an instance of the StorageValue class. + * @param {number | undefined} valueInt - The integer value to be stored. + * @param {number | undefined} valueDouble - The double value to be stored. + * @param {string | undefined} valueString - The string value to be stored. + * @param {Uint8Array | undefined} valueBytes - The byte array value to be stored. + */ + constructor( + valueInt: number | undefined, + valueDouble: number | undefined, + valueString: string | undefined, + valueBytes: Uint8Array | undefined + ) { + this._valueInt = valueInt; + this._valueDouble = valueDouble; + this._valueString = valueString; + this._valueBytes = valueBytes; + } + + /** + * Creates a StorageValue instance with an integer value. + * @param {number} value - The integer value to be stored. + * @returns {StorageValue} - A StorageValue instance containing the integer value. + */ + static ofInt(value: number): StorageValue { + return new StorageValue(value, undefined, undefined, undefined); + } + + /** + * Creates a StorageValue instance with a double value. + * @param {number} value - The double value to be stored. + * @returns {StorageValue} - A StorageValue instance containing the double value. + */ + static ofDouble(value: number): StorageValue { + return new StorageValue(undefined, value, undefined, undefined); + } + + /** + * Creates a StorageValue instance with a string value. + * @param {string} value - The string value to be stored. + * @returns {StorageValue} - A StorageValue instance containing the string value. + */ + static ofString(value: string): StorageValue { + return new StorageValue(undefined, undefined, value, undefined); + } + + /** + * Creates a StorageValue instance with a byte array value. + * @param {Uint8Array} value - The byte array value to be stored. + * @returns {StorageValue} - A StorageValue instance containing the byte array value. + */ + static ofBytes(value: Uint8Array): StorageValue { + return new StorageValue(undefined, undefined, undefined, value); + } + + /** + * Retrieves the integer value stored in this instance. + * @returns {number | undefined} - The integer value, or undefined if no integer value is present. + */ + int(): number | undefined { + return this._valueInt; + } + /** + * Retrieves the double value stored in this instance. + * @returns {number | undefined} - The double value, or undefined if no double value is present. + */ + double(): number | undefined { + return this._valueDouble; + } + /** + * Retrieves the string value stored in this instance. + * @returns {string | undefined} - The string value, or undefined if no string value is present. + */ + string(): string | undefined { + return this._valueString; + } + /** + * Retrieves the byte array value stored in this instance. + * @returns {Uint8Array | undefined} - The byte array value, or undefined if no byte array value is present. + */ + bytes(): Uint8Array | undefined { + return this._valueBytes; + } +} diff --git a/packages/core/src/messages/store-info.ts b/packages/core/src/messages/store-info.ts new file mode 100644 index 000000000..aad70186f --- /dev/null +++ b/packages/core/src/messages/store-info.ts @@ -0,0 +1,22 @@ +/** + * StoreInfo is a class that holds the name of the store. + */ +export class StoreInfo { + private readonly name: string; + + /** + * Creates an instance of the StoreInfo class. + * @param {string} name - The name of the store. + */ + constructor(name: string) { + this.name = name; + } + + /** + * Retrieves the name of the store. + * @returns {string} - The name of the store. + */ + public getName(): string { + return this.name; + } +} diff --git a/packages/core/test/unit/auth/credential-provider.test.ts b/packages/core/test/unit/auth/credential-provider.test.ts index 272790d7b..ac3bfa61b 100644 --- a/packages/core/test/unit/auth/credential-provider.test.ts +++ b/packages/core/test/unit/auth/credential-provider.test.ts @@ -1,6 +1,8 @@ -import {CredentialProvider} from '../../../src/auth/credential-provider'; -import {Base64DecodedV1Token} from '../../../src/internal/utils/auth'; -import {encodeToBase64} from '../../../src/internal/utils/string'; +import {CredentialProvider} from '../../../src'; +import { + Base64DecodedV1Token, + encodeToBase64, +} from '../../../src/internal/utils'; // These tokens have valid syntax, but they don't actually have valid credentials. Just used for unit testing. const fakeTestLegacyToken = @@ -89,11 +91,13 @@ describe('StringMomentoTokenProvider', () => { controlEndpoint: {endpoint: 'control.foo'}, cacheEndpoint: {endpoint: 'cache.foo'}, tokenEndpoint: {endpoint: 'token.foo'}, + storageEndpoint: {endpoint: 'storage.foo'}, }, }); expect(legacyAuthProvider.getControlEndpoint()).toEqual('control.foo'); expect(legacyAuthProvider.getCacheEndpoint()).toEqual('cache.foo'); expect(legacyAuthProvider.getTokenEndpoint()).toEqual('token.foo'); + expect(legacyAuthProvider.getStorageEndpoint()).toEqual('storage.foo'); expect(legacyAuthProvider.areEndpointsOverridden()).toEqual(true); const v1AuthProvider = CredentialProvider.fromString({ @@ -102,12 +106,14 @@ describe('StringMomentoTokenProvider', () => { controlEndpoint: {endpoint: 'control.foo'}, cacheEndpoint: {endpoint: 'cache.foo'}, tokenEndpoint: {endpoint: 'token.foo'}, + storageEndpoint: {endpoint: 'storage.foo'}, }, }); expect(v1AuthProvider.getAuthToken()).toEqual(fakeTestV1ApiKey); expect(v1AuthProvider.getControlEndpoint()).toEqual('control.foo'); expect(v1AuthProvider.getCacheEndpoint()).toEqual('cache.foo'); expect(v1AuthProvider.getTokenEndpoint()).toEqual('token.foo'); + expect(v1AuthProvider.getStorageEndpoint()).toEqual('storage.foo'); expect(v1AuthProvider.areEndpointsOverridden()).toEqual(true); }); @@ -118,12 +124,14 @@ describe('StringMomentoTokenProvider', () => { controlEndpoint: {endpoint: 'control.foo'}, cacheEndpoint: {endpoint: 'cache.foo'}, tokenEndpoint: {endpoint: 'token.foo'}, + storageEndpoint: {endpoint: 'storage.foo'}, }, }); expect(sessionTokenProvider.getAuthToken()).toEqual(fakeSessionToken); expect(sessionTokenProvider.getControlEndpoint()).toEqual('control.foo'); expect(sessionTokenProvider.getCacheEndpoint()).toEqual('cache.foo'); expect(sessionTokenProvider.getTokenEndpoint()).toEqual('token.foo'); + expect(sessionTokenProvider.getStorageEndpoint()).toEqual('storage.foo'); expect(sessionTokenProvider.areEndpointsOverridden()).toEqual(true); }); @@ -205,12 +213,14 @@ describe('EnvMomentoTokenProvider', () => { controlEndpoint: {endpoint: 'control.foo'}, cacheEndpoint: {endpoint: 'cache.foo'}, tokenEndpoint: {endpoint: 'token.foo'}, + storageEndpoint: {endpoint: 'storage.foo'}, }, }); expect(legacyAuthProvider.getAuthToken()).toEqual(fakeTestLegacyToken); expect(legacyAuthProvider.getControlEndpoint()).toEqual('control.foo'); expect(legacyAuthProvider.getCacheEndpoint()).toEqual('cache.foo'); expect(legacyAuthProvider.getTokenEndpoint()).toEqual('token.foo'); + expect(legacyAuthProvider.getStorageEndpoint()).toEqual('storage.foo'); expect(legacyAuthProvider.areEndpointsOverridden()).toEqual(true); process.env[testEnvVarName] = base64EncodedFakeV1AuthToken; @@ -220,12 +230,14 @@ describe('EnvMomentoTokenProvider', () => { controlEndpoint: {endpoint: 'control.foo'}, cacheEndpoint: {endpoint: 'cache.foo'}, tokenEndpoint: {endpoint: 'token.foo'}, + storageEndpoint: {endpoint: 'storage.foo'}, }, }); expect(v1AuthProvider.getAuthToken()).toEqual(fakeTestV1ApiKey); expect(v1AuthProvider.getControlEndpoint()).toEqual('control.foo'); expect(v1AuthProvider.getCacheEndpoint()).toEqual('cache.foo'); expect(v1AuthProvider.getTokenEndpoint()).toEqual('token.foo'); + expect(v1AuthProvider.getStorageEndpoint()).toEqual('storage.foo'); expect(v1AuthProvider.areEndpointsOverridden()).toEqual(true); });