diff --git a/package.json b/package.json index 3a3d9005..d82a0dc0 100644 --- a/package.json +++ b/package.json @@ -11,17 +11,17 @@ "devDependencies": { "@commitlint/cli": "19.5.0", "@commitlint/config-conventional": "19.5.0", - "@eslint/js": "9.11.0", - "@nestjs/common": "10.4.3", - "@nestjs/core": "10.4.3", - "@nestjs/platform-fastify": "10.4.3", - "@nestjs/testing": "10.4.3", + "@eslint/js": "9.11.1", + "@nestjs/common": "10.4.4", + "@nestjs/core": "10.4.4", + "@nestjs/platform-fastify": "10.4.4", + "@nestjs/testing": "10.4.4", "@tsconfig/node20": "20.1.4", "@types/eslint__js": "8.42.3", "@types/jest": "29.5.13", - "@types/node": "20.16.5", + "@types/node": "20.16.7", "concurrently": "9.0.1", - "eslint": "9.11.0", + "eslint": "9.11.1", "eslint-config-prettier": "9.1.0", "eslint-plugin-jest": "28.8.3", "eslint-plugin-prettier": "5.2.1", @@ -39,7 +39,7 @@ "ts-jest": "29.2.5", "tsc-alias": "1.8.10", "typescript": "5.6.2", - "typescript-eslint": "8.6.0" + "typescript-eslint": "8.7.0" }, "engines": { "node": ">=20", diff --git a/packages/redis/.lintstagedrc.mjs b/packages/redis/.lintstagedrc.mjs__ similarity index 100% rename from packages/redis/.lintstagedrc.mjs rename to packages/redis/.lintstagedrc.mjs__ diff --git a/packages/redis/README.md b/packages/redis/README.md index 82c03957..f969ffa5 100644 --- a/packages/redis/README.md +++ b/packages/redis/README.md @@ -60,7 +60,7 @@ - **Both redis & cluster are supported**: You can also specify multiple instances. - **Health**: Checks health of **redis & cluster** server. - **Rigorously tested**: With 100+ tests and 100% code coverage. -- **Services**: Retrieves **redis & cluster** clients via `RedisManager`, `ClusterManager`. +- **Services**: Retrieves **redis & cluster** connection via `RedisService`, `ClusterService`. ### Test coverage @@ -76,8 +76,8 @@ This lib requires **Node.js >=16.13.0**, **NestJS ^10.0.0**, **ioredis ^5.0.0**. - If you depend on **ioredis 5** & **NestJS 10**, please use version **10** of the lib. - If you depend on **ioredis 5** & **NestJS 9**, please use version **9** of the lib. -- If you depend on **ioredis 4**, please use [version 7](https://github.com/liaoliaots/nestjs-redis/tree/v7.0.0) of the lib. - If you depend on **ioredis 5**, **NestJS 7** or **8**, please use [version 8](https://github.com/liaoliaots/nestjs-redis/tree/v8.2.2) of the lib. +- If you depend on **ioredis 4**, please use [version 7](https://github.com/liaoliaots/nestjs-redis/tree/v7.0.0) of the lib. ### Installation @@ -111,8 +111,9 @@ pnpm add @liaoliaots/nestjs-redis ioredis ### Legacy -- version 7, [click here](/docs/v7) +- version 9, [click here](/docs/v9) - version 8, [click here](/docs/v8) +- version 7, [click here](/docs/v7) ## FAQs @@ -125,97 +126,6 @@ pnpm add @liaoliaots/nestjs-redis ioredis -### "Cannot resolve dependency" error - -
- Click to expand - -If you encountered an error like this: - -``` -Nest can't resolve dependencies of the (?). Please make sure that the argument at index [] is available in the context. - -Potential solutions: -- If is a provider, is it part of the current ? -- If is exported from a separate @Module, is that module imported within ? - @Module({ - imports: [ /* the Module containing */ ] - }) -``` - -Please make sure that the `RedisModule` is added directly to the `imports` array of `@Module()` decorator of "Root Module"(if `isGlobal` is true) or "Feature Module"(if `isGlobal` is false). - -Examples of code: - -```ts -// redis-config.service.ts -import { Injectable } from '@nestjs/common'; -import { RedisModuleOptions, RedisOptionsFactory } from '@liaoliaots/nestjs-redis'; - -@Injectable() -export class RedisConfigService implements RedisOptionsFactory { - createRedisOptions(): RedisModuleOptions { - return { - readyLog: true, - config: { - host: 'localhost', - port: 6379, - password: 'authpassword' - } - }; - } -} -``` - -### ✅ Correct - -```ts -// app.module.ts -import { Module } from '@nestjs/common'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { RedisConfigService } from './redis-config.service'; - -@Module({ - imports: [ - RedisModule.forRootAsync({ - useClass: RedisConfigService - }) - ] -}) -export class AppModule {} -``` - -### ❌ Incorrect - -```ts -// my-redis.module.ts -import { Module } from '@nestjs/common'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { RedisConfigService } from './redis-config.service'; - -@Module({ - imports: [ - RedisModule.forRootAsync({ - useClass: RedisConfigService - }) - ] -}) -export class MyRedisModule {} -``` - -```ts -// app.module.ts -import { Module } from '@nestjs/common'; -import { MyRedisModule } from './my-redis.module'; - -@Module({ - imports: [MyRedisModule] -}) -export class AppModule {} -``` - -
- ## Contributing Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. diff --git a/packages/redis/lib/cluster/cluster-manager.ts b/packages/redis/lib/cluster/cluster-manager.ts deleted file mode 100644 index 0aa9d9a8..00000000 --- a/packages/redis/lib/cluster/cluster-manager.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import type { Cluster } from 'ioredis'; -import { CLUSTER_CLIENTS, DEFAULT_CLUSTER_NAMESPACE } from './cluster.constants'; -import { ClusterClients } from './interfaces'; -import { parseNamespace } from '@/utils'; -import { ClientNamespace } from '@/interfaces'; -import { ClientNotFoundError } from '@/errors'; - -/** - * Manager for cluster clients. - * - * @public - */ -@Injectable() -export class ClusterManager { - constructor(@Inject(CLUSTER_CLIENTS) private readonly clusterClients: ClusterClients) {} - - /** - * Retrieves all cluster clients. - */ - get clients(): ReadonlyMap { - return this.clusterClients; - } - - /** - * Retrieves a cluster client by namespace. - */ - getClient(namespace: ClientNamespace = DEFAULT_CLUSTER_NAMESPACE): Cluster { - const client = this.clusterClients.get(namespace); - if (!client) throw new ClientNotFoundError(parseNamespace(namespace), 'cluster'); - return client; - } -} diff --git a/packages/redis/lib/cluster/cluster.constants.ts b/packages/redis/lib/cluster/cluster.constants.ts index 81a83022..323a5936 100644 --- a/packages/redis/lib/cluster/cluster.constants.ts +++ b/packages/redis/lib/cluster/cluster.constants.ts @@ -4,7 +4,10 @@ export const CLUSTER_MERGED_OPTIONS = Symbol(); export const CLUSTER_CLIENTS = Symbol(); -export const DEFAULT_CLUSTER_NAMESPACE = 'default'; +/** + * The default cluster namespace. + */ +export const DEFAULT_CLUSTER = 'default'; export const CLUSTER_MODULE_ID = 'ClusterModule'; diff --git a/packages/redis/lib/cluster/cluster.module.ts b/packages/redis/lib/cluster/cluster.module.ts index 84e19eb1..b2636db6 100644 --- a/packages/redis/lib/cluster/cluster.module.ts +++ b/packages/redis/lib/cluster/cluster.module.ts @@ -1,64 +1,63 @@ import { Module, DynamicModule, Provider, OnApplicationShutdown } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { ClusterModuleOptions, ClusterModuleAsyncOptions, ClusterClients } from './interfaces'; -import { ClusterManager } from './cluster-manager'; +import { ClusterService } from './cluster.service'; import { createOptionsProvider, createAsyncProviders, - createClusterClientProviders, clusterClientsProvider, mergedOptionsProvider } from './cluster.providers'; import { CLUSTER_CLIENTS, CLUSTER_MERGED_OPTIONS } from './cluster.constants'; -import { destroy } from './common'; -import { parseNamespace, isResolution, isRejection, isError } from '@/utils'; +import { parseNamespace, isError } from '@/utils'; import { logger } from './cluster-logger'; import { MissingConfigurationsError } from '@/errors'; import { ERROR_LOG } from '@/messages'; -/** - * @public - */ @Module({}) export class ClusterModule implements OnApplicationShutdown { constructor(private moduleRef: ModuleRef) {} /** * Registers the module synchronously. + * + * @param options - The module options + * @param isGlobal - Register in the global scope + * @returns A DynamicModule */ static forRoot(options: ClusterModuleOptions, isGlobal = true): DynamicModule { - const clusterClientProviders = createClusterClientProviders(); const providers: Provider[] = [ createOptionsProvider(options), clusterClientsProvider, mergedOptionsProvider, - ClusterManager, - ...clusterClientProviders + ClusterService ]; return { global: isGlobal, module: ClusterModule, providers, - exports: [ClusterManager, ...clusterClientProviders] + exports: [ClusterService] }; } /** * Registers the module asynchronously. + * + * @param options - The async module options + * @param isGlobal - Register in the global scope + * @returns A DynamicModule */ static forRootAsync(options: ClusterModuleAsyncOptions, isGlobal = true): DynamicModule { if (!options.useFactory && !options.useClass && !options.useExisting) { throw new MissingConfigurationsError(); } - const clusterClientProviders = createClusterClientProviders(); const providers: Provider[] = [ ...createAsyncProviders(options), clusterClientsProvider, mergedOptionsProvider, - ClusterManager, - ...clusterClientProviders, + ClusterService, ...(options.extraProviders ?? []) ]; @@ -67,19 +66,26 @@ export class ClusterModule implements OnApplicationShutdown { module: ClusterModule, imports: options.imports, providers, - exports: [ClusterManager, ...clusterClientProviders] + exports: [ClusterService] }; } async onApplicationShutdown(): Promise { - const { closeClient } = this.moduleRef.get(CLUSTER_MERGED_OPTIONS); + const { closeClient } = this.moduleRef.get(CLUSTER_MERGED_OPTIONS, { strict: false }); if (closeClient) { - const results = await destroy(this.moduleRef.get(CLUSTER_CLIENTS)); - results.forEach(([namespace, quit]) => { - if (isResolution(namespace) && isRejection(quit) && isError(quit.reason)) { - logger.error(ERROR_LOG(parseNamespace(namespace.value), quit.reason.message), quit.reason.stack); + const clients = this.moduleRef.get(CLUSTER_CLIENTS, { strict: false }); + for (const [namespace, client] of clients) { + if (client.status === 'end') continue; + if (client.status === 'ready') { + try { + await client.quit(); + } catch (e) { + if (isError(e)) logger.error(ERROR_LOG(parseNamespace(namespace), e.message), e.stack); + } + continue; } - }); + client.disconnect(); + } } } } diff --git a/packages/redis/lib/cluster/cluster.providers.ts b/packages/redis/lib/cluster/cluster.providers.ts index 71ee2e02..b820a889 100644 --- a/packages/redis/lib/cluster/cluster.providers.ts +++ b/packages/redis/lib/cluster/cluster.providers.ts @@ -1,14 +1,7 @@ import { Provider, FactoryProvider, ValueProvider } from '@nestjs/common'; -import type { Cluster } from 'ioredis'; import { ClusterModuleOptions, ClusterModuleAsyncOptions, ClusterOptionsFactory, ClusterClients } from './interfaces'; -import { - CLUSTER_OPTIONS, - CLUSTER_CLIENTS, - DEFAULT_CLUSTER_NAMESPACE, - CLUSTER_MERGED_OPTIONS -} from './cluster.constants'; -import { createClient, namespaces } from './common'; -import { ClusterManager } from './cluster-manager'; +import { CLUSTER_OPTIONS, CLUSTER_CLIENTS, DEFAULT_CLUSTER, CLUSTER_MERGED_OPTIONS } from './cluster.constants'; +import { createClient } from './common'; import { defaultClusterModuleOptions } from './default-options'; export const createOptionsProvider = (options: ClusterModuleOptions): ValueProvider => ({ @@ -67,18 +60,6 @@ export const createAsyncOptionsProvider = (options: ClusterModuleAsyncOptions): }; }; -export const createClusterClientProviders = (): FactoryProvider[] => { - const providers: FactoryProvider[] = []; - namespaces.forEach((token, namespace) => { - providers.push({ - provide: token, - useFactory: (clusterManager: ClusterManager) => clusterManager.getClient(namespace), - inject: [ClusterManager] - }); - }); - return providers; -}; - export const clusterClientsProvider: FactoryProvider = { provide: CLUSTER_CLIENTS, useFactory: (options: ClusterModuleOptions) => { @@ -86,13 +67,13 @@ export const clusterClientsProvider: FactoryProvider = { if (Array.isArray(options.config)) { options.config.forEach(item => clients.set( - item.namespace ?? DEFAULT_CLUSTER_NAMESPACE, + item.namespace ?? DEFAULT_CLUSTER, createClient(item, { readyLog: options.readyLog, errorLog: options.errorLog }) ) ); } else if (options.config) { clients.set( - options.config.namespace ?? DEFAULT_CLUSTER_NAMESPACE, + options.config.namespace ?? DEFAULT_CLUSTER, createClient(options.config, { readyLog: options.readyLog, errorLog: options.errorLog }) ); } diff --git a/packages/redis/lib/cluster/cluster-manager.spec.ts b/packages/redis/lib/cluster/cluster.service.spec.ts similarity index 100% rename from packages/redis/lib/cluster/cluster-manager.spec.ts rename to packages/redis/lib/cluster/cluster.service.spec.ts diff --git a/packages/redis/lib/cluster/cluster.service.ts b/packages/redis/lib/cluster/cluster.service.ts new file mode 100644 index 00000000..efabf69c --- /dev/null +++ b/packages/redis/lib/cluster/cluster.service.ts @@ -0,0 +1,40 @@ +import { Injectable, Inject } from '@nestjs/common'; +import type { Cluster } from 'ioredis'; +import { CLUSTER_CLIENTS, DEFAULT_CLUSTER } from './cluster.constants'; +import { ClusterClients } from './interfaces'; +import { parseNamespace } from '@/utils'; +import { Namespace } from '@/interfaces'; +import { ClientNotFoundError } from '@/errors'; + +/** + * Manager for cluster connections. + */ +@Injectable() +export class ClusterService { + constructor(@Inject(CLUSTER_CLIENTS) private readonly clients: ClusterClients) {} + + /** + * Retrieves a cluster connection by namespace. + * However, if the query does not find a connection, it returns ClientNotFoundError: No Connection found error. + * + * @param namespace - The namespace + * @returns A cluster connection + */ + getOrThrow(namespace: Namespace = DEFAULT_CLUSTER): Cluster { + const client = this.clients.get(namespace); + if (!client) throw new ClientNotFoundError(parseNamespace(namespace)); + return client; + } + + /** + * Retrieves a cluster connection by namespace, if the query does not find a connection, it returns `null`; + * + * @param namespace - The namespace + * @returns A cluster connection or nil + */ + getOrNil(namespace: Namespace = DEFAULT_CLUSTER): Cluster | null { + const client = this.clients.get(namespace); + if (!client) return null; + return client; + } +} diff --git a/packages/redis/lib/cluster/common/cluster.decorator.spec.ts b/packages/redis/lib/cluster/common/cluster.decorator.spec.ts deleted file mode 100644 index 23224872..00000000 --- a/packages/redis/lib/cluster/common/cluster.decorator.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Injectable, ValueProvider } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { InjectCluster, namespaces, getClusterToken } from './cluster.decorator'; -import { DEFAULT_CLUSTER_NAMESPACE, CLUSTER_MODULE_ID } from '../cluster.constants'; -import { ClientNamespace } from '@/interfaces'; - -describe('namespaces', () => { - test('should be an instance of the map', () => { - expect(namespaces).toBeInstanceOf(Map); - }); -}); - -describe('getClusterToken', () => { - test('should work correctly', () => { - const namespace1 = Symbol('default-client'); - const namespace2 = 'cache-client'; - expect(getClusterToken(namespace1)).toBe(namespace1); - expect(getClusterToken(namespace2)).toBe(`${CLUSTER_MODULE_ID}:${namespace2}`); - }); -}); - -describe('InjectCluster', () => { - const nameNamespace: ClientNamespace = Symbol('name'); - const genderNamespace: ClientNamespace = DEFAULT_CLUSTER_NAMESPACE; - const name: ValueProvider = { provide: getClusterToken(nameNamespace), useValue: 'liaoliao' }; - const gender: ValueProvider = { provide: getClusterToken(genderNamespace), useValue: 'female' }; - - @Injectable() - class TestName { - constructor( - @InjectCluster(nameNamespace) public readonly value1: string, - @InjectCluster(nameNamespace) public readonly value2: string - ) {} - } - @Injectable() - class TestGender { - constructor( - @InjectCluster(genderNamespace) public readonly value1: string, - @InjectCluster() public readonly value2: string - ) {} - } - - let testName: TestName; - let testGender: TestGender; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [name, gender, TestName, TestGender] - }).compile(); - - testName = module.get(TestName); - testGender = module.get(TestGender); - }); - - test('should work correctly', () => { - expect(testName.value1).toBe(name.useValue); - expect(testName.value2).toBe(name.useValue); - expect(testGender.value1).toBe(gender.useValue); - expect(testGender.value2).toBe(gender.useValue); - }); - - test('should have 2 members in namespaces map', () => { - expect(namespaces.size).toBe(2); - expect(namespaces.has(nameNamespace)).toBe(true); - expect(namespaces.has(genderNamespace)).toBe(true); - }); -}); diff --git a/packages/redis/lib/cluster/common/cluster.decorator.ts b/packages/redis/lib/cluster/common/cluster.decorator.ts deleted file mode 100644 index 46633d1a..00000000 --- a/packages/redis/lib/cluster/common/cluster.decorator.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Inject } from '@nestjs/common'; -import { DEFAULT_CLUSTER_NAMESPACE, CLUSTER_MODULE_ID } from '../cluster.constants'; -import { ClientNamespace } from '@/interfaces'; -import { isSymbol } from '@/utils'; - -export const namespaces = new Map(); - -/** - * This decorator is used to mark a specific constructor parameter as a cluster client. - * - * @param namespace - Client name - * - * @public - */ -export const InjectCluster = (namespace: ClientNamespace = DEFAULT_CLUSTER_NAMESPACE): ParameterDecorator => { - const token = getClusterToken(namespace); - namespaces.set(namespace, token); - return Inject(token); -}; - -/** - * This function generates an injection token for a cluster client. - * - * @param namespace - Client name - * - * @public - */ -export const getClusterToken = (namespace: ClientNamespace): ClientNamespace => { - if (isSymbol(namespace)) return namespace; - return `${CLUSTER_MODULE_ID}:${namespace}`; -}; diff --git a/packages/redis/lib/cluster/common/cluster.utils.ts b/packages/redis/lib/cluster/common/cluster.utils.ts index e8656c7c..b4ae0cda 100644 --- a/packages/redis/lib/cluster/common/cluster.utils.ts +++ b/packages/redis/lib/cluster/common/cluster.utils.ts @@ -1,58 +1,32 @@ import { Cluster } from 'ioredis'; -import { ClusterClientOptions, ClusterClients, ClusterModuleOptions } from '../interfaces'; -import { ClientNamespace } from '@/interfaces'; +import { ClusterClientOptions, ClusterModuleOptions } from '../interfaces'; +import { Namespace } from '@/interfaces'; import { READY_LOG, ERROR_LOG } from '@/messages'; import { logger } from '../cluster-logger'; -import { parseNamespace } from '@/utils'; -import { READY_EVENT, ERROR_EVENT, END_EVENT } from '@/constants'; -import { DEFAULT_CLUSTER_NAMESPACE, NAMESPACE_KEY } from '../cluster.constants'; +import { parseNamespace, get } from '@/utils'; +import { DEFAULT_CLUSTER, NAMESPACE_KEY } from '../cluster.constants'; -export const addListeners = ({ - namespace, - instance, - readyLog, - errorLog -}: { - namespace: ClientNamespace; - instance: Cluster; - readyLog?: boolean; - errorLog?: boolean; -}) => { - Reflect.set(instance, NAMESPACE_KEY, namespace); +export const createClient = ( + { namespace, nodes, onClientCreated, ...clusterOptions }: ClusterClientOptions, + { readyLog, errorLog }: Partial +): Cluster => { + const client = new Cluster(nodes, clusterOptions); + Reflect.defineProperty(client, NAMESPACE_KEY, { + value: namespace ?? DEFAULT_CLUSTER, + writable: false, + enumerable: false, + configurable: false + }); if (readyLog) { - instance.on(READY_EVENT, function (this: Cluster) { - logger.log(READY_LOG(parseNamespace(Reflect.get(this, NAMESPACE_KEY) as ClientNamespace))); + client.on('ready', () => { + logger.log(READY_LOG(parseNamespace(get(client, NAMESPACE_KEY)))); }); } if (errorLog) { - instance.on(ERROR_EVENT, function (this: Cluster, error: Error) { - logger.error( - ERROR_LOG(parseNamespace(Reflect.get(this, NAMESPACE_KEY) as ClientNamespace), error.message), - error.stack - ); + client.on('error', (error: Error) => { + logger.error(ERROR_LOG(parseNamespace(get(client, NAMESPACE_KEY)), error.message), error.stack); }); } -}; - -export const createClient = ( - { namespace, nodes, onClientCreated, ...clusterOptions }: ClusterClientOptions, - { readyLog, errorLog }: Partial -): Cluster => { - const client = new Cluster(nodes, clusterOptions); - addListeners({ namespace: namespace ?? DEFAULT_CLUSTER_NAMESPACE, instance: client, readyLog, errorLog }); if (onClientCreated) onClientCreated(client); return client; }; - -export const destroy = async (clients: ClusterClients) => { - const promises: Promise<[PromiseSettledResult, PromiseSettledResult<'OK'>]>[] = []; - clients.forEach((client, namespace) => { - if (client.status === END_EVENT) return; - if (client.status === READY_EVENT) { - promises.push(Promise.allSettled([namespace, client.quit()])); - return; - } - client.disconnect(); - }); - return await Promise.all(promises); -}; diff --git a/packages/redis/lib/cluster/common/index.ts b/packages/redis/lib/cluster/common/index.ts index 8923880e..a2725c87 100644 --- a/packages/redis/lib/cluster/common/index.ts +++ b/packages/redis/lib/cluster/common/index.ts @@ -1,2 +1 @@ export * from './cluster.utils'; -export * from './cluster.decorator'; diff --git a/packages/redis/lib/cluster/interfaces/cluster-module-options.interface.ts b/packages/redis/lib/cluster/interfaces/cluster-module-options.interface.ts index 9741fd2b..8d76acd8 100644 --- a/packages/redis/lib/cluster/interfaces/cluster-module-options.interface.ts +++ b/packages/redis/lib/cluster/interfaces/cluster-module-options.interface.ts @@ -1,11 +1,7 @@ -import { Type, ModuleMetadata, Provider } from '@nestjs/common'; -import type { Cluster } from 'ioredis'; -import { ClusterNode, ClusterOptions } from 'ioredis'; -import { ClientNamespace } from '@/interfaces'; +import { Type, ModuleMetadata, Provider, InjectionToken, OptionalFactoryDependency } from '@nestjs/common'; +import type { Cluster, ClusterNode, ClusterOptions } from 'ioredis'; +import { Namespace } from '@/interfaces'; -/** - * @public - */ export interface ClusterClientOptions extends ClusterOptions { /** * Client name. If client name is not given then it will be called "default". @@ -13,8 +9,7 @@ export interface ClusterClientOptions extends ClusterOptions { * * @defaultValue `"default"` */ - namespace?: ClientNamespace; - + namespace?: Namespace; /** * List of cluster nodes. * @@ -31,7 +26,6 @@ export interface ClusterClientOptions extends ClusterOptions { * ``` */ nodes: ClusterNode[]; - /** * Function to be executed as soon as the client is created. * @@ -40,9 +34,6 @@ export interface ClusterClientOptions extends ClusterOptions { onClientCreated?: (client: Cluster) => void; } -/** - * @public - */ export interface ClusterModuleOptions { /** * If set to `true`, all clients will be closed automatically on nestjs application shutdown. @@ -50,43 +41,32 @@ export interface ClusterModuleOptions { * @defaultValue `true` */ closeClient?: boolean; - /** * If set to `true`, then ready logging will be displayed when the client is ready. * * @defaultValue `false` */ readyLog?: boolean; - /** * If set to `true`, then errors that occurred while connecting will be displayed by the built-in logger. * * @defaultValue `true` */ errorLog?: boolean; - /** * Used to specify single or multiple clients. */ config: ClusterClientOptions | ClusterClientOptions[]; } -/** - * @public - */ export interface ClusterModuleAsyncOptions extends Pick { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - useFactory?: (...args: any[]) => ClusterModuleOptions | Promise; + useFactory?: (...args: unknown[]) => ClusterModuleOptions | Promise; useClass?: Type; useExisting?: Type; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - inject?: any[]; + inject?: InjectionToken[] | OptionalFactoryDependency[]; extraProviders?: Provider[]; } -/** - * @public - */ export interface ClusterOptionsFactory { createClusterOptions: () => ClusterModuleOptions | Promise; } diff --git a/packages/redis/lib/cluster/interfaces/cluster.interface.ts b/packages/redis/lib/cluster/interfaces/cluster.interface.ts new file mode 100644 index 00000000..c5ab263a --- /dev/null +++ b/packages/redis/lib/cluster/interfaces/cluster.interface.ts @@ -0,0 +1,4 @@ +import type { Cluster } from 'ioredis'; +import { Namespace } from '@/interfaces'; + +export type ClusterClients = Map; diff --git a/packages/redis/lib/cluster/interfaces/index.ts b/packages/redis/lib/cluster/interfaces/index.ts index 886bd10a..d39b025e 100644 --- a/packages/redis/lib/cluster/interfaces/index.ts +++ b/packages/redis/lib/cluster/interfaces/index.ts @@ -1,6 +1,2 @@ -import type { Cluster } from 'ioredis'; -import { ClientNamespace } from '@/interfaces'; - export * from './cluster-module-options.interface'; - -export type ClusterClients = Map; +export * from './cluster.interface'; diff --git a/packages/redis/lib/constants/index.spec.ts b/packages/redis/lib/constants/index.spec.ts deleted file mode 100644 index ab84deb4..00000000 --- a/packages/redis/lib/constants/index.spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as allExports from '.'; - -test('each of exports should be defined', () => { - Object.values(allExports).forEach(value => expect(value).not.toBeNil()); -}); diff --git a/packages/redis/lib/constants/index.ts b/packages/redis/lib/constants/index.ts deleted file mode 100644 index 1326fbcd..00000000 --- a/packages/redis/lib/constants/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const ERROR_EVENT = 'error'; -export const READY_EVENT = 'ready'; -export const END_EVENT = 'end'; diff --git a/packages/redis/lib/errors/client-not-found.error.ts b/packages/redis/lib/errors/client-not-found.error.ts index 8bfb55e5..5b569e14 100644 --- a/packages/redis/lib/errors/client-not-found.error.ts +++ b/packages/redis/lib/errors/client-not-found.error.ts @@ -1,17 +1,9 @@ -import { ClientType } from '@/interfaces'; - /** * Thrown when consumer tries to get client that does not exist. */ export class ClientNotFoundError extends Error { - constructor(namespace: string, type: ClientType) { - super( - `The ${ - type === 'redis' ? 'redis' : 'cluster' - } client "${namespace}" could not be found in the application context.` - ); - + constructor(namespace: string) { + super(`Connection "${namespace}" was not found.`); this.name = ClientNotFoundError.name; - Error.captureStackTrace(this, this.constructor); } } diff --git a/packages/redis/lib/errors/missing-configurations.error.ts b/packages/redis/lib/errors/missing-configurations.error.ts index 2bb31a62..f0120b5e 100644 --- a/packages/redis/lib/errors/missing-configurations.error.ts +++ b/packages/redis/lib/errors/missing-configurations.error.ts @@ -3,9 +3,7 @@ */ export class MissingConfigurationsError extends Error { constructor() { - super(`The asynchronous configurations are missing. Expected one of: "useFactory", "useClass", "useExisting".`); - + super(`Missing required asynchronous configurations. Expected one of: "useFactory", "useClass", "useExisting".`); this.name = MissingConfigurationsError.name; - Error.captureStackTrace(this, this.constructor); } } diff --git a/packages/redis/lib/index.ts b/packages/redis/lib/index.ts index 258e352d..1eab8b01 100644 --- a/packages/redis/lib/index.ts +++ b/packages/redis/lib/index.ts @@ -1,14 +1,12 @@ export { RedisModule } from './redis/redis.module'; -export { DEFAULT_REDIS_NAMESPACE } from './redis/redis.constants'; -export { RedisManager, RedisManager as RedisService } from './redis/redis-manager'; -export { InjectRedis, getRedisToken } from './redis/common'; +export { DEFAULT_REDIS } from './redis/redis.constants'; +export { RedisService } from './redis/redis.service'; export { ClusterModule } from './cluster/cluster.module'; -export { DEFAULT_CLUSTER_NAMESPACE } from './cluster/cluster.constants'; -export { ClusterManager, ClusterManager as ClusterService } from './cluster/cluster-manager'; -export { InjectCluster, getClusterToken } from './cluster/common'; +export { DEFAULT_CLUSTER } from './cluster/cluster.constants'; +export { ClusterService } from './cluster/cluster.service'; // * Types & Interfaces -export { ClientNamespace } from './interfaces'; +export { Namespace } from './interfaces'; export { RedisModuleOptions, RedisModuleAsyncOptions, diff --git a/packages/redis/lib/interfaces/index.ts b/packages/redis/lib/interfaces/index.ts index 62a93d55..74c52297 100644 --- a/packages/redis/lib/interfaces/index.ts +++ b/packages/redis/lib/interfaces/index.ts @@ -1,2 +1 @@ -export type ClientNamespace = string | symbol; -export type ClientType = 'redis' | 'cluster'; +export type Namespace = string | symbol; diff --git a/packages/redis/lib/messages/index.ts b/packages/redis/lib/messages/index.ts index b779d051..d80473a8 100644 --- a/packages/redis/lib/messages/index.ts +++ b/packages/redis/lib/messages/index.ts @@ -1,2 +1,3 @@ -export const READY_LOG = (namespace: string) => `${namespace}: connected successfully to the server`; +export const READY_LOG = (namespace: string) => `${namespace}: the connection was successfully established`; + export const ERROR_LOG = (namespace: string, message: string) => `${namespace}: ${message}`; diff --git a/packages/redis/lib/redis/common/index.ts b/packages/redis/lib/redis/common/index.ts index 61c9c0f5..95ddf5e1 100644 --- a/packages/redis/lib/redis/common/index.ts +++ b/packages/redis/lib/redis/common/index.ts @@ -1,2 +1 @@ export * from './redis.utils'; -export * from './redis.decorator'; diff --git a/packages/redis/lib/redis/common/redis.decorator.spec.ts b/packages/redis/lib/redis/common/redis.decorator.spec.ts deleted file mode 100644 index 19e5d2d7..00000000 --- a/packages/redis/lib/redis/common/redis.decorator.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Injectable, ValueProvider } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { InjectRedis, namespaces, getRedisToken } from './redis.decorator'; -import { DEFAULT_REDIS_NAMESPACE, REDIS_MODULE_ID } from '../redis.constants'; -import { ClientNamespace } from '@/interfaces'; - -describe('namespaces', () => { - test('should be an instance of the map', () => { - expect(namespaces).toBeInstanceOf(Map); - }); -}); - -describe('getRedisToken', () => { - test('should work correctly', () => { - const namespace1 = Symbol('default-client'); - const namespace2 = 'cache-client'; - expect(getRedisToken(namespace1)).toBe(namespace1); - expect(getRedisToken(namespace2)).toBe(`${REDIS_MODULE_ID}:${namespace2}`); - }); -}); - -describe('InjectRedis', () => { - const nameNamespace: ClientNamespace = Symbol('name'); - const genderNamespace: ClientNamespace = DEFAULT_REDIS_NAMESPACE; - const name: ValueProvider = { provide: getRedisToken(nameNamespace), useValue: 'liaoliao' }; - const gender: ValueProvider = { provide: getRedisToken(genderNamespace), useValue: 'female' }; - - @Injectable() - class TestName { - constructor( - @InjectRedis(nameNamespace) public readonly value1: string, - @InjectRedis(nameNamespace) public readonly value2: string - ) {} - } - @Injectable() - class TestGender { - constructor( - @InjectRedis(genderNamespace) public readonly value1: string, - @InjectRedis() public readonly value2: string - ) {} - } - - let testName: TestName; - let testGender: TestGender; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [name, gender, TestName, TestGender] - }).compile(); - - testName = module.get(TestName); - testGender = module.get(TestGender); - }); - - test('should work correctly', () => { - expect(testName.value1).toBe(name.useValue); - expect(testName.value2).toBe(name.useValue); - expect(testGender.value1).toBe(gender.useValue); - expect(testGender.value2).toBe(gender.useValue); - }); - - test('should have 2 members in namespaces map', () => { - expect(namespaces.size).toBe(2); - expect(namespaces.has(nameNamespace)).toBe(true); - expect(namespaces.has(genderNamespace)).toBe(true); - }); -}); diff --git a/packages/redis/lib/redis/common/redis.decorator.ts b/packages/redis/lib/redis/common/redis.decorator.ts deleted file mode 100644 index 14319ac5..00000000 --- a/packages/redis/lib/redis/common/redis.decorator.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Inject } from '@nestjs/common'; -import { DEFAULT_REDIS_NAMESPACE, REDIS_MODULE_ID } from '../redis.constants'; -import { ClientNamespace } from '@/interfaces'; -import { isSymbol } from '@/utils'; - -export const namespaces = new Map(); - -/** - * This decorator is used to mark a specific constructor parameter as a redis client. - * - * @param namespace - Client name - * - * @public - */ -export const InjectRedis = (namespace: ClientNamespace = DEFAULT_REDIS_NAMESPACE): ParameterDecorator => { - const token = getRedisToken(namespace); - namespaces.set(namespace, token); - return Inject(token); -}; - -/** - * This function generates an injection token for a redis client. - * - * @param namespace - Client name - * - * @public - */ -export const getRedisToken = (namespace: ClientNamespace): ClientNamespace => { - if (isSymbol(namespace)) return namespace; - return `${REDIS_MODULE_ID}:${namespace}`; -}; diff --git a/packages/redis/lib/redis/common/redis.utils.ts b/packages/redis/lib/redis/common/redis.utils.ts index c53380ab..84ca18b6 100644 --- a/packages/redis/lib/redis/common/redis.utils.ts +++ b/packages/redis/lib/redis/common/redis.utils.ts @@ -1,38 +1,10 @@ -import Redis from 'ioredis'; -import { RedisClientOptions, RedisClients, RedisModuleOptions } from '../interfaces'; -import { ClientNamespace } from '@/interfaces'; +import { Redis } from 'ioredis'; +import { RedisClientOptions, RedisModuleOptions } from '../interfaces'; +import { Namespace } from '@/interfaces'; import { READY_LOG, ERROR_LOG } from '@/messages'; import { logger } from '../redis-logger'; -import { parseNamespace } from '@/utils'; -import { READY_EVENT, ERROR_EVENT, END_EVENT } from '@/constants'; -import { DEFAULT_REDIS_NAMESPACE, NAMESPACE_KEY } from '../redis.constants'; - -export const addListeners = ({ - namespace, - instance, - readyLog, - errorLog -}: { - namespace: ClientNamespace; - instance: Redis; - readyLog?: boolean; - errorLog?: boolean; -}) => { - Reflect.set(instance, NAMESPACE_KEY, namespace); - if (readyLog) { - instance.on(READY_EVENT, function (this: Redis) { - logger.log(READY_LOG(parseNamespace(Reflect.get(this, NAMESPACE_KEY) as ClientNamespace))); - }); - } - if (errorLog) { - instance.on(ERROR_EVENT, function (this: Redis, error: Error) { - logger.error( - ERROR_LOG(parseNamespace(Reflect.get(this, NAMESPACE_KEY) as ClientNamespace), error.message), - error.stack - ); - }); - } -}; +import { parseNamespace, get } from '@/utils'; +import { DEFAULT_REDIS, NAMESPACE_KEY } from '../redis.constants'; export const createClient = ( { namespace, url, path, onClientCreated, ...redisOptions }: RedisClientOptions, @@ -42,20 +14,22 @@ export const createClient = ( if (url) client = new Redis(url, redisOptions); else if (path) client = new Redis(path, redisOptions); else client = new Redis(redisOptions); - addListeners({ namespace: namespace ?? DEFAULT_REDIS_NAMESPACE, instance: client, readyLog, errorLog }); + Reflect.defineProperty(client, NAMESPACE_KEY, { + value: namespace ?? DEFAULT_REDIS, + writable: false, + enumerable: false, + configurable: false + }); + if (readyLog) { + client.on('ready', () => { + logger.log(READY_LOG(parseNamespace(get(client, NAMESPACE_KEY)))); + }); + } + if (errorLog) { + client.on('error', (error: Error) => { + logger.error(ERROR_LOG(parseNamespace(get(client, NAMESPACE_KEY)), error.message), error.stack); + }); + } if (onClientCreated) onClientCreated(client); return client; }; - -export const destroy = async (clients: RedisClients) => { - const promises: Promise<[PromiseSettledResult, PromiseSettledResult<'OK'>]>[] = []; - clients.forEach((client, namespace) => { - if (client.status === END_EVENT) return; - if (client.status === READY_EVENT) { - promises.push(Promise.allSettled([namespace, client.quit()])); - return; - } - client.disconnect(); - }); - return await Promise.all(promises); -}; diff --git a/packages/redis/lib/redis/default-options.ts b/packages/redis/lib/redis/default-options.ts index 34f7c45f..07f78b33 100644 --- a/packages/redis/lib/redis/default-options.ts +++ b/packages/redis/lib/redis/default-options.ts @@ -3,7 +3,7 @@ import { RedisModuleOptions } from './interfaces'; export const defaultRedisModuleOptions: RedisModuleOptions = { closeClient: true, commonOptions: undefined, - readyLog: false, + readyLog: true, errorLog: true, - config: undefined + config: {} }; diff --git a/packages/redis/lib/redis/interfaces/index.ts b/packages/redis/lib/redis/interfaces/index.ts index 4c8a81a2..f8750073 100644 --- a/packages/redis/lib/redis/interfaces/index.ts +++ b/packages/redis/lib/redis/interfaces/index.ts @@ -1,6 +1,2 @@ -import { Redis } from 'ioredis'; -import { ClientNamespace } from '@/interfaces'; - export * from './redis-module-options.interface'; - -export type RedisClients = Map; +export * from './redis.interface'; diff --git a/packages/redis/lib/redis/interfaces/redis-module-options.interface.ts b/packages/redis/lib/redis/interfaces/redis-module-options.interface.ts index df873e03..80ea72b3 100644 --- a/packages/redis/lib/redis/interfaces/redis-module-options.interface.ts +++ b/packages/redis/lib/redis/interfaces/redis-module-options.interface.ts @@ -1,10 +1,7 @@ -import { Type, ModuleMetadata, Provider } from '@nestjs/common'; -import { Redis, RedisOptions } from 'ioredis'; -import { ClientNamespace } from '@/interfaces'; +import { Type, ModuleMetadata, Provider, InjectionToken, OptionalFactoryDependency } from '@nestjs/common'; +import type { Redis, RedisOptions } from 'ioredis'; +import { Namespace } from '@/interfaces'; -/** - * @public - */ export interface RedisClientOptions extends RedisOptions { /** * Client name. If client name is not given then it will be called "default". @@ -12,8 +9,7 @@ export interface RedisClientOptions extends RedisOptions { * * @defaultValue `"default"` */ - namespace?: ClientNamespace; - + namespace?: Namespace; /** * URI scheme to be used to specify connection options as a redis:// URL or rediss:// URL. * @@ -27,12 +23,10 @@ export interface RedisClientOptions extends RedisOptions { * ``` */ url?: string; - /** * Path to be used for Unix domain sockets. */ path?: string; - /** * Function to be executed as soon as the client is created. * @@ -41,9 +35,6 @@ export interface RedisClientOptions extends RedisOptions { onClientCreated?: (client: Redis) => void; } -/** - * @public - */ export interface RedisModuleOptions { /** * If set to `true`, all clients will be closed automatically on nestjs application shutdown. @@ -51,48 +42,36 @@ export interface RedisModuleOptions { * @defaultValue `true` */ closeClient?: boolean; - /** * Common options to be passed to each client. */ commonOptions?: RedisOptions; - /** * If set to `true`, then ready logging will be displayed when the client is ready. * * @defaultValue `false` */ readyLog?: boolean; - /** * If set to `true`, then errors that occurred while connecting will be displayed by the built-in logger. * * @defaultValue `true` */ errorLog?: boolean; - /** * Used to specify single or multiple clients. */ config?: RedisClientOptions | RedisClientOptions[]; } -/** - * @public - */ export interface RedisModuleAsyncOptions extends Pick { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - useFactory?: (...args: any[]) => RedisModuleOptions | Promise; + useFactory?: (...args: unknown[]) => RedisModuleOptions | Promise; useClass?: Type; useExisting?: Type; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - inject?: any[]; + inject?: InjectionToken[] | OptionalFactoryDependency[]; extraProviders?: Provider[]; } -/** - * @public - */ export interface RedisOptionsFactory { createRedisOptions: () => RedisModuleOptions | Promise; } diff --git a/packages/redis/lib/redis/interfaces/redis.interface.ts b/packages/redis/lib/redis/interfaces/redis.interface.ts new file mode 100644 index 00000000..7f7cb9a8 --- /dev/null +++ b/packages/redis/lib/redis/interfaces/redis.interface.ts @@ -0,0 +1,4 @@ +import type { Redis } from 'ioredis'; +import { Namespace } from '@/interfaces'; + +export type RedisClients = Map; diff --git a/packages/redis/lib/redis/redis-manager.ts b/packages/redis/lib/redis/redis-manager.ts deleted file mode 100644 index 2d72e473..00000000 --- a/packages/redis/lib/redis/redis-manager.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { Redis } from 'ioredis'; -import { REDIS_CLIENTS, DEFAULT_REDIS_NAMESPACE } from './redis.constants'; -import { RedisClients } from './interfaces'; -import { parseNamespace } from '@/utils'; -import { ClientNamespace } from '@/interfaces'; -import { ClientNotFoundError } from '@/errors'; - -/** - * Manager for redis clients. - * - * @public - */ -@Injectable() -export class RedisManager { - constructor(@Inject(REDIS_CLIENTS) private readonly redisClients: RedisClients) {} - - /** - * Retrieves all redis clients. - */ - get clients(): ReadonlyMap { - return this.redisClients; - } - - /** - * Retrieves a redis client by namespace. - */ - getClient(namespace: ClientNamespace = DEFAULT_REDIS_NAMESPACE): Redis { - const client = this.redisClients.get(namespace); - if (!client) throw new ClientNotFoundError(parseNamespace(namespace), 'redis'); - return client; - } -} diff --git a/packages/redis/lib/redis/redis.constants.ts b/packages/redis/lib/redis/redis.constants.ts index 99ae49dc..842a317d 100644 --- a/packages/redis/lib/redis/redis.constants.ts +++ b/packages/redis/lib/redis/redis.constants.ts @@ -4,8 +4,11 @@ export const REDIS_MERGED_OPTIONS = Symbol(); export const REDIS_CLIENTS = Symbol(); -export const DEFAULT_REDIS_NAMESPACE = 'default'; - export const REDIS_MODULE_ID = 'RedisModule'; export const NAMESPACE_KEY = Symbol(); + +/** + * The default redis namespace. + */ +export const DEFAULT_REDIS = 'default'; diff --git a/packages/redis/lib/redis/redis.module.ts b/packages/redis/lib/redis/redis.module.ts index b6d4a252..b5a8c13b 100644 --- a/packages/redis/lib/redis/redis.module.ts +++ b/packages/redis/lib/redis/redis.module.ts @@ -1,64 +1,63 @@ import { Module, DynamicModule, Provider, OnApplicationShutdown } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { RedisModuleOptions, RedisModuleAsyncOptions, RedisClients } from './interfaces'; -import { RedisManager } from './redis-manager'; +import { RedisService } from './redis.service'; import { createOptionsProvider, createAsyncProviders, - createRedisClientProviders, redisClientsProvider, mergedOptionsProvider } from './redis.providers'; import { REDIS_CLIENTS, REDIS_MERGED_OPTIONS } from './redis.constants'; -import { destroy } from './common'; -import { parseNamespace, isResolution, isRejection, isError } from '@/utils'; +import { parseNamespace, isError } from '@/utils'; import { logger } from './redis-logger'; import { MissingConfigurationsError } from '@/errors'; import { ERROR_LOG } from '@/messages'; -/** - * @public - */ @Module({}) export class RedisModule implements OnApplicationShutdown { constructor(private moduleRef: ModuleRef) {} /** * Registers the module synchronously. + * + * @param options - The module options + * @param isGlobal - Register in the global scope + * @returns A DynamicModule */ static forRoot(options: RedisModuleOptions = {}, isGlobal = true): DynamicModule { - const redisClientProviders = createRedisClientProviders(); const providers: Provider[] = [ createOptionsProvider(options), redisClientsProvider, mergedOptionsProvider, - RedisManager, - ...redisClientProviders + RedisService ]; return { global: isGlobal, module: RedisModule, providers, - exports: [RedisManager, ...redisClientProviders] + exports: [RedisService] }; } /** * Registers the module asynchronously. + * + * @param options - The async module options + * @param isGlobal - Register in the global scope + * @returns A DynamicModule */ static forRootAsync(options: RedisModuleAsyncOptions, isGlobal = true): DynamicModule { if (!options.useFactory && !options.useClass && !options.useExisting) { throw new MissingConfigurationsError(); } - const redisClientProviders = createRedisClientProviders(); const providers: Provider[] = [ ...createAsyncProviders(options), redisClientsProvider, mergedOptionsProvider, - RedisManager, - ...redisClientProviders, + RedisService, ...(options.extraProviders ?? []) ]; @@ -67,19 +66,26 @@ export class RedisModule implements OnApplicationShutdown { module: RedisModule, imports: options.imports, providers, - exports: [RedisManager, ...redisClientProviders] + exports: [RedisService] }; } async onApplicationShutdown(): Promise { - const { closeClient } = this.moduleRef.get(REDIS_MERGED_OPTIONS); + const { closeClient } = this.moduleRef.get(REDIS_MERGED_OPTIONS, { strict: false }); if (closeClient) { - const results = await destroy(this.moduleRef.get(REDIS_CLIENTS)); - results.forEach(([namespace, quit]) => { - if (isResolution(namespace) && isRejection(quit) && isError(quit.reason)) { - logger.error(ERROR_LOG(parseNamespace(namespace.value), quit.reason.message), quit.reason.stack); + const clients = this.moduleRef.get(REDIS_CLIENTS, { strict: false }); + for (const [namespace, client] of clients) { + if (client.status === 'end') continue; + if (client.status === 'ready') { + try { + await client.quit(); + } catch (e) { + if (isError(e)) logger.error(ERROR_LOG(parseNamespace(namespace), e.message), e.stack); + } + continue; } - }); + client.disconnect(); + } } } } diff --git a/packages/redis/lib/redis/redis.providers.ts b/packages/redis/lib/redis/redis.providers.ts index ca1f098f..0b9d4b5a 100644 --- a/packages/redis/lib/redis/redis.providers.ts +++ b/packages/redis/lib/redis/redis.providers.ts @@ -1,9 +1,7 @@ import { Provider, FactoryProvider, ValueProvider } from '@nestjs/common'; -import type { Redis } from 'ioredis'; import { RedisModuleOptions, RedisModuleAsyncOptions, RedisOptionsFactory, RedisClients } from './interfaces'; -import { REDIS_OPTIONS, REDIS_CLIENTS, DEFAULT_REDIS_NAMESPACE, REDIS_MERGED_OPTIONS } from './redis.constants'; -import { createClient, namespaces } from './common'; -import { RedisManager } from './redis-manager'; +import { REDIS_OPTIONS, REDIS_CLIENTS, DEFAULT_REDIS, REDIS_MERGED_OPTIONS } from './redis.constants'; +import { createClient } from './common'; import { defaultRedisModuleOptions } from './default-options'; export const createOptionsProvider = (options: RedisModuleOptions): ValueProvider => ({ @@ -62,18 +60,6 @@ export const createAsyncOptionsProvider = (options: RedisModuleAsyncOptions): Pr }; }; -export const createRedisClientProviders = (): FactoryProvider[] => { - const providers: FactoryProvider[] = []; - namespaces.forEach((token, namespace) => { - providers.push({ - provide: token, - useFactory: (redisManager: RedisManager) => redisManager.getClient(namespace), - inject: [RedisManager] - }); - }); - return providers; -}; - export const redisClientsProvider: FactoryProvider = { provide: REDIS_CLIENTS, useFactory: (options: RedisModuleOptions) => { @@ -81,7 +67,7 @@ export const redisClientsProvider: FactoryProvider = { if (Array.isArray(options.config)) { options.config.forEach(item => clients.set( - item.namespace ?? DEFAULT_REDIS_NAMESPACE, + item.namespace ?? DEFAULT_REDIS, createClient( { ...options.commonOptions, ...item }, { readyLog: options.readyLog, errorLog: options.errorLog } @@ -90,7 +76,7 @@ export const redisClientsProvider: FactoryProvider = { ); } else if (options.config) { clients.set( - options.config.namespace ?? DEFAULT_REDIS_NAMESPACE, + options.config.namespace ?? DEFAULT_REDIS, createClient(options.config, { readyLog: options.readyLog, errorLog: options.errorLog }) ); } diff --git a/packages/redis/lib/redis/redis-manager.spec.ts b/packages/redis/lib/redis/redis.service.spec.ts similarity index 100% rename from packages/redis/lib/redis/redis-manager.spec.ts rename to packages/redis/lib/redis/redis.service.spec.ts diff --git a/packages/redis/lib/redis/redis.service.ts b/packages/redis/lib/redis/redis.service.ts new file mode 100644 index 00000000..76a2c749 --- /dev/null +++ b/packages/redis/lib/redis/redis.service.ts @@ -0,0 +1,40 @@ +import { Injectable, Inject } from '@nestjs/common'; +import type { Redis } from 'ioredis'; +import { REDIS_CLIENTS, DEFAULT_REDIS } from './redis.constants'; +import { RedisClients } from './interfaces'; +import { parseNamespace } from '@/utils'; +import { Namespace } from '@/interfaces'; +import { ClientNotFoundError } from '@/errors'; + +/** + * Manager for redis connections. + */ +@Injectable() +export class RedisService { + constructor(@Inject(REDIS_CLIENTS) private readonly clients: RedisClients) {} + + /** + * Retrieves a redis connection by namespace. + * However, if the query does not find a connection, it returns ClientNotFoundError: No Connection found error. + * + * @param namespace - The namespace + * @returns A redis connection + */ + getOrThrow(namespace: Namespace = DEFAULT_REDIS): Redis { + const client = this.clients.get(namespace); + if (!client) throw new ClientNotFoundError(parseNamespace(namespace)); + return client; + } + + /** + * Retrieves a redis connection by namespace, if the query does not find a connection, it returns `null`; + * + * @param namespace - The namespace + * @returns A redis connection or nil + */ + getOrNil(namespace: Namespace = DEFAULT_REDIS): Redis | null { + const client = this.clients.get(namespace); + if (!client) return null; + return client; + } +} diff --git a/packages/redis/lib/utils/index.ts b/packages/redis/lib/utils/index.ts index 9e117d53..b3e01f2a 100644 --- a/packages/redis/lib/utils/index.ts +++ b/packages/redis/lib/utils/index.ts @@ -1,2 +1,3 @@ export * from './is'; export * from './parsers'; +export * from './reflect'; diff --git a/packages/redis/lib/utils/is.spec.ts b/packages/redis/lib/utils/is.spec.ts index 2c002a88..2e7e36d6 100644 --- a/packages/redis/lib/utils/is.spec.ts +++ b/packages/redis/lib/utils/is.spec.ts @@ -1,4 +1,4 @@ -import { isString, isSymbol, isError, isResolution, isRejection } from './is'; +import { isString, isError } from './is'; describe('isString', () => { test('should work correctly', () => { @@ -12,17 +12,6 @@ describe('isString', () => { }); }); -describe('isSymbol', () => { - test('should work correctly', () => { - expect(isSymbol(Symbol())).toBe(true); - expect(isSymbol('')).toBe(false); - expect(isSymbol(1)).toBe(false); - expect(isSymbol(true)).toBe(false); - expect(isSymbol(undefined)).toBe(false); - expect(isSymbol(null)).toBe(false); - }); -}); - describe('isError', () => { test('should work correctly', () => { class CustomError extends Error {} @@ -39,17 +28,3 @@ describe('isError', () => { expect(isError(Symbol())).toBe(false); }); }); - -describe('isResolution', () => { - test('should work correctly', () => { - expect(isResolution({ status: 'fulfilled', value: '' })).toBe(true); - expect(isResolution({ status: 'rejected', reason: '' })).toBe(false); - }); -}); - -describe('isRejection', () => { - test('should work correctly', () => { - expect(isRejection({ status: 'fulfilled', value: '' })).toBe(false); - expect(isRejection({ status: 'rejected', reason: '' })).toBe(true); - }); -}); diff --git a/packages/redis/lib/utils/is.ts b/packages/redis/lib/utils/is.ts index e6cd1343..82a649d7 100644 --- a/packages/redis/lib/utils/is.ts +++ b/packages/redis/lib/utils/is.ts @@ -2,40 +2,17 @@ * Returns `true` if the value is of type `string`. * * @param value - Any value + * @returns A boolean value for Type Guard */ export const isString = (value: unknown): value is string => typeof value === 'string'; -/** - * Returns `true` if the value is of type `symbol`. - * - * @param value - Any value - */ -export const isSymbol = (value: unknown): value is symbol => typeof value === 'symbol'; - /** * Returns `true` if the value is an instance of `Error`. * * @param value - Any value + * @returns A boolean value for Type Guard */ export const isError = (value: unknown): value is Error => { const typeName = Object.prototype.toString.call(value).slice(8, -1); return typeName === 'Error'; }; - -/** - * Returns `true` if the value is of type `PromiseFulfilledResult`. - * - * @param value - `PromiseSettledResult` - */ -export const isResolution = (value: PromiseSettledResult): value is PromiseFulfilledResult => { - return value.status === 'fulfilled'; -}; - -/** - * Returns `true` if the value is of type `PromiseRejectedResult`. - * - * @param value - `PromiseSettledResult` - */ -export const isRejection = (value: PromiseSettledResult): value is PromiseRejectedResult => { - return value.status === 'rejected'; -}; diff --git a/packages/redis/lib/utils/parsers.ts b/packages/redis/lib/utils/parsers.ts index 50a924ea..6f44f826 100644 --- a/packages/redis/lib/utils/parsers.ts +++ b/packages/redis/lib/utils/parsers.ts @@ -1,10 +1,11 @@ import { isString } from './is'; -import { ClientNamespace } from '@/interfaces'; +import { Namespace } from '@/interfaces'; /** * Parses namespace to string. * - * @param namespace - The namespace of the client + * @param namespace - The namespace + * @returns A string value */ -export const parseNamespace = (namespace: ClientNamespace): string => +export const parseNamespace = (namespace: Namespace): string => isString(namespace) ? namespace : namespace.toString(); diff --git a/packages/redis/lib/utils/reflect.ts b/packages/redis/lib/utils/reflect.ts new file mode 100644 index 00000000..91af23f2 --- /dev/null +++ b/packages/redis/lib/utils/reflect.ts @@ -0,0 +1,7 @@ +/** + * Same as Reflect.get + * + * @param target + * @param propertyKey + */ +export const get = (target: object, propertyKey: PropertyKey) => Reflect.get(target, propertyKey) as T; diff --git a/packages/redis/package.json b/packages/redis/package.json index 73de5b2f..d91e8dd1 100644 --- a/packages/redis/package.json +++ b/packages/redis/package.json @@ -1,6 +1,6 @@ { "name": "@liaoliaots/nestjs-redis", - "version": "10.0.0-alpha.3", + "version": "10.0.0", "description": "Redis(ioredis) module for Nest framework (node.js).", "author": "LiaoLiao ", "exports": "./dist/index.js", @@ -25,10 +25,10 @@ ], "scripts": { "prebuild": "rimraf dist", - "build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json", + "build": "tsc --project tsconfig.build.json && tsc-alias -p tsconfig.build.json", "lint": "concurrently \"npm:lint:es\" \"npm:lint:tsc\"", "lint:es": "eslint \"{lib,test}/**/*.ts\"", - "lint:tsc": "tsc -p tsconfig.json --noEmit", + "lint:tsc": "tsc --project tsconfig.json --noEmit", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80842780..58ea3918 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,25 +10,25 @@ importers: devDependencies: '@commitlint/cli': specifier: 19.5.0 - version: 19.5.0(@types/node@20.16.5)(typescript@5.6.2) + version: 19.5.0(@types/node@20.16.7)(typescript@5.6.2) '@commitlint/config-conventional': specifier: 19.5.0 version: 19.5.0 '@eslint/js': - specifier: 9.11.0 - version: 9.11.0 + specifier: 9.11.1 + version: 9.11.1 '@nestjs/common': - specifier: 10.4.3 - version: 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) + specifier: 10.4.4 + version: 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': - specifier: 10.4.3 - version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + specifier: 10.4.4 + version: 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/platform-fastify': - specifier: 10.4.3 - version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)) + specifier: 10.4.4 + version: 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)) '@nestjs/testing': - specifier: 10.4.3 - version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)) + specifier: 10.4.4 + version: 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)) '@tsconfig/node20': specifier: 20.1.4 version: 20.1.4 @@ -39,23 +39,23 @@ importers: specifier: 29.5.13 version: 29.5.13 '@types/node': - specifier: 20.16.5 - version: 20.16.5 + specifier: 20.16.7 + version: 20.16.7 concurrently: specifier: 9.0.1 version: 9.0.1 eslint: - specifier: 9.11.0 - version: 9.11.0(jiti@1.21.6) + specifier: 9.11.1 + version: 9.11.1(jiti@1.21.6) eslint-config-prettier: specifier: 9.1.0 - version: 9.1.0(eslint@9.11.0(jiti@1.21.6)) + version: 9.1.0(eslint@9.11.1(jiti@1.21.6)) eslint-plugin-jest: specifier: 28.8.3 - version: 28.8.3(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(jest@29.7.0(@types/node@20.16.5))(typescript@5.6.2) + version: 28.8.3(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(jest@29.7.0(@types/node@20.16.7))(typescript@5.6.2) eslint-plugin-prettier: specifier: 5.2.1 - version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.11.0(jiti@1.21.6)))(eslint@9.11.0(jiti@1.21.6))(prettier@3.3.3) + version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6))(prettier@3.3.3) fastify: specifier: 4.28.1 version: 4.28.1 @@ -70,10 +70,10 @@ importers: version: 1.9.0 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@20.16.5) + version: 29.7.0(@types/node@20.16.7) jest-extended: specifier: 4.0.2 - version: 4.0.2(jest@29.7.0(@types/node@20.16.5)) + version: 4.0.2(jest@29.7.0(@types/node@20.16.7)) lint-staged: specifier: 15.2.10 version: 15.2.10 @@ -91,7 +91,7 @@ importers: version: 7.8.1 ts-jest: specifier: 29.2.5 - version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.5))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.7))(typescript@5.6.2) tsc-alias: specifier: 1.8.10 version: 1.8.10 @@ -99,17 +99,17 @@ importers: specifier: 5.6.2 version: 5.6.2 typescript-eslint: - specifier: 8.6.0 - version: 8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2) + specifier: 8.7.0 + version: 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) packages/node-redis: dependencies: '@nestjs/common': specifier: ^10.0.0 - version: 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': specifier: ^10.0.0 - version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) redis: specifier: ^4.1.0 version: 4.7.0 @@ -121,10 +121,10 @@ importers: dependencies: '@nestjs/common': specifier: ^10.0.0 - version: 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': specifier: ^10.0.0 - version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) ioredis: specifier: ^5.0.0 version: 5.4.1 @@ -136,13 +136,13 @@ importers: dependencies: '@nestjs/common': specifier: ^10.0.0 - version: 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': specifier: ^10.0.0 - version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/terminus': specifier: ^10.0.0 - version: 10.2.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 10.2.3(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) ioredis: specifier: ^5.0.0 version: 5.4.1 @@ -412,12 +412,16 @@ packages: resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.6.0': + resolution: {integrity: sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@3.1.0': resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.11.0': - resolution: {integrity: sha512-LPkkenkDqyzTFauZLLAPhIb48fj6drrfMvRGSL9tS3AcZBSVTllemLSNyCvHNNL2t797S/6DJNSIwRwXgMO/eQ==} + '@eslint/js@9.11.1': + resolution: {integrity: sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.4': @@ -560,8 +564,8 @@ packages: resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} - '@nestjs/common@10.4.3': - resolution: {integrity: sha512-4hbLd3XIJubHSylYd/1WSi4VQvG68KM/ECYpMDqA3k3J1/T17SAg40sDoq3ZoO5OZgU0xuNyjuISdOTjs11qVg==} + '@nestjs/common@10.4.4': + resolution: {integrity: sha512-0j2/zqRw9nvHV1GKTktER8B/hIC/Z8CYFjN/ZqUuvwayCH+jZZBhCR2oRyuvLTXdnlSmtCAg2xvQ0ULqQvzqhA==} peerDependencies: class-transformer: '*' class-validator: '*' @@ -573,8 +577,8 @@ packages: class-validator: optional: true - '@nestjs/core@10.4.3': - resolution: {integrity: sha512-6OQz+5C8mT8yRtfvE5pPCq+p6w5jDot+oQku1KzQ24ABn+lay1KGuJwcKZhdVNuselx+8xhdMxknZTA8wrGLIg==} + '@nestjs/core@10.4.4': + resolution: {integrity: sha512-y9tjmAzU6LTh1cC/lWrRsCcOd80khSR0qAHAqwY2svbW+AhsR/XCzgpZrAAKJrm/dDfjLCZKyxJSayeirGcW5Q==} peerDependencies: '@nestjs/common': ^10.0.0 '@nestjs/microservices': ^10.0.0 @@ -590,8 +594,8 @@ packages: '@nestjs/websockets': optional: true - '@nestjs/platform-fastify@10.4.3': - resolution: {integrity: sha512-F7SlU0ZAZkSSDlfNBRggqtxPnkJ0LjW7E6b4eWH/RjxNabmyxDtEx7BsQayMMPcuQS6T5l1iKSLdmKcDOGMUKQ==} + '@nestjs/platform-fastify@10.4.4': + resolution: {integrity: sha512-3fRt9mhhqe7aS1kF9myAFSUazhW88yrq9w3LrdHbjOTkGh8ZiZckjzKL705xORVJw2d/BHkgP8AqoNthakLJeQ==} peerDependencies: '@fastify/static': ^6.0.0 || ^7.0.0 '@fastify/view': ^7.0.0 || ^8.0.0 @@ -651,8 +655,8 @@ packages: typeorm: optional: true - '@nestjs/testing@10.4.3': - resolution: {integrity: sha512-SBNWrMU51YAlYmW86wyjlGZ2uLnASNiOPD0lBcNIlxxei0b05/aI3nh7OPuxbXQUdedUJfPq2d2jZj4TRG4S0w==} + '@nestjs/testing@10.4.4': + resolution: {integrity: sha512-qRGFj51A5RM7JqA8pcyEwSLA3Y0dle/PAZ8oxP0suimoCusRY3Tk7wYqutZdCNj1ATb678SDaUZDHk2pwSv9/g==} peerDependencies: '@nestjs/common': ^10.0.0 '@nestjs/core': ^10.0.0 @@ -681,10 +685,6 @@ packages: engines: {node: '>=8.0.0', npm: '>=5.0.0'} hasBin: true - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@pkgr/core@0.1.1': resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -772,8 +772,8 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@20.16.5': - resolution: {integrity: sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==} + '@types/node@20.16.7': + resolution: {integrity: sha512-QkDQjAY3gkvJNcZOWwzy3BN34RweT0OQ9zJyvLCU0kSK22dO2QYh/NHGfbEAYylPYzRB1/iXcojS79wOg5gFSw==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -784,8 +784,8 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@typescript-eslint/eslint-plugin@8.6.0': - resolution: {integrity: sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==} + '@typescript-eslint/eslint-plugin@8.7.0': + resolution: {integrity: sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -795,8 +795,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.6.0': - resolution: {integrity: sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==} + '@typescript-eslint/parser@8.7.0': + resolution: {integrity: sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -805,12 +805,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@8.6.0': - resolution: {integrity: sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==} + '@typescript-eslint/scope-manager@8.7.0': + resolution: {integrity: sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.6.0': - resolution: {integrity: sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==} + '@typescript-eslint/type-utils@8.7.0': + resolution: {integrity: sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -818,12 +818,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@8.6.0': - resolution: {integrity: sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==} + '@typescript-eslint/types@8.7.0': + resolution: {integrity: sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.6.0': - resolution: {integrity: sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==} + '@typescript-eslint/typescript-estree@8.7.0': + resolution: {integrity: sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -831,14 +831,14 @@ packages: typescript: optional: true - '@typescript-eslint/utils@8.6.0': - resolution: {integrity: sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==} + '@typescript-eslint/utils@8.7.0': + resolution: {integrity: sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/visitor-keys@8.6.0': - resolution: {integrity: sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==} + '@typescript-eslint/visitor-keys@8.7.0': + resolution: {integrity: sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} JSONStream@1.3.5: @@ -995,8 +995,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.23.3: - resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==} + browserslist@4.24.0: + resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1025,8 +1025,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001662: - resolution: {integrity: sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==} + caniuse-lite@1.0.30001663: + resolution: {integrity: sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==} chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -1226,8 +1226,8 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.27: - resolution: {integrity: sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==} + electron-to-chromium@1.5.28: + resolution: {integrity: sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -1314,8 +1314,8 @@ packages: resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.11.0: - resolution: {integrity: sha512-yVS6XODx+tMFMDFcG4+Hlh+qG7RM6cCJXtQhCKLSsr3XkLvWggHjCqjfh0XsPPnt1c56oaT6PMgW9XWQQjdHXA==} + eslint@9.11.1: + resolution: {integrity: sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1722,8 +1722,8 @@ packages: resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} engines: {node: '>=6'} - jackspeak@4.0.1: - resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==} + jackspeak@4.0.2: + resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==} engines: {node: 20 || >=22} jake@10.9.2: @@ -2629,8 +2629,8 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - typescript-eslint@8.6.0: - resolution: {integrity: sha512-eEhhlxCEpCd4helh3AO1hk0UP2MvbRi9CtIAJTVPQjuSXOOO2jsEacNi4UdcJzZJbeuVg1gMhtZ8UYb+NFYPrA==} + typescript-eslint@8.7.0: + resolution: {integrity: sha512-nEHbEYJyHwsuf7c3V3RS7Saq+1+la3i0ieR3qP0yjqWSzVmh8Drp47uOl9LjbPANac4S7EFSqvcYIKXUUwIfIQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -2784,7 +2784,7 @@ snapshots: dependencies: '@babel/compat-data': 7.25.4 '@babel/helper-validator-option': 7.24.8 - browserslist: 4.23.3 + browserslist: 4.24.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -2947,11 +2947,11 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@commitlint/cli@19.5.0(@types/node@20.16.5)(typescript@5.6.2)': + '@commitlint/cli@19.5.0(@types/node@20.16.7)(typescript@5.6.2)': dependencies: '@commitlint/format': 19.5.0 '@commitlint/lint': 19.5.0 - '@commitlint/load': 19.5.0(@types/node@20.16.5)(typescript@5.6.2) + '@commitlint/load': 19.5.0(@types/node@20.16.7)(typescript@5.6.2) '@commitlint/read': 19.5.0 '@commitlint/types': 19.5.0 tinyexec: 0.3.0 @@ -2998,7 +2998,7 @@ snapshots: '@commitlint/rules': 19.5.0 '@commitlint/types': 19.5.0 - '@commitlint/load@19.5.0(@types/node@20.16.5)(typescript@5.6.2)': + '@commitlint/load@19.5.0(@types/node@20.16.7)(typescript@5.6.2)': dependencies: '@commitlint/config-validator': 19.5.0 '@commitlint/execute-rule': 19.5.0 @@ -3006,7 +3006,7 @@ snapshots: '@commitlint/types': 19.5.0 chalk: 5.3.0 cosmiconfig: 9.0.0(typescript@5.6.2) - cosmiconfig-typescript-loader: 5.0.0(@types/node@20.16.5)(cosmiconfig@9.0.0(typescript@5.6.2))(typescript@5.6.2) + cosmiconfig-typescript-loader: 5.0.0(@types/node@20.16.7)(cosmiconfig@9.0.0(typescript@5.6.2))(typescript@5.6.2) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -3057,9 +3057,9 @@ snapshots: '@types/conventional-commits-parser': 5.0.0 chalk: 5.3.0 - '@eslint-community/eslint-utils@4.4.0(eslint@9.11.0(jiti@1.21.6))': + '@eslint-community/eslint-utils@4.4.0(eslint@9.11.1(jiti@1.21.6))': dependencies: - eslint: 9.11.0(jiti@1.21.6) + eslint: 9.11.1(jiti@1.21.6) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.11.1': {} @@ -3072,6 +3072,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/core@0.6.0': {} + '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 @@ -3086,7 +3088,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.11.0': {} + '@eslint/js@9.11.1': {} '@eslint/object-schema@2.1.4': {} @@ -3155,7 +3157,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.16.5 + '@types/node': 20.16.7 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -3168,14 +3170,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.16.5 + '@types/node': 20.16.7 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.16.5) + jest-config: 29.7.0(@types/node@20.16.7) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -3200,7 +3202,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.16.5 + '@types/node': 20.16.7 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -3218,7 +3220,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.16.5 + '@types/node': 20.16.7 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -3240,7 +3242,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.16.5 + '@types/node': 20.16.7 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -3310,7 +3312,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.16.5 + '@types/node': 20.16.7 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -3333,7 +3335,7 @@ snapshots: '@lukeed/csprng@1.1.0': {} - '@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: iterare: 1.2.1 reflect-metadata: 0.2.2 @@ -3341,9 +3343,9 @@ snapshots: tslib: 2.7.0 uid: 2.0.2 - '@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/core@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -3355,31 +3357,31 @@ snapshots: transitivePeerDependencies: - encoding - '@nestjs/platform-fastify@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))': + '@nestjs/platform-fastify@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))': dependencies: '@fastify/cors': 9.0.1 '@fastify/formbody': 7.4.0 '@fastify/middie': 8.3.3 - '@nestjs/common': 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) fastify: 4.28.1 light-my-request: 6.0.0 path-to-regexp: 3.3.0 tslib: 2.7.0 - '@nestjs/terminus@10.2.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/terminus@10.2.3(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) boxen: 5.1.2 check-disk-space: 3.4.0 reflect-metadata: 0.2.2 rxjs: 7.8.1 - '@nestjs/testing@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))': + '@nestjs/testing@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1))': dependencies: - '@nestjs/common': 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1) tslib: 2.7.0 '@nodelib/fs.scandir@2.1.5': @@ -3402,9 +3404,6 @@ snapshots: transitivePeerDependencies: - encoding - '@pkgjs/parseargs@0.11.0': - optional: true - '@pkgr/core@0.1.1': {} '@redis/bloom@1.2.0(@redis/client@1.6.0)': @@ -3468,7 +3467,7 @@ snapshots: '@types/conventional-commits-parser@5.0.0': dependencies: - '@types/node': 20.16.5 + '@types/node': 20.16.7 '@types/eslint@9.6.1': dependencies: @@ -3483,7 +3482,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.16.5 + '@types/node': 20.16.7 '@types/istanbul-lib-coverage@2.0.6': {} @@ -3502,7 +3501,7 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@20.16.5': + '@types/node@20.16.7': dependencies: undici-types: 6.19.8 @@ -3514,15 +3513,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)': + '@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)': dependencies: '@eslint-community/regexpp': 4.11.1 - '@typescript-eslint/parser': 8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2) - '@typescript-eslint/scope-manager': 8.6.0 - '@typescript-eslint/type-utils': 8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2) - '@typescript-eslint/utils': 8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 8.6.0 - eslint: 9.11.0(jiti@1.21.6) + '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) + '@typescript-eslint/scope-manager': 8.7.0 + '@typescript-eslint/type-utils': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.7.0 + eslint: 9.11.1(jiti@1.21.6) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -3532,28 +3531,28 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)': + '@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)': dependencies: - '@typescript-eslint/scope-manager': 8.6.0 - '@typescript-eslint/types': 8.6.0 - '@typescript-eslint/typescript-estree': 8.6.0(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 8.6.0 + '@typescript-eslint/scope-manager': 8.7.0 + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2) + '@typescript-eslint/visitor-keys': 8.7.0 debug: 4.3.7 - eslint: 9.11.0(jiti@1.21.6) + eslint: 9.11.1(jiti@1.21.6) optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.6.0': + '@typescript-eslint/scope-manager@8.7.0': dependencies: - '@typescript-eslint/types': 8.6.0 - '@typescript-eslint/visitor-keys': 8.6.0 + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/visitor-keys': 8.7.0 - '@typescript-eslint/type-utils@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)': + '@typescript-eslint/type-utils@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)': dependencies: - '@typescript-eslint/typescript-estree': 8.6.0(typescript@5.6.2) - '@typescript-eslint/utils': 8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2) + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) debug: 4.3.7 ts-api-utils: 1.3.0(typescript@5.6.2) optionalDependencies: @@ -3562,12 +3561,12 @@ snapshots: - eslint - supports-color - '@typescript-eslint/types@8.6.0': {} + '@typescript-eslint/types@8.7.0': {} - '@typescript-eslint/typescript-estree@8.6.0(typescript@5.6.2)': + '@typescript-eslint/typescript-estree@8.7.0(typescript@5.6.2)': dependencies: - '@typescript-eslint/types': 8.6.0 - '@typescript-eslint/visitor-keys': 8.6.0 + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/visitor-keys': 8.7.0 debug: 4.3.7 fast-glob: 3.3.2 is-glob: 4.0.3 @@ -3579,20 +3578,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2)': + '@typescript-eslint/utils@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0(jiti@1.21.6)) - '@typescript-eslint/scope-manager': 8.6.0 - '@typescript-eslint/types': 8.6.0 - '@typescript-eslint/typescript-estree': 8.6.0(typescript@5.6.2) - eslint: 9.11.0(jiti@1.21.6) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@1.21.6)) + '@typescript-eslint/scope-manager': 8.7.0 + '@typescript-eslint/types': 8.7.0 + '@typescript-eslint/typescript-estree': 8.7.0(typescript@5.6.2) + eslint: 9.11.1(jiti@1.21.6) transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@8.6.0': + '@typescript-eslint/visitor-keys@8.7.0': dependencies: - '@typescript-eslint/types': 8.6.0 + '@typescript-eslint/types': 8.7.0 eslint-visitor-keys: 3.4.3 JSONStream@1.3.5: @@ -3771,12 +3770,12 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.23.3: + browserslist@4.24.0: dependencies: - caniuse-lite: 1.0.30001662 - electron-to-chromium: 1.5.27 + caniuse-lite: 1.0.30001663 + electron-to-chromium: 1.5.28 node-releases: 2.0.18 - update-browserslist-db: 1.1.0(browserslist@4.23.3) + update-browserslist-db: 1.1.0(browserslist@4.24.0) bs-logger@0.2.6: dependencies: @@ -3799,7 +3798,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001662: {} + caniuse-lite@1.0.30001663: {} chalk@2.4.2: dependencies: @@ -3913,9 +3912,9 @@ snapshots: cookie@0.6.0: {} - cosmiconfig-typescript-loader@5.0.0(@types/node@20.16.5)(cosmiconfig@9.0.0(typescript@5.6.2))(typescript@5.6.2): + cosmiconfig-typescript-loader@5.0.0(@types/node@20.16.7)(cosmiconfig@9.0.0(typescript@5.6.2))(typescript@5.6.2): dependencies: - '@types/node': 20.16.5 + '@types/node': 20.16.7 cosmiconfig: 9.0.0(typescript@5.6.2) jiti: 1.21.6 typescript: 5.6.2 @@ -3929,13 +3928,13 @@ snapshots: optionalDependencies: typescript: 5.6.2 - create-jest@29.7.0(@types/node@20.16.5): + create-jest@29.7.0(@types/node@20.16.7): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.16.5) + jest-config: 29.7.0(@types/node@20.16.7) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -3982,7 +3981,7 @@ snapshots: dependencies: jake: 10.9.2 - electron-to-chromium@1.5.27: {} + electron-to-chromium@1.5.28: {} emittery@0.13.1: {} @@ -4008,30 +4007,30 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@9.1.0(eslint@9.11.0(jiti@1.21.6)): + eslint-config-prettier@9.1.0(eslint@9.11.1(jiti@1.21.6)): dependencies: - eslint: 9.11.0(jiti@1.21.6) + eslint: 9.11.1(jiti@1.21.6) - eslint-plugin-jest@28.8.3(@typescript-eslint/eslint-plugin@8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(jest@29.7.0(@types/node@20.16.5))(typescript@5.6.2): + eslint-plugin-jest@28.8.3(@typescript-eslint/eslint-plugin@8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(jest@29.7.0(@types/node@20.16.7))(typescript@5.6.2): dependencies: - '@typescript-eslint/utils': 8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2) - eslint: 9.11.0(jiti@1.21.6) + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) + eslint: 9.11.1(jiti@1.21.6) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2) - jest: 29.7.0(@types/node@20.16.5) + '@typescript-eslint/eslint-plugin': 8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) + jest: 29.7.0(@types/node@20.16.7) transitivePeerDependencies: - supports-color - typescript - eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.11.0(jiti@1.21.6)))(eslint@9.11.0(jiti@1.21.6))(prettier@3.3.3): + eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6))(prettier@3.3.3): dependencies: - eslint: 9.11.0(jiti@1.21.6) + eslint: 9.11.1(jiti@1.21.6) prettier: 3.3.3 prettier-linter-helpers: 1.0.0 synckit: 0.9.1 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 9.1.0(eslint@9.11.0(jiti@1.21.6)) + eslint-config-prettier: 9.1.0(eslint@9.11.1(jiti@1.21.6)) eslint-scope@8.0.2: dependencies: @@ -4042,17 +4041,20 @@ snapshots: eslint-visitor-keys@4.0.0: {} - eslint@9.11.0(jiti@1.21.6): + eslint@9.11.1(jiti@1.21.6): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0(jiti@1.21.6)) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1(jiti@1.21.6)) '@eslint-community/regexpp': 4.11.1 '@eslint/config-array': 0.18.0 + '@eslint/core': 0.6.0 '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.11.0 + '@eslint/js': 9.11.1 '@eslint/plugin-kit': 0.2.0 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.3.0 '@nodelib/fs.walk': 1.2.8 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 @@ -4300,7 +4302,7 @@ snapshots: glob@11.0.0: dependencies: foreground-child: 3.3.0 - jackspeak: 4.0.1 + jackspeak: 4.0.2 minimatch: 10.0.1 minipass: 7.1.2 package-json-from-dist: 1.0.0 @@ -4484,11 +4486,9 @@ snapshots: iterare@1.2.1: {} - jackspeak@4.0.1: + jackspeak@4.0.2: dependencies: '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 jake@10.9.2: dependencies: @@ -4509,7 +4509,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.16.5 + '@types/node': 20.16.7 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -4529,16 +4529,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.16.5): + jest-cli@29.7.0(@types/node@20.16.7): dependencies: '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.16.5) + create-jest: 29.7.0(@types/node@20.16.7) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.16.5) + jest-config: 29.7.0(@types/node@20.16.7) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -4548,7 +4548,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.16.5): + jest-config@29.7.0(@types/node@20.16.7): dependencies: '@babel/core': 7.25.2 '@jest/test-sequencer': 29.7.0 @@ -4573,7 +4573,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.16.5 + '@types/node': 20.16.7 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -4602,16 +4602,16 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.16.5 + '@types/node': 20.16.7 jest-mock: 29.7.0 jest-util: 29.7.0 - jest-extended@4.0.2(jest@29.7.0(@types/node@20.16.5)): + jest-extended@4.0.2(jest@29.7.0(@types/node@20.16.7)): dependencies: jest-diff: 29.7.0 jest-get-type: 29.6.3 optionalDependencies: - jest: 29.7.0(@types/node@20.16.5) + jest: 29.7.0(@types/node@20.16.7) jest-get-type@29.6.3: {} @@ -4619,7 +4619,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.16.5 + '@types/node': 20.16.7 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -4658,7 +4658,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.16.5 + '@types/node': 20.16.7 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -4693,7 +4693,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.16.5 + '@types/node': 20.16.7 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -4721,7 +4721,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.16.5 + '@types/node': 20.16.7 chalk: 4.1.2 cjs-module-lexer: 1.4.1 collect-v8-coverage: 1.0.2 @@ -4767,7 +4767,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.16.5 + '@types/node': 20.16.7 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -4786,7 +4786,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.16.5 + '@types/node': 20.16.7 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -4795,17 +4795,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 20.16.5 + '@types/node': 20.16.7 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.16.5): + jest@29.7.0(@types/node@20.16.7): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.16.5) + jest-cli: 29.7.0(@types/node@20.16.7) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -5433,12 +5433,12 @@ snapshots: dependencies: typescript: 5.6.2 - ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.5))(typescript@5.6.2): + ts-jest@29.2.5(@babel/core@7.25.2)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(jest@29.7.0(@types/node@20.16.7))(typescript@5.6.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.16.5) + jest: 29.7.0(@types/node@20.16.7) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -5473,11 +5473,11 @@ snapshots: type-fest@0.21.3: {} - typescript-eslint@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2): + typescript-eslint@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.6.0(@typescript-eslint/parser@8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2) - '@typescript-eslint/parser': 8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2) - '@typescript-eslint/utils': 8.6.0(eslint@9.11.0(jiti@1.21.6))(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 8.7.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2))(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) + '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) + '@typescript-eslint/utils': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.6.2) optionalDependencies: typescript: 5.6.2 transitivePeerDependencies: @@ -5494,9 +5494,9 @@ snapshots: unicorn-magic@0.1.0: {} - update-browserslist-db@1.1.0(browserslist@4.23.3): + update-browserslist-db@1.1.0(browserslist@4.24.0): dependencies: - browserslist: 4.23.3 + browserslist: 4.24.0 escalade: 3.2.0 picocolors: 1.1.0