diff --git a/middleware/src/common/index.ts b/middleware/src/common/index.ts index c8bbaeb0..afaa901b 100644 --- a/middleware/src/common/index.ts +++ b/middleware/src/common/index.ts @@ -1,3 +1 @@ -// Placeholder: common exports (interfaces, types, constants, utils) will live here. - -export const __commonPlaceholder = true; +export * from './plugin-manager'; diff --git a/middleware/src/common/plugin-manager.ts b/middleware/src/common/plugin-manager.ts new file mode 100644 index 00000000..6dac4ea6 --- /dev/null +++ b/middleware/src/common/plugin-manager.ts @@ -0,0 +1,233 @@ +import { Logger } from '@nestjs/common'; + +export interface PluginLifecycle { + name: string; + onInit?: () => Promise | void; + onDestroy?: () => Promise | void; + onError?: (error: Error) => void; + dependsOn?: string[]; +} + +export interface PluginManagerOptions { + initTimeoutMs?: number; +} + +export interface PluginInitOptions { + timeoutMs?: number; +} + +export class PluginManager { + private readonly logger = new Logger(PluginManager.name); + private readonly registry: PluginLifecycle[] = []; + private readonly initializedOrder: string[] = []; + + constructor(private readonly options: PluginManagerOptions = {}) {} + + register(plugin: PluginLifecycle): void { + if (!plugin || !plugin.name || typeof plugin.name !== 'string') { + throw new Error('Plugin must provide a non-empty name'); + } + + if (this.registry.some((entry) => entry.name === plugin.name)) { + throw new Error(`Plugin already registered: ${plugin.name}`); + } + + this.registry.push({ ...plugin }); + } + + async initAll(opts: PluginInitOptions = {}): Promise { + const timeoutMs = opts.timeoutMs ?? this.options.initTimeoutMs ?? 10_000; + const ordered = this.validatePluginOrder(); + const initialized: PluginLifecycle[] = []; + + try { + for (const plugin of ordered) { + if (!plugin.onInit) { + this.initializedOrder.push(plugin.name); + initialized.push(plugin); + continue; + } + + await this.executeInit(plugin, timeoutMs); + this.initializedOrder.push(plugin.name); + initialized.push(plugin); + } + } catch (error) { + // Clean up already initialized plugins in reverse registration order + for (const plugin of initialized.slice().reverse()) { + try { + await this.executeDestroy(plugin); + } catch (destroyError) { + this.logger.error( + `Plugin ${plugin.name} onDestroy failed after init failure`, + (destroyError as Error)?.stack, + ); + } + } + throw error; + } + } + + async destroyAll(): Promise { + const reverseOrder = [...this.registry].reverse(); + const errors: Error[] = []; + + for (const plugin of reverseOrder) { + if (!this.initializedOrder.includes(plugin.name)) { + continue; + } + + try { + await this.executeDestroy(plugin); + } catch (err) { + errors.push(err as Error); + } + } + + if (errors.length > 0) { + throw new Error( + `Plugin onDestroy failed for ${errors.length} plugin(s); first: ${errors[0].message}`, + ); + } + + this.initializedOrder.length = 0; + } + + getRegisteredPluginNames(): string[] { + return this.registry.map((p) => p.name); + } + + private validatePluginOrder(): PluginLifecycle[] { + const nameIndex = new Map(); + this.registry.forEach((plugin, idx) => nameIndex.set(plugin.name, idx)); + + for (const plugin of this.registry) { + if (!plugin.dependsOn || plugin.dependsOn.length === 0) { + continue; + } + + for (const dep of plugin.dependsOn) { + if (!nameIndex.has(dep)) { + throw new Error( + `Plugin ${plugin.name} depends on unknown plugin ${dep}`, + ); + } + + if ((nameIndex.get(dep) ?? -1) > (nameIndex.get(plugin.name) ?? -1)) { + throw new Error( + `Plugin dependency order invalid: ${plugin.name} depends on ${dep} but ${dep} was registered later`, + ); + } + } + } + + // detect circular dependency graph if any direct cycles happen + const visited = new Set(); + const stack = new Set(); + + const visit = (plugin: PluginLifecycle) => { + if (stack.has(plugin.name)) { + throw new Error(`Circular dependency detected with plugin ${plugin.name}`); + } + + if (visited.has(plugin.name)) { + return; + } + + stack.add(plugin.name); + visited.add(plugin.name); + + for (const depName of plugin.dependsOn ?? []) { + const dep = this.registry.find((entry) => entry.name === depName); + if (dep) { + visit(dep); + } + } + + stack.delete(plugin.name); + }; + + this.registry.forEach(visit); + + return [...this.registry]; + } + + private async executeInit(plugin: PluginLifecycle, timeoutMs: number) { + const onInit = plugin.onInit; + if (!onInit) { + return; + } + + let timeoutHandle: NodeJS.Timeout | undefined; + + const initPromise = Promise.resolve().then(async () => { + return onInit(); + }); + + const timerPromise = new Promise((_, reject) => { + timeoutHandle = setTimeout(() => { + reject(new Error(`onInit timeout after ${timeoutMs}ms`)); + }, timeoutMs); + }); + + try { + await Promise.race([initPromise, timerPromise]); + } catch (error) { + const err = + error instanceof Error + ? error + : new Error(String(error ?? 'Unknown error')); + const message = `Plugin ${plugin.name} onInit failed: ${err.message}`; + + this.logger.error(message, err.stack); + + if (plugin.onError) { + try { + plugin.onError(err); + } catch (subError) { + this.logger.error( + `Plugin ${plugin.name} onError threw an error`, + (subError as Error)?.stack, + ); + } + } + + throw new Error(message); + } finally { + if (timeoutHandle) { + clearTimeout(timeoutHandle); + } + } + } + + private async executeDestroy(plugin: PluginLifecycle) { + const onDestroy = plugin.onDestroy; + if (!onDestroy) { + return; + } + + try { + await Promise.resolve(onDestroy()); + } catch (error) { + const err = + error instanceof Error + ? error + : new Error(String(error ?? 'Unknown error')); + const message = `Plugin ${plugin.name} onDestroy failed: ${err.message}`; + this.logger.error(message, err.stack); + + if (plugin.onError) { + try { + plugin.onError(err); + } catch (subError) { + this.logger.error( + `Plugin ${plugin.name} onError threw an error during destroy`, + (subError as Error)?.stack, + ); + } + } + + throw new Error(message); + } + } +} diff --git a/middleware/tests/unit/plugin-manager.spec.ts b/middleware/tests/unit/plugin-manager.spec.ts new file mode 100644 index 00000000..f7e41b01 --- /dev/null +++ b/middleware/tests/unit/plugin-manager.spec.ts @@ -0,0 +1,124 @@ +import { PluginManager } from '../../src/common/plugin-manager'; + +describe('PluginManager', () => { + beforeEach(() => { + jest.useRealTimers(); + }); + + it('calls onInit in registration order and onDestroy in reverse', async () => { + const sequence: string[] = []; + const manager = new PluginManager(); + + manager.register({ + name: 'plugin-a', + onInit: () => { + sequence.push('plugin-a.init'); + }, + onDestroy: () => { + sequence.push('plugin-a.destroy'); + }, + }); + + manager.register({ + name: 'plugin-b', + onInit: () => { + sequence.push('plugin-b.init'); + }, + onDestroy: () => { + sequence.push('plugin-b.destroy'); + }, + }); + + await manager.initAll(); + await manager.destroyAll(); + + expect(sequence).toEqual([ + 'plugin-a.init', + 'plugin-b.init', + 'plugin-b.destroy', + 'plugin-a.destroy', + ]); + }); + + it('aborts bootstrap on onInit throw and includes plugin name', async () => { + const destroyed: string[] = []; + const onError = jest.fn(); + const manager = new PluginManager(); + + manager.register({ + name: 'plugin-a', + onInit: () => { + destroyed.push('plugin-a.init'); + }, + onDestroy: () => { + destroyed.push('plugin-a.destroy'); + }, + }); + + manager.register({ + name: 'plugin-b', + onInit: () => { + throw new Error('fail B'); + }, + onDestroy: () => { + destroyed.push('plugin-b.destroy'); + }, + onError, + }); + + await expect(manager.initAll()).rejects.toThrow(/plugin-b/); + + // plugin-a was initialized then cleaned up, plugin-b never completed + expect(destroyed).toEqual(['plugin-a.init', 'plugin-a.destroy']); + expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'fail B' })); + }); + + it('aborts bootstrap on onInit timeout (default 10s)', async () => { + const onError = jest.fn(); + const manager = new PluginManager(); + + manager.register({ + name: 'plugin-slow', + onInit: () => new Promise(() => {}), + onError, + }); + + await expect(manager.initAll({ timeoutMs: 10 })).rejects.toThrow(/onInit timeout/); + expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: expect.stringContaining('onInit timeout') })); + }); + + it('rejects if dependency registration order is invalid', async () => { + const manager = new PluginManager(); + + manager.register({ + name: 'plugin-b', + dependsOn: ['plugin-a'], + onInit: jest.fn(), + }); + + manager.register({ + name: 'plugin-a', + onInit: jest.fn(), + }); + + await expect(manager.initAll()).rejects.toThrow(/depends on plugin-a but plugin-a was registered later/); + }); + + it('detects circular dependencies', async () => { + const manager = new PluginManager(); + + manager.register({ + name: 'plugin-a', + dependsOn: ['plugin-b'], + onInit: jest.fn(), + }); + + manager.register({ + name: 'plugin-b', + dependsOn: ['plugin-a'], + onInit: jest.fn(), + }); + + await expect(manager.initAll()).rejects.toThrow(/Circular dependency detected/); + }); +}); diff --git a/package-lock.json b/package-lock.json index 36196d54..6ac5c51a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -208,7 +208,6 @@ "version": "0.6.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@xhmikosr/bin-wrapper": "^13.0.5", @@ -251,7 +250,6 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" @@ -288,7 +286,6 @@ "version": "22.18.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -299,7 +296,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -441,7 +437,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -488,7 +483,6 @@ "version": "10.9.2", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -530,7 +524,6 @@ "backend/node_modules/typeorm": { "version": "0.3.26", "license": "MIT", - "peer": true, "dependencies": { "@sqltools/formatter": "^1.2.5", "ansis": "^3.17.0", @@ -657,7 +650,6 @@ "version": "5.8.3", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -774,13 +766,15 @@ "version": "0.1.0", "dependencies": { "@nestjs/common": "^11.0.12", + "@nestjs/config": "^4.0.0", "@types/micromatch": "^4.0.10", "bcrypt": "^6.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "express": "^5.1.0", "jsonwebtoken": "^9.0.2", - "micromatch": "^4.0.8" + "micromatch": "^4.0.8", + "stellar-sdk": "^13.1.0" }, "devDependencies": { "@types/express": "^5.0.0", @@ -1136,7 +1130,6 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -3540,7 +3533,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.14.tgz", "integrity": "sha512-IN/tlqd7Nl9gl6f0jsWEuOrQDaCI9vHzxv0fisHysfBQzfQIkqlv5A7w4Qge02BUQyczXT9HHPgHtWHCxhjRng==", "license": "MIT", - "peer": true, "dependencies": { "file-type": "21.3.0", "iterare": "1.2.1", @@ -3588,7 +3580,6 @@ "integrity": "sha512-7OXPPMoDr6z+5NkoQKu4hOhfjz/YYqM3bNilPqv1WVFWrzSmuNXxvhbX69YMmNmRYascPXiwESqf5jJdjKXEww==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", @@ -3685,7 +3676,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.12.tgz", "integrity": "sha512-GYK/vHI0SGz5m8mxr7v3Urx8b9t78Cf/dj5aJMZlGd9/1D9OI1hAl00BaphjEXINUJ/BQLxIlF2zUjrYsd6enQ==", "license": "MIT", - "peer": true, "dependencies": { "cors": "2.8.5", "express": "5.2.1", @@ -5616,7 +5606,6 @@ "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -5627,7 +5616,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -5756,7 +5744,6 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -6476,7 +6463,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "devOptional": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6549,7 +6535,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6979,7 +6964,6 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -7379,7 +7363,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7719,7 +7702,6 @@ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -7776,15 +7758,13 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/class-validator": { "version": "0.14.3", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", "license": "MIT", - "peer": true, "dependencies": { "@types/validator": "^13.15.3", "libphonenumber-js": "^1.11.1", @@ -8619,6 +8599,7 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -8984,7 +8965,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9174,7 +9154,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -9649,7 +9628,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -11090,7 +11068,6 @@ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.2.tgz", "integrity": "sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==", "license": "MIT", - "peer": true, "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", @@ -11756,7 +11733,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -13347,6 +13323,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -14549,7 +14526,6 @@ "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", "license": "MIT", - "peer": true, "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -14724,7 +14700,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -15040,7 +15015,6 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -15300,7 +15274,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -15310,7 +15283,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -15330,7 +15302,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -15402,8 +15373,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -15418,8 +15388,7 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -15722,7 +15691,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -16373,7 +16341,6 @@ "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", "hasInstallScript": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", @@ -17149,7 +17116,6 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -17354,7 +17320,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -17775,7 +17740,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18223,6 +18187,7 @@ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -18241,6 +18206,7 @@ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -18253,7 +18219,8 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", @@ -18261,6 +18228,7 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -18275,6 +18243,7 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -18284,7 +18253,8 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/webpack/node_modules/schema-utils": { "version": "4.3.3", @@ -18292,6 +18262,7 @@ "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0",