diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 407bece5..b146e555 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,14 +40,13 @@ jobs: distribution: temurin java-version: 17 - name: Execute build test jacocoTestReport and sonar analysis - if: endsWith(github.REF, '/master') == true + if: endsWith(github.REF, '/master') == true || github.event.pull_request.head.repo.fork == false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew build test jacocoTestReport sonar --refresh-dependencies --no-daemon --continue -Denv.ci=true + run: ./gradlew clean build generateMergedReport sonar --refresh-dependencies --no-daemon --continue -Denv.ci=true - name: Execute build test jacocoTestReport pull request - if: endsWith(github.REF, '/merge') == true + if: github.event.pull_request.head.repo.fork == true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew build test jacocoTestReport --refresh-dependencies --no-daemon --continue -Denv.ci=true \ No newline at end of file + run: ./gradlew clean build generateMergedReport --refresh-dependencies --no-daemon --continue -Denv.ci=true \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a93eaa31..68c28a76 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,7 @@ name: release on: release: - types: [ released ] + types: [ released, prereleased ] jobs: release: runs-on: ubuntu-latest diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml new file mode 100644 index 00000000..1f88ecd6 --- /dev/null +++ b/.github/workflows/updater.yml @@ -0,0 +1,41 @@ +name: updater +on: + workflow_dispatch: + schedule: + - cron: '0 12 * * 5' # every Friday at 07:00 Colombia Time +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + - name: Check for updates + run: ./gradlew internalTask --action UPDATE_DEPENDENCIES + - name: Check for changes + id: git_changes + run: | + git diff --name-only + if [[ $(git diff --name-only) ]]; then + echo "Changes detected!" + echo "HAS_CHANGES=true" >> $GITHUB_ENV + else + echo "No changes detected!" + echo "HAS_CHANGES=false" >> $GITHUB_ENV + fi + - name: Create Pull Request + if: env.HAS_CHANGES == 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.PA_TOKEN }} + committer: Dependencies Bot + commit-message: 'fix(deps): update dependencies' + title: 'fix(deps): update dependencies' + body: 'This PR updates dependencies to latest versions' + branch: 'feature/autoupdate-deps' + base: 'master' + labels: 'dependencies' + reviewers: 'juancgalvis' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8a16dda7..47bd6ef3 100644 --- a/.gitignore +++ b/.gitignore @@ -644,4 +644,5 @@ MigrationBackup/ # End of https://www.toptal.com/developers/gitignore/api/macos,linux,windows,gradle,java,intellij,visualstudio,eclipse contiperf-report -samples/async/local-example/ \ No newline at end of file +samples/async/local-example/ +.kafka-env \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a4f054e..8047f4db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,207 @@ # Changelog +## [v5.4.1](https://github.com/reactive-commons/reactive-commons-java/tree/v5.4.1) (2025-06-09) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.4.0...v5.4.1) + +**Merged pull requests:** + +- fix\(discard\): Allow easy discard notifier overriding [\#144](https://github.com/reactive-commons/reactive-commons-java/pull/144) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.4.0](https://github.com/reactive-commons/reactive-commons-java/tree/v5.4.0) (2025-04-10) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.3.1...v5.4.0) + +**Merged pull requests:** + +- feat\(open-multi-domain-api\): Allow use any domain as source or destination [\#139](https://github.com/reactive-commons/reactive-commons-java/pull/139) ([juancgalvis](https://github.com/juancgalvis)) +- fix\(deps\): update dependencies [\#138](https://github.com/reactive-commons/reactive-commons-java/pull/138) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.3.1](https://github.com/reactive-commons/reactive-commons-java/tree/v5.3.1) (2025-02-24) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.3.0...v5.3.1) + +**Merged pull requests:** + +- chore\(deps\): update dependencies [\#137](https://github.com/reactive-commons/reactive-commons-java/pull/137) ([luisgomez29](https://github.com/luisgomez29)) +- fix\(deps\): update dependencies [\#135](https://github.com/reactive-commons/reactive-commons-java/pull/135) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.3.0](https://github.com/reactive-commons/reactive-commons-java/tree/v5.3.0) (2025-02-24) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.3...v5.3.0) + +**Merged pull requests:** + +- feat\(raw-handler\): Raw event handler [\#136](https://github.com/reactive-commons/reactive-commons-java/pull/136) ([juancgalvis](https://github.com/juancgalvis)) +- refactor: fix code semells [\#134](https://github.com/reactive-commons/reactive-commons-java/pull/134) ([luisgomez29](https://github.com/luisgomez29)) + +## [v5.2.3](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.3) (2025-01-24) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.2...v5.2.3) + +**Implemented enhancements:** + +- Add unit tests for classes \(RabbitDomainEventBus, RabbitDiscardNotifier\) [\#75](https://github.com/reactive-commons/reactive-commons-java/issues/75) +- Test de class TopologyCreator of module async-rabbit [\#74](https://github.com/reactive-commons/reactive-commons-java/issues/74) +- Increase coverage by testing classes \(QueryExecutor, EventExecutor and CommandExecutor\) [\#72](https://github.com/reactive-commons/reactive-commons-java/issues/72) +- Centralize bintray task configuration in a single place [\#8](https://github.com/reactive-commons/reactive-commons-java/issues/8) + +**Closed issues:** + +- Acerca de Reactive Commons y Secretos AWS Secrets Manager [\#81](https://github.com/reactive-commons/reactive-commons-java/issues/81) +- Allow multi broker configuration [\#80](https://github.com/reactive-commons/reactive-commons-java/issues/80) +- Update DynamicRegistryImp reactive commons SQS to new DynamicRegistry especification [\#78](https://github.com/reactive-commons/reactive-commons-java/issues/78) +- Add interaction diagrams to documentation [\#76](https://github.com/reactive-commons/reactive-commons-java/issues/76) +- Circular dependency with HandlerResolver and Dynamic registry [\#55](https://github.com/reactive-commons/reactive-commons-java/issues/55) + +**Merged pull requests:** + +- fix\(deps\): update dependencies [\#133](https://github.com/reactive-commons/reactive-commons-java/pull/133) ([juancgalvis](https://github.com/juancgalvis)) +- build\(deps\): bump path-to-regexp and express in /docs [\#132](https://github.com/reactive-commons/reactive-commons-java/pull/132) ([dependabot[bot]](https://github.com/apps/dependabot)) +- docs\(updated\): Add secret example documentation and updater actions [\#131](https://github.com/reactive-commons/reactive-commons-java/pull/131) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.2.2](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.2) (2025-01-09) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.1...v5.2.2) + +**Merged pull requests:** + +- fix\(dynamic-registry\): move to respective listener bean dependency [\#130](https://github.com/reactive-commons/reactive-commons-java/pull/130) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.2.1](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.1) (2025-01-08) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.2.0...v5.2.1) + +**Merged pull requests:** + +- fix\(circular-dependency\): Extract listener config and remove handler resolver from replies listener [\#129](https://github.com/reactive-commons/reactive-commons-java/pull/129) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.2.0](https://github.com/reactive-commons/reactive-commons-java/tree/v5.2.0) (2024-12-27) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.3...v5.2.0) + +**Merged pull requests:** + +- feat\(tls\): ssl default options [\#128](https://github.com/reactive-commons/reactive-commons-java/pull/128) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.1.3](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.3) (2024-12-27) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.2...v5.1.3) + +**Closed issues:** + +- Compile error message when using reactive-commons-java and Spring Boot [\#93](https://github.com/reactive-commons/reactive-commons-java/issues/93) + +**Merged pull requests:** + +- fix\(docs\): Update docs for secret filler and log when rabbit connection error [\#127](https://github.com/reactive-commons/reactive-commons-java/pull/127) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.1.2](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.2) (2024-12-14) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.2-beta...v5.1.2) + +## [v5.1.2-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.2-beta) (2024-12-14) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.1-beta...v5.1.2-beta) + +**Merged pull requests:** + +- chore\(health\): ignore health indicator when dependency not exists [\#126](https://github.com/reactive-commons/reactive-commons-java/pull/126) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.1.1-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.1-beta) (2024-12-05) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.1.0-beta...v5.1.1-beta) + +**Merged pull requests:** + +- docs\(scenarios\): Add scenarios documentation [\#125](https://github.com/reactive-commons/reactive-commons-java/pull/125) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.1.0-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.1.0-beta) (2024-10-11) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.1-beta...v5.1.0-beta) + +**Merged pull requests:** + +- chore\(next\): Create shared starter [\#124](https://github.com/reactive-commons/reactive-commons-java/pull/124) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.0.1-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.1-beta) (2024-09-02) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.5...v5.0.1-beta) + +**Merged pull requests:** + +- build\(deps\): bump micromatch from 4.0.7 to 4.0.8 in /docs [\#123](https://github.com/reactive-commons/reactive-commons-java/pull/123) ([dependabot[bot]](https://github.com/apps/dependabot)) +- chore\(next\): Fix kafka starter [\#121](https://github.com/reactive-commons/reactive-commons-java/pull/121) ([juancgalvis](https://github.com/juancgalvis)) + +## [v4.1.5](https://github.com/reactive-commons/reactive-commons-java/tree/v4.1.5) (2024-09-02) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.0-beta...v4.1.5) + +**Merged pull requests:** + +- build\(deps\): bump webpack from 5.91.0 to 5.94.0 in /docs [\#122](https://github.com/reactive-commons/reactive-commons-java/pull/122) ([dependabot[bot]](https://github.com/apps/dependabot)) +- docs\(kafka\): kafka docs [\#120](https://github.com/reactive-commons/reactive-commons-java/pull/120) ([AndresFelipe11](https://github.com/AndresFelipe11)) + +## [v5.0.0-beta](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.0-beta) (2024-08-27) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.3-alpha...v5.0.0-beta) + +**Merged pull requests:** + +- chore\(next\): Update starters and extract common reusable code to an aditional module [\#119](https://github.com/reactive-commons/reactive-commons-java/pull/119) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.0.3-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.3-alpha) (2024-08-23) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.2-alpha...v5.0.3-alpha) + +**Merged pull requests:** + +- chore\(next\): Fix test, artifactid and docs [\#118](https://github.com/reactive-commons/reactive-commons-java/pull/118) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.0.2-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.2-alpha) (2024-08-23) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.1-alpha...v5.0.2-alpha) + +**Merged pull requests:** + +- chore\(next\): Unify rabbitmq starter with starter eda [\#117](https://github.com/reactive-commons/reactive-commons-java/pull/117) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.0.1-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.1-alpha) (2024-08-20) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v5.0.0-alpha...v5.0.1-alpha) + +**Closed issues:** + +- Adjust to reactive-commons library according to the CloudEvents specification. [\#111](https://github.com/reactive-commons/reactive-commons-java/issues/111) + +**Merged pull requests:** + +- chore\(kafka\): Implement kafka binding with support for Domain Events [\#116](https://github.com/reactive-commons/reactive-commons-java/pull/116) ([juancgalvis](https://github.com/juancgalvis)) + +## [v5.0.0-alpha](https://github.com/reactive-commons/reactive-commons-java/tree/v5.0.0-alpha) (2024-08-08) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.4...v5.0.0-alpha) + +**Merged pull requests:** + +- chore\(next\): add native support for Cloud Events [\#115](https://github.com/reactive-commons/reactive-commons-java/pull/115) ([jcanacon](https://github.com/jcanacon)) + +## [v4.1.4](https://github.com/reactive-commons/reactive-commons-java/tree/v4.1.4) (2024-07-25) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.3...v4.1.4) + +**Merged pull requests:** + +- fix\(health\): Use blocking IO for health indicator connection [\#114](https://github.com/reactive-commons/reactive-commons-java/pull/114) ([jhonatan-kmt](https://github.com/jhonatan-kmt)) + +## [v4.1.3](https://github.com/reactive-commons/reactive-commons-java/tree/v4.1.3) (2024-07-17) + +[Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.2...v4.1.3) + +**Merged pull requests:** + +- fix\(notifier\): Set the discard notifier with domain connection properties [\#113](https://github.com/reactive-commons/reactive-commons-java/pull/113) ([jhonatan-kmt](https://github.com/jhonatan-kmt)) + ## [v4.1.2](https://github.com/reactive-commons/reactive-commons-java/tree/v4.1.2) (2024-07-09) [Full Changelog](https://github.com/reactive-commons/reactive-commons-java/compare/v4.1.1...v4.1.2) diff --git a/README.md b/README.md index c75ae2f9..b0442e35 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # reactive-commons-java The purpose of reactive-commons is to provide a set of abstractions and implementations over different patterns and practices that make the foundation of a reactive microservices architecture. -Docs: https://reactivecommons.org/reactive-commons-java/ +Docs: [https://bancolombia.github.io/reactive-commons-java/](https://bancolombia.github.io/reactive-commons-java) Other projects: https://github.com/bancolombia diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/CommandsProcessPerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/CommandsProcessPerfTest.java index d1afdf0d..c4f974a1 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/CommandsProcessPerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/CommandsProcessPerfTest.java @@ -27,7 +27,7 @@ class CommandsProcessPerfTest { private static final String COMMAND_NAME = "app.command.test"; - private static final int messageCount = 40000; + private static final int MESSAGE_COUNT = 40000; private static final Semaphore semaphore = new Semaphore(0); private static final CountDownLatch latch = new CountDownLatch(12 + 1); @@ -41,18 +41,18 @@ class CommandsProcessPerfTest { @Test void commandShouldArrive() throws InterruptedException { final long init_p = System.currentTimeMillis(); - createMessages(messageCount); + createMessages(MESSAGE_COUNT); final long end_p = System.currentTimeMillis() - init_p; System.out.println("Total Publication Time: " + end_p + "ms"); latch.countDown(); final long init = System.currentTimeMillis(); - semaphore.acquire(messageCount); + semaphore.acquire(MESSAGE_COUNT); final long end = System.currentTimeMillis(); final long total = end - init; - final double microsPerMessage = ((total + 0.0) / messageCount) * 1000; - System.out.println("Message count: " + messageCount); + final double microsPerMessage = ((total + 0.0) / MESSAGE_COUNT) * 1000; + System.out.println("Message count: " + MESSAGE_COUNT); System.out.println("Total Execution Time: " + total + "ms"); System.out.println("Microseconds per message: " + microsPerMessage + "us"); if (System.getProperty("env.ci") == null) { @@ -82,7 +82,10 @@ public static void main(String[] args) { @Bean public HandlerRegistry registry() { - final HandlerRegistry registry = range(0, 20).reduce(HandlerRegistry.register(), (r, i) -> r.handleCommand("app.command.name" + i, message -> Mono.empty(), Map.class)).block(); + final HandlerRegistry registry = range(0, 20) + .reduce(HandlerRegistry.register(), (r, i) -> + r.handleCommand("app.command.name" + i, message -> Mono.empty(), Map.class)) + .block(); return registry .handleCommand(COMMAND_NAME, this::handleSimple, DummyMessage.class); } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java index b312eef5..208277a5 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DirectGatewayPerfTest.java @@ -3,8 +3,8 @@ import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.reactivecommons.api.domain.Command; -import org.reactivecommons.async.rabbit.RabbitDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; +import org.reactivecommons.async.rabbit.RabbitDirectAsyncGateway; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; @@ -24,7 +24,7 @@ class DirectGatewayPerfTest { private static final String COMMAND_NAME = "app.command.test"; - private static final int messageCount = 40000; + private static final int MESSAGE_COUNT = 40000; private static final Semaphore semaphore = new Semaphore(0); @Autowired @@ -36,17 +36,17 @@ class DirectGatewayPerfTest { @Test void shouldSendInOptimalTime() throws InterruptedException { - final Flux> messages = createMessages(messageCount); + final Flux> messages = createMessages(MESSAGE_COUNT); final Flux target = messages.flatMap(dummyMessageCommand -> gateway.sendCommand(dummyMessageCommand, appName) .doOnSuccess(aVoid -> semaphore.release())); final long init = System.currentTimeMillis(); target.subscribe(); - semaphore.acquire(messageCount); + semaphore.acquire(MESSAGE_COUNT); final long end = System.currentTimeMillis(); - assertMessageThroughput(end - init, messageCount, 200); + assertMessageThroughput(end - init, MESSAGE_COUNT, 200); } @Test @@ -67,8 +67,10 @@ void shouldSendBatchInOptimalTime1Channel() throws InterruptedException { private void shouldSendBatchInOptimalTimeNChannels(int channels) throws InterruptedException { List> subs = new ArrayList<>(channels); for (int i = 0; i < channels; ++i) { - final Flux> messages = createMessages(messageCount / channels); - final Mono target = gateway.sendCommands(messages, appName).then().doOnSuccess(_v -> semaphore.release()); + final Flux> messages = createMessages(MESSAGE_COUNT / channels); + final Mono target = gateway.sendCommands(messages, appName) + .then() + .doOnSuccess(_v -> semaphore.release()); subs.add(target); } @@ -79,7 +81,7 @@ private void shouldSendBatchInOptimalTimeNChannels(int channels) throws Interrup final long end = System.currentTimeMillis(); final long total = end - init; - assertMessageThroughput(total, messageCount, 230); + assertMessageThroughput(total, MESSAGE_COUNT, 230); } private void assertMessageThroughput(long total, long messageCount, int reqMicrosPerMessage) { @@ -94,7 +96,9 @@ private void assertMessageThroughput(long total, long messageCount, int reqMicro } private Flux> createMessages(int count) { - final List> commands = IntStream.range(0, count).mapToObj(value -> new Command<>(COMMAND_NAME, UUID.randomUUID().toString(), new DummyMessage())).collect(Collectors.toList()); + final List> commands = IntStream.range(0, count) + .mapToObj(value -> new Command<>(COMMAND_NAME, UUID.randomUUID().toString(), new DummyMessage())) + .collect(Collectors.toList()); return Flux.fromIterable(commands); } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DummyMessage.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DummyMessage.java index a36f3cff..d2445516 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DummyMessage.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DummyMessage.java @@ -1,6 +1,7 @@ package org.reactivecommons.test; import lombok.Data; + import java.util.concurrent.ThreadLocalRandom; @Data diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java index f91c01f6..1d6e380b 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/DynamicRegistryTest.java @@ -4,7 +4,7 @@ import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DynamicRegistry; -import org.reactivecommons.async.api.handlers.EventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.impl.config.annotations.EnableDomainEventBus; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.reactivestreams.Publisher; @@ -13,7 +13,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.time.Duration; @@ -35,21 +35,20 @@ class DynamicRegistryTest { @Test void shouldReceiveResponse() { - UnicastProcessor result = UnicastProcessor.create(); - EventHandler fn = message -> fromRunnable(() -> result.onNext(message.getData())); + Sinks.Many result = Sinks.many().unicast().onBackpressureBuffer(); + DomainEventHandler fn = message -> fromRunnable( + () -> result.emitNext(message.getData(), Sinks.EmitFailureHandler.FAIL_FAST) + ); dynamicRegistry.listenEvent("test.event", fn, String.class).block(); final Publisher emit = eventBus.emit(new DomainEvent<>("test.event", "42", "Hello")); from(emit).block(); - StepVerifier.create(result.next().timeout(Duration.ofSeconds(10))) + StepVerifier.create(result.asFlux().next().timeout(Duration.ofSeconds(10))) .expectNext("Hello") .verifyComplete(); - - } - @SpringBootApplication @EnableMessageListeners @EnableDomainEventBus @@ -57,6 +56,5 @@ static class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } - } } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/QueryProcessPerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/QueryProcessPerfTest.java index 7fafa565..cc85ac89 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/QueryProcessPerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/QueryProcessPerfTest.java @@ -30,7 +30,7 @@ class QueryProcessPerfTest { private static final String QUERY_NAME = "app.command.test"; - private static final int messageCount = 40000; + private static final int MESSAGE_COUNT = 40000; private static final Semaphore semaphore = new Semaphore(0); private static final AtomicLong atomicLong = new AtomicLong(0); private static final CountDownLatch latch = new CountDownLatch(12 + 1); @@ -44,19 +44,20 @@ class QueryProcessPerfTest { @Test void serveQueryPerformanceTest() throws InterruptedException { - final Flux> messages = createMessages(messageCount); + final Flux> messages = createMessages(MESSAGE_COUNT); final long init = System.currentTimeMillis(); messages - .flatMap(dummyMessageAsyncQuery -> gateway.requestReply(dummyMessageAsyncQuery, appName, DummyMessage.class) - .doOnNext(s -> semaphore.release()) + .flatMap(dummyMessageAsyncQuery -> + gateway.requestReply(dummyMessageAsyncQuery, appName, DummyMessage.class) + .doOnNext(s -> semaphore.release()) ) .subscribe(); - semaphore.acquire(messageCount); + semaphore.acquire(MESSAGE_COUNT); final long end = System.currentTimeMillis(); final long total = end - init; - assertMessageThroughput(total, messageCount, 200); + assertMessageThroughput(total, MESSAGE_COUNT, 200); } private void assertMessageThroughput(long total, long messageCount, int reqMicrosPerMessage) { @@ -72,7 +73,9 @@ private void assertMessageThroughput(long total, long messageCount, int reqMicro private Flux> createMessages(int count) { - final List> queryList = IntStream.range(0, count).mapToObj(_v -> new AsyncQuery<>(QUERY_NAME, new DummyMessage())).collect(Collectors.toList()); + final List> queryList = IntStream.range(0, count) + .mapToObj(_v -> new AsyncQuery<>(QUERY_NAME, new DummyMessage())) + .collect(Collectors.toList()); return Flux.fromIterable(queryList); } @@ -87,7 +90,11 @@ public static void main(String[] args) { @Bean public HandlerRegistry registry() { - final HandlerRegistry registry = range(0, 20).reduce(HandlerRegistry.register(), (r, i) -> r.handleCommand("app.command.name" + i, message -> Mono.empty(), Map.class)).block(); + final HandlerRegistry registry = range(0, 20) + .reduce(HandlerRegistry.register(), (r, i) -> r.handleCommand( + "app.command.name" + i, message -> Mono.empty(), Map.class + )) + .block(); return registry .serveQuery(QUERY_NAME, this::handleSimple, DummyMessage.class); } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java index ae8b2419..06abd8e8 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleDirectCommunicationTest.java @@ -5,7 +5,7 @@ import org.reactivecommons.async.api.AsyncQuery; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.CommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.springframework.beans.factory.annotation.Autowired; @@ -15,14 +15,15 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import reactor.core.publisher.Mono; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.time.Duration; import java.util.concurrent.ThreadLocalRandom; import static org.assertj.core.api.Assertions.assertThat; -import static reactor.core.publisher.Mono.*; +import static reactor.core.publisher.Mono.empty; +import static reactor.core.publisher.Mono.just; @SpringBootTest class SimpleDirectCommunicationTest { @@ -35,20 +36,18 @@ class SimpleDirectCommunicationTest { @Value("${spring.application.name}") private String appName; - @Autowired - private UnicastProcessor> listener; - - private String commandId = ThreadLocalRandom.current().nextInt() + ""; - private Long data = ThreadLocalRandom.current().nextLong(); + private final String commandId = ThreadLocalRandom.current().nextInt() + ""; + private final Long data = ThreadLocalRandom.current().nextLong(); @Test void commandShouldArrive() { Command command = new Command<>(COMMAND_NAME, commandId, data); gateway.sendCommand(command, appName).subscribe(); + Sinks.Many> listener = Sinks.many().unicast().onBackpressureBuffer(); - StepVerifier.create(listener.next()).assertNext(cmd -> { + StepVerifier.create(listener.asFlux().next()).assertNext(cmd -> { assertThat(cmd).extracting(Command::getCommandId, Command::getData, Command::getName) - .containsExactly(commandId, data, COMMAND_NAME); + .containsExactly(commandId, data, COMMAND_NAME); }).verifyComplete(); } @@ -56,34 +55,33 @@ void commandShouldArrive() { void shouldReceiveResponse() { final Mono reply = gateway.requestReply(new AsyncQuery<>("double", 42), appName, Integer.class); StepVerifier.create(reply.timeout(Duration.ofSeconds(15))) - .expectNext(42*2) - .verifyComplete(); + .expectNext(42 * 2) + .verifyComplete(); } - @SpringBootApplication @EnableDirectAsyncGateway @EnableMessageListeners - static class App{ + static class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } @Bean - public HandlerRegistry registry(UnicastProcessor> listener) { + public HandlerRegistry registry(Sinks.Many> listener) { return HandlerRegistry.register() - .serveQuery("double", rqt -> just(rqt*2), Long.class) - .handleCommand(COMMAND_NAME, handle(listener), Long.class); + .serveQuery("double", rqt -> just(rqt * 2), Long.class) + .handleCommand(COMMAND_NAME, handle(listener), Long.class); } @Bean - public UnicastProcessor> listener() { - return UnicastProcessor.create(); + public Sinks.Many> listener() { + return Sinks.many().unicast().onBackpressureBuffer(); } - private CommandHandler handle(UnicastProcessor> listener) { + private DomainCommandHandler handle(Sinks.Many> listener) { return command -> { - listener.onNext(command); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); return empty(); }; } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java index 3ff83c83..ff5c7949 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/SimpleEventNotificationTest.java @@ -4,7 +4,7 @@ import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.EventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.impl.config.annotations.EnableDomainEventBus; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.springframework.beans.factory.annotation.Autowired; @@ -12,13 +12,15 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.util.concurrent.ThreadLocalRandom; import static org.assertj.core.api.Assertions.assertThat; -import static reactor.core.publisher.Mono.*; +import static reactor.core.publisher.Mono.empty; +import static reactor.core.publisher.Mono.from; +import static reactor.core.publisher.Mono.just; @SpringBootTest class SimpleEventNotificationTest { @@ -28,17 +30,15 @@ class SimpleEventNotificationTest { @Autowired private DomainEventBus eventBus; - @Autowired - private UnicastProcessor> listener; - - private String eventId = ThreadLocalRandom.current().nextInt() + ""; - private Long data = ThreadLocalRandom.current().nextLong(); + private final String eventId = ThreadLocalRandom.current().nextInt() + ""; + private final Long data = ThreadLocalRandom.current().nextLong(); @Test void shouldReceiveEvent() throws InterruptedException { DomainEvent event = new DomainEvent<>(EVENT_NAME, eventId, data); + Sinks.Many> listener = Sinks.many().unicast().onBackpressureBuffer(); from(eventBus.emit(event)).subscribe(); - StepVerifier.create(listener.take(1)).assertNext(evt -> + StepVerifier.create(listener.asFlux().take(1)).assertNext(evt -> assertThat(evt).extracting(DomainEvent::getName, DomainEvent::getEventId, DomainEvent::getData) .containsExactly(EVENT_NAME, eventId, data) ).verifyComplete(); @@ -54,20 +54,20 @@ public static void main(String[] args) { } @Bean - public HandlerRegistry registry(UnicastProcessor> listener) { + public HandlerRegistry registry(Sinks.Many> listener) { return HandlerRegistry.register() .serveQuery("double", rqt -> just(rqt * 2), Long.class) .listenEvent(EVENT_NAME, handle(listener), Long.class); } @Bean - public UnicastProcessor> listener() { - return UnicastProcessor.create(); + public Sinks.Many> listener() { + return Sinks.many().unicast().onBackpressureBuffer(); } - private EventHandler handle(UnicastProcessor> listener) { + private DomainEventHandler handle(Sinks.Many> listener) { return command -> { - listener.onNext(command); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); return empty(); }; } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java index 75808a2f..f146ee9a 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/BlockingCommandHandlePerfTest.java @@ -4,7 +4,7 @@ import org.reactivecommons.api.domain.Command; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.CommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.springframework.beans.factory.annotation.Autowired; @@ -14,7 +14,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import reactor.core.publisher.Flux; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.time.Duration; @@ -23,7 +23,7 @@ import static java.lang.System.out; import static org.assertj.core.api.Assertions.assertThat; -import static reactor.core.publisher.Mono.*; +import static reactor.core.publisher.Mono.fromRunnable; @SpringBootTest class BlockingCommandHandlePerfTest { @@ -36,14 +36,13 @@ class BlockingCommandHandlePerfTest { @Value("${spring.application.name}") private String appName; - @Autowired - private UnicastProcessor> listener; - - private String commandId = ThreadLocalRandom.current().nextInt() + ""; - private Long data = ThreadLocalRandom.current().nextLong(); + private final String commandId = ThreadLocalRandom.current().nextInt() + ""; + private final Long data = ThreadLocalRandom.current().nextLong(); @Test void commandShouldBeHandledInParallel() throws InterruptedException { + Sinks.Many> listener = Sinks.many().unicast().onBackpressureBuffer(); + Flux.range(0, 12).flatMap(i -> { Command command = new Command<>(COMMAND_NAME, commandId + 1, data + 1); return gateway.sendCommand(command, appName); @@ -51,13 +50,13 @@ void commandShouldBeHandledInParallel() throws InterruptedException { final long init = System.currentTimeMillis(); - final Flux> results = listener.take(12).collectList() + final Flux> results = listener.asFlux().take(12).collectList() .timeout(Duration.ofMillis(1500)) .flatMapMany(Flux::fromIterable); StepVerifier.create(results).assertNext(cmd -> { - assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); - }) + assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); + }) .expectNextCount(11) .verifyComplete(); @@ -79,30 +78,25 @@ public static void main(String[] args) { } @Bean - public HandlerRegistry registry(UnicastProcessor> listener) { + public HandlerRegistry registry(Sinks.Many> listener) { return HandlerRegistry.register() .handleCommand(COMMAND_NAME, handle(listener), Long.class); } @Bean - public UnicastProcessor> listener() { - return UnicastProcessor.create(); + public Sinks.Many> listener() { + return Sinks.many().unicast().onBackpressureBuffer(); } - private CommandHandler handle(UnicastProcessor> listener) { - return command -> { - return fromRunnable(() -> { -// out.println("Received at: " + System.currentTimeMillis()/1000); - try { -// out.println("internal: " + Thread.currentThread().getName()); - TimeUnit.MILLISECONDS.sleep(750); -// out.println("Handled at: " + System.currentTimeMillis()/1000); - listener.onNext(command); - } catch (InterruptedException e) { - } - listener.onNext(command); - }); - }; + private DomainCommandHandler handle(Sinks.Many> listener) { + return command -> fromRunnable(() -> { + try { + TimeUnit.MILLISECONDS.sleep(750); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); + } catch (InterruptedException ignored) { + } + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); + }); } } } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java index b41711bd..c2d00722 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/ParallelOnBlockingInSubscriptionTimeTest.java @@ -5,7 +5,7 @@ import org.reactivecommons.api.domain.Command; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.CommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.springframework.beans.factory.annotation.Autowired; @@ -15,7 +15,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import reactor.core.publisher.Flux; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.time.Duration; @@ -37,15 +37,13 @@ class ParallelOnBlockingInSubscriptionTimeTest { @Value("${spring.application.name}") private String appName; - @Autowired - private UnicastProcessor> listener; - - private String commandId = ThreadLocalRandom.current().nextInt() + ""; - private Long data = ThreadLocalRandom.current().nextLong(); + private final String commandId = ThreadLocalRandom.current().nextInt() + ""; + private final Long data = ThreadLocalRandom.current().nextLong(); @Test @Disabled - public void commandShouldBeHandledInParallel() throws InterruptedException { + void commandShouldBeHandledInParallel() throws InterruptedException { + Sinks.Many> listener = Sinks.many().unicast().onBackpressureBuffer(); Flux.range(0, 12).flatMap(i -> { Command command = new Command<>(COMMAND_NAME, commandId + 1, data + 1); return gateway.sendCommand(command, appName); @@ -53,13 +51,13 @@ public void commandShouldBeHandledInParallel() throws InterruptedException { final long init = System.currentTimeMillis(); - final Flux> results = listener.take(12).collectList() + final Flux> results = listener.asFlux().take(12).collectList() .timeout(Duration.ofMillis(1500)) .flatMapMany(Flux::fromIterable); StepVerifier.create(results).assertNext(cmd -> { - assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); - }) + assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); + }) .expectNextCount(11) .verifyComplete(); @@ -81,27 +79,24 @@ public static void main(String[] args) { } @Bean - public HandlerRegistry registry(UnicastProcessor> listener) { + public HandlerRegistry registry(Sinks.Many> listener) { return HandlerRegistry.register() .handleCommand(COMMAND_NAME, handle(listener), Long.class); } @Bean - public UnicastProcessor> listener() { - return UnicastProcessor.create(); + public Sinks.Many> listener() { + return Sinks.many().unicast().onBackpressureBuffer(); } - private CommandHandler handle(UnicastProcessor> listener) { + private DomainCommandHandler handle(Sinks.Many> listener) { return command -> { -// out.println("Received at: " + System.currentTimeMillis()/1000); try { -// out.println("internal: " + Thread.currentThread().getName()); TimeUnit.MILLISECONDS.sleep(750); -// out.println("Handled at: " + System.currentTimeMillis()/1000); - listener.onNext(command); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); } catch (InterruptedException e) { } - listener.onNext(command); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); return empty(); }; } diff --git a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java index b359aaa2..582bf059 100644 --- a/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java +++ b/acceptance/async-tests/src/test/java/org/reactivecommons/test/perf/SimpleCommandHandlePerfTest.java @@ -5,7 +5,7 @@ import org.reactivecommons.api.domain.Command; import org.reactivecommons.async.api.DirectAsyncGateway; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.CommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.impl.config.annotations.EnableDirectAsyncGateway; import org.reactivecommons.async.impl.config.annotations.EnableMessageListeners; import org.springframework.beans.factory.annotation.Autowired; @@ -15,7 +15,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import reactor.core.publisher.Flux; -import reactor.core.publisher.UnicastProcessor; +import reactor.core.publisher.Sinks; import reactor.test.StepVerifier; import java.time.Duration; @@ -36,15 +36,13 @@ class SimpleCommandHandlePerfTest { @Value("${spring.application.name}") private String appName; - @Autowired - private UnicastProcessor> listener; - - private String commandId = ThreadLocalRandom.current().nextInt() + ""; - private Long data = ThreadLocalRandom.current().nextLong(); + private final String commandId = ThreadLocalRandom.current().nextInt() + ""; + private final Long data = ThreadLocalRandom.current().nextLong(); @Test @Disabled - public void commandShouldBeHandledInParallel() { + void commandShouldBeHandledInParallel() { + Sinks.Many> listener = Sinks.many().unicast().onBackpressureBuffer(); Flux.range(0, 30).flatMap(i -> { Command command = new Command<>(COMMAND_NAME, commandId + 1, data + 1); return gateway.sendCommand(command, appName); @@ -52,13 +50,13 @@ public void commandShouldBeHandledInParallel() { final long init = System.currentTimeMillis(); - final Flux> results = listener.take(30).collectList() + final Flux> results = listener.asFlux().take(30).collectList() .timeout(Duration.ofMillis(3500)) .flatMapMany(Flux::fromIterable); StepVerifier.create(results).assertNext(cmd -> { - assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); - }) + assertThat(cmd.getName()).isEqualTo(COMMAND_NAME); + }) .expectNextCount(29) .verifyComplete(); @@ -78,23 +76,23 @@ public static void main(String[] args) { } @Bean - public HandlerRegistry registry(UnicastProcessor> listener) { + public HandlerRegistry registry(Sinks.Many> listener) { return HandlerRegistry.register() .handleCommand(COMMAND_NAME, handle(listener), Long.class); } @Bean - public UnicastProcessor> listener() { - return UnicastProcessor.create(); + public Sinks.Many> listener() { + return Sinks.many().unicast().onBackpressureBuffer(); } - private CommandHandler handle(UnicastProcessor> listener) { + private DomainCommandHandler handle(Sinks.Many> listener) { return command -> { out.println("Received at: " + System.currentTimeMillis() / 1000); out.println("Received in: " + Thread.currentThread().getName()); return delay(Duration.ofMillis(750)).doOnNext(i -> { out.println("Handled at: " + System.currentTimeMillis() / 1000); - listener.onNext(command); + listener.emitNext(command, Sinks.EmitFailureHandler.FAIL_FAST); }).then(); }; } diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DefaultCommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DefaultCommandHandler.java index ba21dffe..51eefc1a 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DefaultCommandHandler.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DefaultCommandHandler.java @@ -1,6 +1,6 @@ package org.reactivecommons.async.api; -import org.reactivecommons.async.api.handlers.CommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; -public interface DefaultCommandHandler extends CommandHandler { +public interface DefaultCommandHandler extends DomainCommandHandler { } diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java index 1501035b..ee48e05e 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DirectAsyncGateway.java @@ -5,7 +5,7 @@ import reactor.core.publisher.Mono; public interface DirectAsyncGateway { - public static final String DELAYED = "rc-delay"; + String DELAYED = "rc-delay"; Mono sendCommand(Command command, String targetName); @@ -17,8 +17,12 @@ public interface DirectAsyncGateway { Mono sendCommand(CloudEvent command, String targetName); + Mono sendCommand(CloudEvent command, String targetName, long delayMillis); + Mono sendCommand(CloudEvent command, String targetName, String domain); + Mono sendCommand(CloudEvent command, String targetName, long delayMillis, String domain); + Mono requestReply(AsyncQuery query, String targetName, Class type); Mono requestReply(AsyncQuery query, String targetName, Class type, String domain); diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java index 043f95c6..e24de8d3 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/DynamicRegistry.java @@ -1,6 +1,6 @@ package org.reactivecommons.async.api; -import org.reactivecommons.async.api.handlers.EventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; import reactor.core.publisher.Mono; @@ -8,7 +8,7 @@ public interface DynamicRegistry { @Deprecated - Mono listenEvent(String eventName, EventHandler fn, Class eventClass); + Mono listenEvent(String eventName, DomainEventHandler fn, Class eventClass); void serveQuery(String resource, QueryHandler handler, Class queryClass); diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java index d5e35fc3..1df771da 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/HandlerRegistry.java @@ -1,34 +1,39 @@ package org.reactivecommons.async.api; import io.cloudevents.CloudEvent; -import io.cloudevents.core.provider.EventFormatProvider; -import io.cloudevents.jackson.JsonFormat; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import org.reactivecommons.async.api.handlers.CommandHandler; -import org.reactivecommons.async.api.handlers.EventHandler; +import org.reactivecommons.api.domain.RawMessage; +import org.reactivecommons.async.api.handlers.CloudCommandHandler; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; +import org.reactivecommons.async.api.handlers.RawCommandHandler; +import org.reactivecommons.async.api.handlers.RawEventHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; +import org.reactivecommons.async.api.handlers.registered.RegisteredDomainHandlers; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; import java.lang.reflect.ParameterizedType; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @Getter @NoArgsConstructor(access = AccessLevel.PACKAGE) -public class HandlerRegistry { +public final class HandlerRegistry { public static final String DEFAULT_DOMAIN = "app"; - private final Map>> domainEventListeners = new ConcurrentHashMap<>(); - private final List> dynamicEventHandlers = new CopyOnWriteArrayList<>(); - private final List> eventNotificationListener = new CopyOnWriteArrayList<>(); - private final List> handlers = new CopyOnWriteArrayList<>(); - private final List> commandHandlers = new CopyOnWriteArrayList<>(); + private final RegisteredDomainHandlers> domainEventListeners = + new RegisteredDomainHandlers<>(); + private final RegisteredDomainHandlers> dynamicEventHandlers = + new RegisteredDomainHandlers<>(); + private final RegisteredDomainHandlers> eventNotificationListener = + new RegisteredDomainHandlers<>(); + private final RegisteredDomainHandlers> handlers = new RegisteredDomainHandlers<>(); + private final RegisteredDomainHandlers> commandHandlers = + new RegisteredDomainHandlers<>(); public static HandlerRegistry register() { @@ -37,75 +42,163 @@ public static HandlerRegistry register() { return instance; } - public HandlerRegistry listenDomainEvent(String domain, String eventName, EventHandler handler, Class eventClass) { - domainEventListeners.computeIfAbsent(domain, ignored -> new CopyOnWriteArrayList<>()) - .add(new RegisteredEventListener<>(eventName, handler, eventClass)); + //events: DomainEvent + public HandlerRegistry listenEvent(String eventName, DomainEventHandler handler, Class eventClass) { + return listenDomainEvent(DEFAULT_DOMAIN, eventName, handler, eventClass); + } + + public HandlerRegistry listenDomainEvent(String domain, String eventName, DomainEventHandler handler, + Class eventClass) { + domainEventListeners.add(domain, new RegisteredEventListener<>(eventName, handler, eventClass)); return this; } - public HandlerRegistry listenEvent(String eventName, EventHandler handler, Class eventClass) { - domainEventListeners.computeIfAbsent(DEFAULT_DOMAIN, ignored -> new CopyOnWriteArrayList<>()) - .add(new RegisteredEventListener<>(eventName, handler, eventClass)); + // events: CloudEvent + public HandlerRegistry listenCloudEvent(String eventName, CloudEventHandler handler) { + return listenDomainCloudEvent(DEFAULT_DOMAIN, eventName, handler); + } + + public HandlerRegistry listenDomainCloudEvent(String domain, String eventName, CloudEventHandler handler) { + domainEventListeners.add(domain, new RegisteredEventListener<>(eventName, handler, CloudEvent.class)); return this; } - public HandlerRegistry listenEvent(String eventName, EventHandler handler) { - return listenEvent(eventName, handler, inferGenericParameterType(handler)); + // events: RawMessage + public HandlerRegistry listenRawEvent(String eventName, RawEventHandler handler) { + return listenDomainRawEvent(DEFAULT_DOMAIN, eventName, handler); } - public HandlerRegistry listenNotificationEvent(String eventName, EventHandler handler, Class eventClass) { - eventNotificationListener.add(new RegisteredEventListener<>(eventName, handler, eventClass)); + public HandlerRegistry listenDomainRawEvent(String domain, String eventName, RawEventHandler handler) { + domainEventListeners.add(domain, new RegisteredEventListener<>(eventName, handler, RawMessage.class)); return this; } - public HandlerRegistry handleDynamicEvents(String eventNamePattern, EventHandler handler, Class eventClass) { - dynamicEventHandlers.add(new RegisteredEventListener<>(eventNamePattern, handler, eventClass)); + // notifications: DomainEvent + public HandlerRegistry listenNotificationEvent(String eventName, DomainEventHandler handler, + Class eventClass) { + return listenNotificationEvent(DEFAULT_DOMAIN, eventName, handler, eventClass); + } + + public HandlerRegistry listenNotificationEvent(String domain, String eventName, DomainEventHandler handler, + Class eventClass) { + eventNotificationListener.add(domain, new RegisteredEventListener<>(eventName, handler, eventClass)); return this; } - public HandlerRegistry handleDynamicEvents(String eventNamePattern, EventHandler handler) { - return handleDynamicEvents(eventNamePattern, handler, inferGenericParameterType(handler)); + // notifications: CloudEvent + public HandlerRegistry listenNotificationCloudEvent(String eventName, CloudEventHandler handler) { + return listenNotificationCloudEvent(DEFAULT_DOMAIN, eventName, handler); } - public HandlerRegistry handleCommand(String commandName, CommandHandler fn, Class commandClass) { - commandHandlers.add(new RegisteredCommandHandler<>(commandName, fn, commandClass)); + public HandlerRegistry listenNotificationCloudEvent(String domain, String eventName, CloudEventHandler handler) { + eventNotificationListener.add(domain, new RegisteredEventListener<>(eventName, handler, CloudEvent.class)); return this; } - public HandlerRegistry handleCommand(String commandName, CommandHandler fn) { - commandHandlers.add(new RegisteredCommandHandler<>(commandName, fn, inferGenericParameterType(fn))); + // notifications: RawMessage + public HandlerRegistry listenNotificationRawEvent(String eventName, RawEventHandler handler) { + return listenNotificationRawEvent(DEFAULT_DOMAIN, eventName, handler); + } + + public HandlerRegistry listenNotificationRawEvent(String domain, String eventName, RawEventHandler handler) { + eventNotificationListener.add(domain, new RegisteredEventListener<>(eventName, handler, RawMessage.class)); return this; } - public HandlerRegistry serveQuery(String resource, QueryHandler handler) { - return serveQuery(resource, handler, inferGenericParameterType(handler)); + // dynamic: DomainEvent supported only for default domain + public HandlerRegistry handleDynamicEvents(String eventNamePattern, DomainEventHandler handler, + Class eventClass) { + dynamicEventHandlers.add(DEFAULT_DOMAIN, new RegisteredEventListener<>(eventNamePattern, handler, eventClass)); + return this; } - public HandlerRegistry serveQuery(String resource, QueryHandler handler, Class queryClass) { - if(queryClass == CloudEvent.class){ - handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> - { - CloudEvent query = EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .deserialize(message); + // dynamic: CloudEvent supported only for default domain + public HandlerRegistry handleDynamicCloudEvents(String eventNamePattern, CloudEventHandler handler) { + dynamicEventHandlers.add(DEFAULT_DOMAIN, new RegisteredEventListener<>(eventNamePattern, handler, + CloudEvent.class)); + return this; + } - return handler.handle((R) query); + // commands: Command + public HandlerRegistry handleCommand(String commandName, DomainCommandHandler fn, Class commandClass) { + return handleCommand(DEFAULT_DOMAIN, commandName, fn, commandClass); + } - } , byte[].class)); - } - else{ - handlers.add(new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message), queryClass)); - } + public HandlerRegistry handleCommand(String domain, String commandName, DomainCommandHandler fn, + Class commandClass) { + commandHandlers.add(domain, new RegisteredCommandHandler<>(commandName, fn, commandClass)); + return this; + } + + // commands: CloudEvent + public HandlerRegistry handleCloudEventCommand(String commandName, CloudCommandHandler handler) { + return handleCloudEventCommand(DEFAULT_DOMAIN, commandName, handler); + } + + public HandlerRegistry handleCloudEventCommand(String domain, String commandName, CloudCommandHandler handler) { + commandHandlers.add(domain, new RegisteredCommandHandler<>(commandName, handler, CloudEvent.class)); + return this; + } + + // commands: RawMessage + public HandlerRegistry handleRawCommand(String commandName, RawCommandHandler handler) { + return handleRawCommand(DEFAULT_DOMAIN, commandName, handler); + } + + public HandlerRegistry handleRawCommand(String domain, String commandName, RawCommandHandler handler) { + commandHandlers.add(domain, new RegisteredCommandHandler<>(commandName, handler, RawMessage.class)); + return this; + } + + // queries: Query + public HandlerRegistry serveQuery(String resource, QueryHandler handler, Class queryClass) { + handlers.add(DEFAULT_DOMAIN, new RegisteredQueryHandler<>(resource, (ignored, message) -> + handler.handle(message), queryClass + )); return this; } public HandlerRegistry serveQuery(String resource, QueryHandlerDelegate handler, Class queryClass) { - handlers.add(new RegisteredQueryHandler<>(resource, handler, queryClass)); + handlers.add(DEFAULT_DOMAIN, new RegisteredQueryHandler<>(resource, handler, queryClass)); + return this; + } + + public HandlerRegistry serveCloudEventQuery(String resource, QueryHandler handler) { + handlers.add(DEFAULT_DOMAIN, new RegisteredQueryHandler<>(resource, (ignored, message) -> + handler.handle(message), CloudEvent.class + )); + return this; + } + + public HandlerRegistry serveCloudEventQuery(String resource, QueryHandlerDelegate handler) { + handlers.add(DEFAULT_DOMAIN, new RegisteredQueryHandler<>(resource, handler, CloudEvent.class)); return this; } + @Deprecated(forRemoval = true) + public HandlerRegistry listenEvent(String eventName, DomainEventHandler handler) { + return listenEvent(eventName, handler, inferGenericParameterType(handler)); + } + + @Deprecated(forRemoval = true) + public HandlerRegistry handleDynamicEvents(String eventNamePattern, DomainEventHandler handler) { + return handleDynamicEvents(eventNamePattern, handler, inferGenericParameterType(handler)); + } + + @Deprecated(forRemoval = true) + public HandlerRegistry handleCommand(String commandName, DomainCommandHandler handler) { + commandHandlers.add(DEFAULT_DOMAIN, new RegisteredCommandHandler<>(commandName, handler, + inferGenericParameterType(handler))); + return this; + } + + @Deprecated(forRemoval = true) + public HandlerRegistry serveQuery(String resource, QueryHandler handler) { + return serveQuery(resource, handler, inferGenericParameterType(handler)); + } + + @Deprecated(forRemoval = true) @SuppressWarnings("unchecked") private Class inferGenericParameterType(QueryHandler handler) { try { @@ -117,8 +210,9 @@ private Class inferGenericParameterType(QueryHandler handler) { } } + @Deprecated(forRemoval = true) @SuppressWarnings("unchecked") - private Class inferGenericParameterType(CommandHandler handler) { + private Class inferGenericParameterType(DomainCommandHandler handler) { try { ParameterizedType genericSuperclass = (ParameterizedType) handler.getClass().getGenericInterfaces()[0]; return (Class) genericSuperclass.getActualTypeArguments()[0]; @@ -128,7 +222,8 @@ private Class inferGenericParameterType(CommandHandler handler) { } } - private Class inferGenericParameterType(EventHandler handler) { + @Deprecated(forRemoval = true) + private Class inferGenericParameterType(DomainEventHandler handler) { try { ParameterizedType genericSuperclass = (ParameterizedType) handler.getClass().getGenericInterfaces()[0]; return (Class) genericSuperclass.getActualTypeArguments()[0]; diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudCommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudCommandHandler.java new file mode 100644 index 00000000..294a4dd8 --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudCommandHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import io.cloudevents.CloudEvent; + +public interface CloudCommandHandler extends CommandHandler { +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudEventHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudEventHandler.java new file mode 100644 index 00000000..86878903 --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CloudEventHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import io.cloudevents.CloudEvent; + +public interface CloudEventHandler extends EventHandler { +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CommandHandler.java index 4b84b53d..887267cd 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CommandHandler.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/CommandHandler.java @@ -1,6 +1,4 @@ package org.reactivecommons.async.api.handlers; -import org.reactivecommons.api.domain.Command; - -public interface CommandHandler extends GenericHandler> { +public interface CommandHandler extends GenericHandler { } diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainCommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainCommandHandler.java new file mode 100644 index 00000000..8ddf34ad --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainCommandHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import org.reactivecommons.api.domain.Command; + +public interface DomainCommandHandler extends CommandHandler> { +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainEventHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainEventHandler.java new file mode 100644 index 00000000..ff0d929c --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/DomainEventHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import org.reactivecommons.api.domain.DomainEvent; + +public interface DomainEventHandler extends EventHandler>{ +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/EventHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/EventHandler.java index 038251fb..45041bb0 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/EventHandler.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/EventHandler.java @@ -1,6 +1,4 @@ package org.reactivecommons.async.api.handlers; -import org.reactivecommons.api.domain.DomainEvent; - -public interface EventHandler extends GenericHandler> { +public interface EventHandler extends GenericHandler { } diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawCommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawCommandHandler.java new file mode 100644 index 00000000..c2c63bad --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawCommandHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import org.reactivecommons.api.domain.RawMessage; + +public interface RawCommandHandler extends CommandHandler { +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawEventHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawEventHandler.java new file mode 100644 index 00000000..9ed19005 --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/RawEventHandler.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.api.handlers; + +import org.reactivecommons.api.domain.RawMessage; + +public interface RawEventHandler extends EventHandler { +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredCommandHandler.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredCommandHandler.java index 290086ab..adc1c694 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredCommandHandler.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredCommandHandler.java @@ -6,8 +6,8 @@ @RequiredArgsConstructor @Getter -public class RegisteredCommandHandler { +public class RegisteredCommandHandler { private final String path; - private final CommandHandler handler; + private final CommandHandler handler; private final Class inputClass; } diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredDomainHandlers.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredDomainHandlers.java new file mode 100644 index 00000000..1410e005 --- /dev/null +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredDomainHandlers.java @@ -0,0 +1,18 @@ +package org.reactivecommons.async.api.handlers.registered; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public class RegisteredDomainHandlers extends ConcurrentHashMap> { + private static final String DEFAULT_DOMAIN = "app"; + + public RegisteredDomainHandlers() { + super(); + put(DEFAULT_DOMAIN, new CopyOnWriteArrayList<>()); + } + + public void add(String domain, T handler) { + computeIfAbsent(domain, ignored -> new CopyOnWriteArrayList<>()).add(handler); + } +} diff --git a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredEventListener.java b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredEventListener.java index e0d2103f..48e66bc2 100644 --- a/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredEventListener.java +++ b/async/async-commons-api/src/main/java/org/reactivecommons/async/api/handlers/registered/RegisteredEventListener.java @@ -6,8 +6,8 @@ @RequiredArgsConstructor @Getter -public class RegisteredEventListener { +public class RegisteredEventListener { private final String path; - private final EventHandler handler; + private final EventHandler handler; private final Class inputClass; } diff --git a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java index 121feeb8..cb46b2ef 100644 --- a/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java +++ b/async/async-commons-api/src/test/java/org/reactivecommons/async/api/HandlerRegistryTest.java @@ -1,14 +1,19 @@ package org.reactivecommons.async.api; +import io.cloudevents.CloudEvent; import lombok.Data; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.reactivecommons.api.domain.Command; import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.async.api.handlers.CommandHandler; -import org.reactivecommons.async.api.handlers.EventHandler; +import org.reactivecommons.api.domain.RawMessage; +import org.reactivecommons.async.api.handlers.CloudCommandHandler; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; +import org.reactivecommons.async.api.handlers.RawCommandHandler; +import org.reactivecommons.async.api.handlers.RawEventHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; @@ -21,46 +26,89 @@ class HandlerRegistryTest { private final HandlerRegistry registry = HandlerRegistry.register(); private final String name = "some.event"; + private final String nameRaw = "some.raw.event"; + private final String nameRawNotification = "some.raw.notification.event"; + private final String domain = "some-domain"; + @Test - void shouldListenEventWithTypeInferenceWhenClassInstanceIsUsed() { - SomeEventHandler eventHandler = new SomeEventHandler(); + void shouldListenDomainEvent() { + SomeDomainEventHandler eventHandler = new SomeDomainEventHandler<>(); - registry.listenEvent(name, eventHandler); + registry.listenDomainEvent(domain, name, eventHandler, SomeDataClass.class); - assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) + assertThat(registry.getDomainEventListeners().get(domain)) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) .containsExactly(name, SomeDataClass.class, eventHandler)).hasSize(1); } @Test - void shouldRegisterPatternEventHandlerWithTypeInference() { - SomeEventHandler eventHandler = new SomeEventHandler(); + void shouldListenDomainCloudEvent() { + SomeCloudEventHandler eventHandler = new SomeCloudEventHandler(); - String eventNamePattern = "a.*"; + registry.listenDomainCloudEvent(domain, name, eventHandler); - HandlerRegistry resultRegistry = registry.listenEvent(eventNamePattern, eventHandler); - RegisteredEventListener expectedRegisteredEventListener = - new RegisteredEventListener<>(eventNamePattern, eventHandler, SomeDataClass.class); + assertThat(registry.getDomainEventListeners().get(domain)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) + .containsExactly(name, CloudEvent.class, eventHandler)).hasSize(1); + } + + @Test + void shouldListenDomainRawEvent() { + SomeRawEventHandler eventHandler = new SomeRawEventHandler(); + + registry.listenRawEvent(name, eventHandler); assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) - .anySatisfy(registeredEventListener -> assertThat(registeredEventListener) - .usingRecursiveComparison() - .isEqualTo(expectedRegisteredEventListener)); + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) + .containsExactly(name, RawMessage.class, eventHandler)).hasSize(1); + } - assertThat(resultRegistry) - .isSameAs(registry); + @Test + void shouldListenEvent() { + SomeDomainEventHandler eventHandler = new SomeDomainEventHandler<>(); + + registry.listenEvent(name, eventHandler, SomeDataClass.class); + + assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) + .containsExactly(name, SomeDataClass.class, eventHandler)).hasSize(1); + } + + @Test + void shouldListenCloudEvent() { + SomeCloudEventHandler eventHandler = new SomeCloudEventHandler(); + + registry.listenCloudEvent(name, eventHandler); + + assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) + .containsExactly(name, CloudEvent.class, eventHandler)).hasSize(1); } @Test void shouldRegisterPatternEventHandler() { - SomeEventHandler eventHandler = new SomeEventHandler(); + SomeDomainEventHandler eventHandler = new SomeDomainEventHandler<>(); String eventNamePattern = "a.*"; HandlerRegistry resultRegistry = registry.listenEvent(eventNamePattern, eventHandler, SomeDataClass.class); - RegisteredEventListener expectedRegisteredEventListener = + RegisteredEventListener> expectedRegisteredEventListener = new RegisteredEventListener<>(eventNamePattern, eventHandler, SomeDataClass.class); assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) @@ -75,60 +123,132 @@ void shouldRegisterPatternEventHandler() { @Test void shouldRegisterNotificationEventListener() { registry.listenNotificationEvent(name, message -> Mono.empty(), SomeDataClass.class); - assertThat(registry.getEventNotificationListener()) + assertThat(registry.getEventNotificationListener().get(DEFAULT_DOMAIN)) .anySatisfy(listener -> assertThat(listener.getPath()).isEqualTo(name)); } + @Test + void shouldRegisterNotificationCloudEventListener() { + registry.listenNotificationCloudEvent(name, message -> Mono.empty()); + assertThat(registry.getEventNotificationListener().get(DEFAULT_DOMAIN)) + .anySatisfy(listener -> assertThat(listener.getPath()).isEqualTo(name)); + } + + @Test + void shouldRegisterNotificationRawEventListener() { + SomeRawEventHandler eventHandler = new SomeRawEventHandler(); + + registry.listenNotificationRawEvent(nameRawNotification, eventHandler); + + assertThat(registry.getEventNotificationListener().get(DEFAULT_DOMAIN)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) + .containsExactly(nameRawNotification, RawMessage.class, eventHandler)).hasSize(1); + } + @Test @SuppressWarnings("unchecked") - public void listenEvent() { - EventHandler handler = mock(EventHandler.class); + void listenEvent() { + SomeDomainEventHandler handler = mock(SomeDomainEventHandler.class); registry.listenEvent(name, handler, SomeDataClass.class); assertThat(registry.getDomainEventListeners().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, RegisteredEventListener::getHandler) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) .containsExactly(name, SomeDataClass.class, handler)).hasSize(1); } @Test - void handleCommandWithTypeInference() { - SomeCommandHandler handler = new SomeCommandHandler(); + void shouldListenDynamicEvent() { + SomeDomainEventHandler eventHandler = new SomeDomainEventHandler<>(); + + registry.handleDynamicEvents(name, eventHandler, SomeDataClass.class); + + assertThat(registry.getDynamicEventHandlers().get(DEFAULT_DOMAIN)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) + .containsExactly(name, SomeDataClass.class, eventHandler)).hasSize(1); + } + + @Test + void shouldListenDynamicCloudEvent() { + SomeCloudEventHandler eventHandler = new SomeCloudEventHandler(); - registry.handleCommand(name, handler); + registry.handleDynamicCloudEvents(name, eventHandler); - assertThat(registry.getCommandHandlers()) + assertThat(registry.getDynamicEventHandlers().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) - .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, RegisteredCommandHandler::getHandler) + .extracting(RegisteredEventListener::getPath, RegisteredEventListener::getInputClass, + RegisteredEventListener::getHandler + ) + .containsExactly(name, CloudEvent.class, eventHandler)).hasSize(1); + } + + @Test + void handleDomainCommand() { + SomeDomainCommandHandler handler = new SomeDomainCommandHandler<>(); + + registry.handleCommand(name, handler, SomeDataClass.class); + + assertThat(registry.getCommandHandlers().get(DEFAULT_DOMAIN)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, + RegisteredCommandHandler::getHandler + ) .containsExactly(name, SomeDataClass.class, handler)).hasSize(1); } @Test - void handleCommandWithoutTypeShouldFail() { - Assertions.assertThrows( - RuntimeException.class, - () -> registry.handleCommand(name, (Command message) -> Mono.empty())); + void handleCloudEventCommand() { + SomeCloudCommandHandler cloudCommandHandler = new SomeCloudCommandHandler(); + + registry.handleCloudEventCommand(name, cloudCommandHandler); + + assertThat(registry.getCommandHandlers().get(DEFAULT_DOMAIN)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, + RegisteredCommandHandler::getHandler + ) + .containsExactly(name, CloudEvent.class, cloudCommandHandler)).hasSize(1); } @Test - void listenEventWithoutTypeShouldFail() { - Assertions.assertThrows( - RuntimeException.class, - () -> registry.listenEvent(name, (DomainEvent message) -> Mono.empty())); + void handleRawCommand() { + SomeRawCommandEventHandler eventHandler = new SomeRawCommandEventHandler(); + + registry.handleRawCommand(nameRaw, eventHandler); + + assertThat(registry.getCommandHandlers().get(DEFAULT_DOMAIN)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass, + RegisteredCommandHandler::getHandler + ) + .containsExactly(nameRaw, RawMessage.class, eventHandler)).hasSize(1); } @Test - void handleQueryWithoutTypeShouldFail() { - Assertions.assertThrows( - RuntimeException.class, - () -> registry.serveQuery(name, (SomeDataClass query) -> Mono.empty())); + void shouldServerCloudEventQuery() { + SomeCloudEventQueryHandler queryHandler = new SomeCloudEventQueryHandler(); + + registry.serveCloudEventQuery(name, queryHandler); + + assertThat(registry.getHandlers().get(DEFAULT_DOMAIN)) + .anySatisfy(registered -> assertThat(registered) + .extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) + .containsExactly(name, CloudEvent.class)).hasSize(1); } @Test void handleCommandWithLambda() { registry.handleCommand(name, (Command message) -> Mono.empty(), SomeDataClass.class); - assertThat(registry.getCommandHandlers()) + assertThat(registry.getCommandHandlers().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) .extracting(RegisteredCommandHandler::getPath, RegisteredCommandHandler::getInputClass) .containsExactly(name, SomeDataClass.class)).hasSize(1); @@ -138,7 +258,7 @@ void handleCommandWithLambda() { @Test void serveQueryWithLambda() { registry.serveQuery(name, message -> Mono.empty(), SomeDataClass.class); - assertThat(registry.getHandlers()) + assertThat(registry.getHandlers().get(DEFAULT_DOMAIN)) .anySatisfy(registered -> assertThat(registered) .extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, SomeDataClass.class)).hasSize(1); @@ -147,11 +267,12 @@ void serveQueryWithLambda() { @Test void serveQueryWithTypeInference() { QueryHandler handler = new SomeQueryHandler(); - registry.serveQuery(name, handler); - assertThat(registry.getHandlers()).anySatisfy(registered -> { + registry.serveQuery(name, handler, SomeDataClass.class); + assertThat(registry.getHandlers().get(DEFAULT_DOMAIN)).anySatisfy(registered -> { assertThat(registered).extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, SomeDataClass.class); - assertThat(registered).extracting(RegisteredQueryHandler::getHandler).isInstanceOf(QueryHandlerDelegate.class); + assertThat(registered).extracting(RegisteredQueryHandler::getHandler) + .isInstanceOf(QueryHandlerDelegate.class); }).hasSize(1); } @@ -159,7 +280,7 @@ void serveQueryWithTypeInference() { void serveQueryDelegate() { QueryHandlerDelegate handler = new SomeQueryHandlerDelegate(); registry.serveQuery(name, handler, SomeDataClass.class); - assertThat(registry.getHandlers()).anySatisfy(registered -> { + assertThat(registry.getHandlers().get(DEFAULT_DOMAIN)).anySatisfy(registered -> { assertThat(registered).extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, SomeDataClass.class); }).hasSize(1); @@ -168,7 +289,7 @@ void serveQueryDelegate() { @Test void serveQueryDelegateWithLambda() { registry.serveQuery(name, (from, message) -> Mono.empty(), SomeDataClass.class); - assertThat(registry.getHandlers()).anySatisfy(registered -> { + assertThat(registry.getHandlers().get(DEFAULT_DOMAIN)).anySatisfy(registered -> { assertThat(registered).extracting(RegisteredQueryHandler::getPath, RegisteredQueryHandler::getQueryClass) .containsExactly(name, SomeDataClass.class); }).hasSize(1); @@ -181,20 +302,48 @@ public Mono handle(From from, SomeDataClass message) { } } - private static class SomeEventHandler implements EventHandler { + private static class SomeDomainEventHandler implements DomainEventHandler { @Override public Mono handle(DomainEvent message) { return Mono.empty(); } } - private static class SomeCommandHandler implements CommandHandler { + private static class SomeCloudEventHandler implements CloudEventHandler { + @Override + public Mono handle(CloudEvent message) { + return null; + } + } + + private static class SomeRawEventHandler implements RawEventHandler { + @Override + public Mono handle(RawMessage message) { + return null; + } + } + + private static class SomeDomainCommandHandler implements DomainCommandHandler { @Override public Mono handle(Command message) { return Mono.empty(); } } + private static class SomeCloudCommandHandler implements CloudCommandHandler { + @Override + public Mono handle(CloudEvent message) { + return null; + } + } + + private static class SomeRawCommandEventHandler implements RawCommandHandler { + @Override + public Mono handle(RawMessage message) { + return Mono.empty(); + } + } + private static class SomeQueryHandler implements QueryHandler { @Override public Mono handle(SomeDataClass message) { @@ -202,6 +351,12 @@ public Mono handle(SomeDataClass message) { } } + private static class SomeCloudEventQueryHandler implements QueryHandler { + @Override + public Mono handle(CloudEvent message) { + return Mono.empty(); + } + } @Data private static class SomeDataClass { diff --git a/async/async-commons/async-commons.gradle b/async/async-commons/async-commons.gradle index 567820c6..ce803b55 100644 --- a/async/async-commons/async-commons.gradle +++ b/async/async-commons/async-commons.gradle @@ -8,9 +8,10 @@ dependencies { api project(':domain-events-api') compileOnly 'io.projectreactor:reactor-core' - api 'io.projectreactor.rabbitmq:reactor-rabbitmq:1.5.6' api 'com.fasterxml.jackson.core:jackson-databind' - implementation 'commons-io:commons-io:2.16.1' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + implementation 'commons-io:commons-io:2.18.0' + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' testImplementation 'io.projectreactor:reactor-test' } \ No newline at end of file diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/CommandExecutor.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/CommandExecutor.java index 174ac95f..b0e36641 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/CommandExecutor.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/CommandExecutor.java @@ -1,23 +1,18 @@ package org.reactivecommons.async.commons; - -import org.reactivecommons.api.domain.Command; +import lombok.RequiredArgsConstructor; import org.reactivecommons.async.api.handlers.CommandHandler; import org.reactivecommons.async.commons.communications.Message; import reactor.core.publisher.Mono; import java.util.function.Function; +@RequiredArgsConstructor public class CommandExecutor { private final CommandHandler eventHandler; - private final Function> converter; - - public CommandExecutor(CommandHandler eventHandler, Function> converter) { - this.eventHandler = eventHandler; - this.converter = converter; - } + private final Function converter; - public Mono execute(Message rawMessage){ + public Mono execute(Message rawMessage) { return eventHandler.handle(converter.apply(rawMessage)); } } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDiscardNotifier.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/DLQDiscardNotifier.java similarity index 59% rename from async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDiscardNotifier.java rename to async/async-commons/src/main/java/org/reactivecommons/async/commons/DLQDiscardNotifier.java index 7eefa165..4c5e59fe 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDiscardNotifier.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/DLQDiscardNotifier.java @@ -1,44 +1,49 @@ -package org.reactivecommons.async.rabbit; +package org.reactivecommons.async.commons; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; import lombok.Data; +import lombok.RequiredArgsConstructor; import lombok.extern.java.Log; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.exceptions.MessageConversionException; -import org.reactivecommons.async.commons.DiscardNotifier; import reactor.core.publisher.Mono; -import java.io.IOException; import java.util.logging.Level; import static java.lang.String.format; +import static org.reactivecommons.async.commons.converters.json.JacksonMessageConverter.APPLICATION_CLOUD_EVENT_JSON; @Log -public class RabbitDiscardNotifier implements DiscardNotifier { - +@RequiredArgsConstructor +public class DLQDiscardNotifier implements DiscardNotifier { private final DomainEventBus eventBus; - private final ObjectMapper objectMapper; - - public RabbitDiscardNotifier(DomainEventBus eventBus, ObjectMapper objectMapper) { - this.eventBus = eventBus; - this.objectMapper = objectMapper; - } + private final MessageConverter messageConverter; @Override public Mono notifyDiscard(Message message) { try { return notify(message).onErrorResume(this::onError); - }catch (Exception e){ + } catch (Exception e) { return onError(e); } } - private Mono notify(Message message){ + private Mono notify(Message message) { + if (isCloudEvent(message)) { + CloudEvent cloudEvent = messageConverter.readCloudEvent(message); + String dlqType = cloudEvent.getType() + ".dlq"; + CloudEvent forDlq = CloudEventBuilder.from(cloudEvent) + .withType(dlqType) + .build(); + return Mono.from(eventBus.emit(forDlq)); + } try { - JsonSkeleton node = readSkeleton(message); + JsonSkeleton node = messageConverter.readValue(message, JsonSkeleton.class); return Mono.from(eventBus.emit(createEvent(node))); } catch (MessageConversionException e) { return notifyUnreadableMessage(message, e); @@ -47,9 +52,9 @@ private Mono notify(Message message){ private Mono notifyUnreadableMessage(Message message, MessageConversionException e) { String bodyString; - try{ + try { bodyString = new String(message.getBody()); - }catch (Exception ex){ + } catch (Exception ex) { bodyString = "Opaque binary Message, unable to decode: " + ex.getMessage(); } log.log(Level.SEVERE, format("Unable to interpret discarded message: %s", bodyString), e); @@ -57,32 +62,28 @@ private Mono notifyUnreadableMessage(Message message, MessageConversionExc return Mono.from(eventBus.emit(event)); } - private Mono onError(Throwable e){ + private Mono onError(Throwable e) { log.log(Level.SEVERE, "FATAL!! unable to notify Discard of message!!", e); return Mono.empty(); } - private JsonSkeleton readSkeleton(Message message) { - try { - return objectMapper.readValue(message.getBody(), JsonSkeleton.class); - } catch (IOException e) { - throw new MessageConversionException(e); - } - } - - private DomainEvent createEvent(JsonSkeleton skeleton) { if (skeleton.isCommand()) { - return new DomainEvent<>(skeleton.name+".dlq", skeleton.commandId, skeleton.data); + return new DomainEvent<>(skeleton.name + ".dlq", skeleton.commandId, skeleton.data); } else if (skeleton.isEvent()) { - return new DomainEvent<>(skeleton.name+".dlq", skeleton.eventId, skeleton.data); + return new DomainEvent<>(skeleton.name + ".dlq", skeleton.eventId, skeleton.data); } else if (skeleton.isQuery()) { - return new DomainEvent<>(skeleton.resource+".dlq", skeleton.resource+"query", skeleton.queryData); + return new DomainEvent<>(skeleton.resource + ".dlq", skeleton.resource + "query", skeleton.queryData); } else { throw new MessageConversionException("Fail to math message type"); } } + private boolean isCloudEvent(Message message) { + return message.getProperties().getContentType() != null + && message.getProperties().getContentType().contains(APPLICATION_CLOUD_EVENT_JSON); + } + @Data private static class JsonSkeleton { private String name; @@ -101,7 +102,7 @@ public boolean isCommand() { } public boolean isQuery() { - return !empty(resource) && queryData != null; + return !empty(resource) && queryData != null; } private boolean empty(String str) { diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/DiscardNotifier.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/DiscardNotifier.java index 82e638b6..f698809b 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/DiscardNotifier.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/DiscardNotifier.java @@ -4,7 +4,5 @@ import reactor.core.publisher.Mono; public interface DiscardNotifier { - Mono notifyDiscard(Message message); - } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java index 9a362707..39657bf6 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/EventExecutor.java @@ -1,23 +1,18 @@ package org.reactivecommons.async.commons; - -import org.reactivecommons.api.domain.DomainEvent; +import lombok.RequiredArgsConstructor; import org.reactivecommons.async.api.handlers.EventHandler; import org.reactivecommons.async.commons.communications.Message; import reactor.core.publisher.Mono; import java.util.function.Function; +@RequiredArgsConstructor public class EventExecutor { private final EventHandler eventHandler; - private final Function> converter; - - public EventExecutor(EventHandler eventHandler, Function> converter) { - this.eventHandler = eventHandler; - this.converter = converter; - } + private final Function converter; - public Mono execute(Message rawMessage){ + public Mono execute(Message rawMessage) { return eventHandler.handle(converter.apply(rawMessage)); } } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/FallbackStrategy.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/FallbackStrategy.java index 3648f67e..d30525fa 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/FallbackStrategy.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/FallbackStrategy.java @@ -1,12 +1,12 @@ package org.reactivecommons.async.commons; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor public enum FallbackStrategy { FAST_RETRY("ATTENTION!! Fast retry message to same Queue: %s"), DEFINITIVE_DISCARD("ATTENTION!! DEFINITIVE DISCARD!! of the message: %s"), RETRY_DLQ("ATTENTION!! Sending message to Retry DLQ: %s"); public final String message; - FallbackStrategy(String message){ - this.message = message; - } } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java new file mode 100644 index 00000000..ece82925 --- /dev/null +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/HandlerResolver.java @@ -0,0 +1,99 @@ +package org.reactivecommons.async.commons; + +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; +import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; +import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; +import org.reactivecommons.async.commons.utils.matcher.KeyMatcher; +import org.reactivecommons.async.commons.utils.matcher.Matcher; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +@Log +@RequiredArgsConstructor +public class HandlerResolver { + + private final Map> queryHandlers; + private final Map> eventListeners; + private final Map> eventsToBind; + private final Map> eventNotificationListeners; + private final Map> commandHandlers; + + private final Matcher matcher = new KeyMatcher(); + + public boolean hasNotificationListeners() { + return !eventNotificationListeners.isEmpty(); + } + + public boolean hasCommandHandlers() { + return !commandHandlers.isEmpty(); + } + + public boolean hasQueryHandlers() { + return !queryHandlers.isEmpty(); + } + + @SuppressWarnings("unchecked") + public RegisteredQueryHandler getQueryHandler(String path) { + return (RegisteredQueryHandler) queryHandlers + .computeIfAbsent(path, getMatchHandler(queryHandlers)); + } + + @SuppressWarnings("unchecked") + public RegisteredCommandHandler getCommandHandler(String path) { + return (RegisteredCommandHandler) commandHandlers + .computeIfAbsent(path, getMatchHandler(commandHandlers)); + } + + @SuppressWarnings("unchecked") + public RegisteredEventListener getEventListener(String path) { + if (eventListeners.containsKey(path)) { + return (RegisteredEventListener) eventListeners.get(path); + } + return (RegisteredEventListener) getMatchHandler(eventListeners).apply(path); + } + + public Collection> getNotificationListeners() { + return eventNotificationListeners.values(); + } + + @SuppressWarnings("unchecked") + public RegisteredEventListener getNotificationListener(String path) { + return (RegisteredEventListener) eventNotificationListeners + .computeIfAbsent(path, getMatchHandler(eventNotificationListeners)); + } + + // Returns only the listenEvent not the handleDynamicEvents + public Collection> getEventListeners() { + return eventsToBind.values(); + } + + public List getEventNames() { + return List.copyOf(eventListeners.keySet()); + } + + public List getNotificationNames() { + return List.copyOf(eventNotificationListeners.keySet()); + } + + public void addEventListener(RegisteredEventListener listener) { + eventListeners.put(listener.getPath(), listener); + } + + public void addQueryHandler(RegisteredQueryHandler handler) { + if (handler.getPath().contains("*") || handler.getPath().contains("#")) { + throw new RuntimeException("avoid * or # in dynamic handlers, make sure you have no conflicts with cached" + + " patterns"); + } + queryHandlers.put(handler.getPath(), handler); + } + + private Function getMatchHandler(Map handlers) { + return name -> handlers.get(matcher.match(handlers.keySet(), name)); + } + +} diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/Headers.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/Headers.java index 05bbe553..4058b4ba 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/Headers.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/Headers.java @@ -1,5 +1,10 @@ package org.reactivecommons.async.commons; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) public final class Headers { public static final String REPLY_ID = "x-reply_id"; @@ -8,9 +13,4 @@ public final class Headers { public static final String SERVED_QUERY_ID = "x-serveQuery-id"; public static final String SOURCE_APPLICATION = "sourceApplication"; public static final String REPLY_TIMEOUT_MILLIS = "x-reply-timeout-millis"; - - - private Headers() { - } - } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java index 663a6251..ad60f221 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/communications/Message.java @@ -1,20 +1,37 @@ package org.reactivecommons.async.commons.communications; +import org.reactivecommons.api.domain.RawMessage; + import java.util.Map; /** * Simple Internal Message representation + * * @author Daniel Bustamante Ospina */ -public interface Message { +public interface Message extends RawMessage { byte[] getBody(); + Properties getProperties(); interface Properties { String getContentType(); - String getContentEncoding(); + + default String getContentEncoding() { + return null; + } + long getContentLength(); + Map getHeaders(); + + default String getKey() { + return null; + } + + default String getTopic() { + return null; + } } } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/config/BrokerConfig.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/config/BrokerConfig.java index fa588ad0..9ea2fbe6 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/config/BrokerConfig.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/config/BrokerConfig.java @@ -1,10 +1,15 @@ package org.reactivecommons.async.commons.config; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + import java.time.Duration; import java.util.UUID; +@Getter +@RequiredArgsConstructor public class BrokerConfig { - private final String routingKey = UUID.randomUUID().toString().replaceAll("-", ""); + private final String routingKey = UUID.randomUUID().toString().replace("-", ""); private final boolean persistentQueries; private final boolean persistentCommands; private final boolean persistentEvents; @@ -17,31 +22,4 @@ public BrokerConfig() { this.replyTimeout = Duration.ofSeconds(15); } - public BrokerConfig(boolean persistentQueries, boolean persistentCommands, boolean persistentEvents, Duration replyTimeout) { - this.persistentQueries = persistentQueries; - this.persistentCommands = persistentCommands; - this.persistentEvents = persistentEvents; - this.replyTimeout = replyTimeout; - } - - public boolean isPersistentQueries() { - return persistentQueries; - } - - public boolean isPersistentCommands() { - return persistentCommands; - } - - public boolean isPersistentEvents() { - return persistentEvents; - } - - public Duration getReplyTimeout() { - return replyTimeout; - } - - public String getRoutingKey() { - return routingKey; - } - } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/MessageConverter.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/MessageConverter.java index 2202ab5b..3c13124b 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/MessageConverter.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/MessageConverter.java @@ -14,10 +14,14 @@ public interface MessageConverter { Command readCommand(Message message, Class bodyClass); + CloudEvent readCloudEvent(Message message); + T readValue(Message message, Class valueClass); Command readCommandStructure(Message message); + DomainEvent readDomainEventStructure(Message message); + AsyncQuery readAsyncQueryStructure(Message message); Message toMessage(Object object); diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExt.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java similarity index 60% rename from async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExt.java rename to async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java index 88570b9d..311f5137 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExt.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/CloudEventBuilderExt.java @@ -1,14 +1,16 @@ -package org.reactivecommons.async.rabbit.converters.json; +package org.reactivecommons.async.commons.converters.json; import com.fasterxml.jackson.databind.ObjectMapper; import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.SneakyThrows; -import lombok.experimental.UtilityClass; import java.util.Objects; -@UtilityClass -public class CloudEventBuilderExt { +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public final class CloudEventBuilderExt { private static final ObjectMapper mapper = new ObjectMapper(); @SneakyThrows @@ -16,6 +18,10 @@ public static byte[] asBytes(Object object) { return mapper.writeValueAsBytes(object); } + public static CloudEventData asCloudEventData(Object object) { + return () -> asBytes(object); + } + @SneakyThrows public static T fromCloudEventData(CloudEvent cloudEvent, Class classValue) { return mapper.readValue(Objects.requireNonNull(cloudEvent.getData()).toBytes(), classValue); diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java index 163cde62..f4a2faed 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/DefaultObjectMapperSupplier.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.cloudevents.jackson.JsonFormat; public class DefaultObjectMapperSupplier implements ObjectMapperSupplier { @@ -9,7 +11,9 @@ public class DefaultObjectMapperSupplier implements ObjectMapperSupplier { public ObjectMapper get() { final ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - + objectMapper.findAndRegisterModules(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.registerModule(JsonFormat.getCloudEventJacksonModule()); return objectMapper; } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverter.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/JacksonMessageConverter.java similarity index 77% rename from async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverter.java rename to async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/JacksonMessageConverter.java index 0872dd85..92e8bc72 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverter.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/converters/json/JacksonMessageConverter.java @@ -1,29 +1,27 @@ -package org.reactivecommons.async.rabbit.converters.json; +package org.reactivecommons.async.commons.converters.json; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; import lombok.Data; +import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.Command; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.async.api.AsyncQuery; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.exceptions.MessageConversionException; -import org.reactivecommons.async.rabbit.RabbitMessage; import java.io.IOException; -import java.nio.charset.StandardCharsets; -public class JacksonMessageConverter implements MessageConverter { - private static final String CONTENT_TYPE = "application/json"; +@RequiredArgsConstructor +public abstract class JacksonMessageConverter implements MessageConverter { public static final String FAILED_TO_CONVERT_MESSAGE_CONTENT = "Failed to convert Message content"; + public static final String CONTENT_TYPE = "content-type"; + public static final String APPLICATION_CLOUD_EVENT_JSON = "application/cloudevents+json"; + public static final String APPLICATION_JSON = "application/json"; - private final ObjectMapper objectMapper; - - - public JacksonMessageConverter(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - } + protected final ObjectMapper objectMapper; @Override public AsyncQuery readAsyncQuery(Message message, Class bodyClass) { @@ -58,6 +56,11 @@ public Command readCommand(Message message, Class bodyClass) { } } + @Override + public CloudEvent readCloudEvent(Message message) { + return readValue(message, CloudEvent.class); + } + @Override public T readValue(Message message, Class valueClass) { try { @@ -88,22 +91,6 @@ public AsyncQuery readAsyncQueryStructure(Message message) { return new AsyncQuery<>(asyncQueryJson.getResource(), (T) asyncQueryJson.getQueryData()); } - @Override - public Message toMessage(Object object) { - byte[] bytes; - try { - String jsonString = this.objectMapper.writeValueAsString(object); - bytes = jsonString.getBytes(StandardCharsets.UTF_8); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - RabbitMessage.RabbitMessageProperties props = new RabbitMessage.RabbitMessageProperties(); - props.setContentType(CONTENT_TYPE); - props.setContentEncoding(StandardCharsets.UTF_8.name()); - props.setContentLength(bytes.length); - return new RabbitMessage(bytes, props); - } - @Data private static class AsyncQueryJson { private String resource; diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/CustomReporter.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/CustomReporter.java index 2a94f7aa..ef4bbbea 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/CustomReporter.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/CustomReporter.java @@ -13,23 +13,27 @@ public interface CustomReporter { String QUERY_CLASS = "org.reactivecommons.async.api.AsyncQuery"; default Mono reportError(Throwable ex, Message rawMessage, Object message, boolean redelivered) { - switch (message.getClass().getName()){ - case COMMAND_CLASS: - return reportError(ex, rawMessage, (Command) message, redelivered); - case EVENT_CLASS: - return reportError(ex, rawMessage, (DomainEvent) message, redelivered); - case QUERY_CLASS: - return reportError(ex, rawMessage, (AsyncQuery) message, redelivered); - default: - return Mono.empty(); - } + var name = message.getClass().getName(); + return Mono.just(name) + .filter(COMMAND_CLASS::equals) + .flatMap(n -> reportError(ex, rawMessage, (Command) message, redelivered)) + .switchIfEmpty(Mono.just(name) + .filter(EVENT_CLASS::equals) + .flatMap(n -> reportError(ex, rawMessage, (DomainEvent) message, redelivered)) + .switchIfEmpty(Mono.just(name) + .filter(QUERY_CLASS::equals) + .flatMap(n -> reportError(ex, rawMessage, (AsyncQuery) message, redelivered)) + ) + ); } default void reportMetric(String type, String handlerPath, Long duration, boolean success) { } Mono reportError(Throwable ex, Message rawMessage, Command message, boolean redelivered); + Mono reportError(Throwable ex, Message rawMessage, DomainEvent message, boolean redelivered); + Mono reportError(Throwable ex, Message rawMessage, AsyncQuery message, boolean redelivered); } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/DefaultCustomReporter.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/DefaultCustomReporter.java new file mode 100644 index 00000000..2b4b359f --- /dev/null +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/ext/DefaultCustomReporter.java @@ -0,0 +1,25 @@ +package org.reactivecommons.async.commons.ext; + +import org.reactivecommons.api.domain.Command; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.api.AsyncQuery; +import org.reactivecommons.async.commons.communications.Message; +import reactor.core.publisher.Mono; + +public class DefaultCustomReporter implements CustomReporter { + + @Override + public Mono reportError(Throwable ex, Message rawMessage, Command message, boolean redelivered) { + return Mono.empty(); + } + + @Override + public Mono reportError(Throwable ex, Message rawMessage, DomainEvent message, boolean redelivered) { + return Mono.empty(); + } + + @Override + public Mono reportError(Throwable ex, Message rawMessage, AsyncQuery message, boolean redelivered) { + return Mono.empty(); + } +} diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/ArrayUtils.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/ArrayUtils.java index 009678f4..0a67edec 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/ArrayUtils.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/ArrayUtils.java @@ -1,11 +1,14 @@ package org.reactivecommons.async.commons.utils; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; + import java.util.ArrayList; import java.util.Arrays; -public class ArrayUtils { - private ArrayUtils(){} +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public final class ArrayUtils { public static Object[] prefixArray(E head, E[] tail) { final ArrayList objects = new ArrayList<>(1 + tail.length); diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java index f7511312..26168a9e 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/LoggerSubscriber.java @@ -1,5 +1,6 @@ package org.reactivecommons.async.commons.utils; +import lombok.RequiredArgsConstructor; import lombok.extern.java.Log; import reactor.core.publisher.BaseSubscriber; import reactor.core.publisher.SignalType; @@ -10,18 +11,15 @@ @Log +@RequiredArgsConstructor public class LoggerSubscriber extends BaseSubscriber { private final String flowName; private static final String ON_COMPLETE_MSG = "%s: ##On Complete Hook!!"; - private static final String ON_ERROR_MSG = "%s: ##On Error Hook!!"; + private static final String ON_ERROR_MSG = "%s: ##On Error Hook!! %s"; private static final String ON_CANCEL_MSG = "%s: ##On Cancel Hook!!"; private static final String ON_FINALLY_MSG = "%s: ##On Finally Hook! Signal type: %s"; - public LoggerSubscriber(String flowName) { - this.flowName = flowName; - } - @Override protected void hookOnComplete() { log.warning(format(ON_COMPLETE_MSG)); @@ -29,7 +27,7 @@ protected void hookOnComplete() { @Override protected void hookOnError(Throwable throwable) { - log.log(Level.SEVERE, format(ON_ERROR_MSG), throwable); + log.log(Level.SEVERE, format(ON_ERROR_MSG, throwable.getMessage()), throwable); } @Override diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/NameGenerator.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/NameGenerator.java index 40013ec8..259c3d25 100644 --- a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/NameGenerator.java +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/NameGenerator.java @@ -1,10 +1,14 @@ package org.reactivecommons.async.commons.utils; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; + import java.nio.ByteBuffer; import java.util.Base64; import java.util.UUID; -public class NameGenerator { +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public final class NameGenerator { public static String fromNameWithSuffix(String appName, String suffix) { if (suffix != null && !suffix.isEmpty()) { @@ -27,7 +31,7 @@ private static String generateName(String applicationName, String suffix) { bb.putLong(uuid.getMostSignificantBits()) .putLong(uuid.getLeastSignificantBits()); // Convert to base64 and remove trailing = - String realSuffix = suffix != null && !"".equals(suffix) ? suffix + "." : ""; + String realSuffix = suffix != null && !suffix.isEmpty() ? suffix + "." : ""; return applicationName + "." + realSuffix + encodeToUrlSafeString(bb.array()) .replace("=", ""); } diff --git a/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverBuilder.java b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverBuilder.java new file mode 100644 index 00000000..5054362d --- /dev/null +++ b/async/async-commons/src/main/java/org/reactivecommons/async/commons/utils/resolver/HandlerResolverBuilder.java @@ -0,0 +1,106 @@ +package org.reactivecommons.async.commons.utils.resolver; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.java.Log; +import org.reactivecommons.async.api.DefaultCommandHandler; +import org.reactivecommons.async.api.HandlerRegistry; +import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; +import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; +import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; +import org.reactivecommons.async.commons.HandlerResolver; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Stream; + +@Log +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class HandlerResolverBuilder { + + public static HandlerResolver buildResolver(String domain, Map registries, + final DefaultCommandHandler defaultCommandHandler) { + final ConcurrentMap> queryHandlers = registries + .values() + .stream() + .flatMap(r -> r.getHandlers() + .getOrDefault(domain, List.of()) + .stream()) + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); + + final ConcurrentMap> commandHandlers = registries + .values() + .stream() + .flatMap(r -> r.getCommandHandlers() + .getOrDefault(domain, List.of()) + .stream()) + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); + + final ConcurrentMap> eventNotificationListener = registries + .values() + .stream() + .flatMap(r -> r.getEventNotificationListener() + .getOrDefault(domain, List.of()) + .stream()) + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); + + final ConcurrentMap> eventsToBind = getEventsToBind(domain, + registries); + + final ConcurrentMap> eventHandlers = + getEventHandlersWithDynamics(domain, registries); + + return new HandlerResolver(queryHandlers, eventHandlers, eventsToBind, eventNotificationListener, + commandHandlers) { + @Override + @SuppressWarnings("unchecked") + public RegisteredCommandHandler getCommandHandler(String path) { + final RegisteredCommandHandler handler = super.getCommandHandler(path); + return handler != null ? + handler : new RegisteredCommandHandler<>("", defaultCommandHandler, Object.class); + } + }; + } + + private static ConcurrentMap> getEventHandlersWithDynamics( + String domain, Map registries) { + // event handlers and dynamic handlers + return registries + .values() + .stream() + .flatMap(r -> Stream.concat(r.getDomainEventListeners() + .getOrDefault(domain, List.of()) + .stream(), + getDynamics(domain, r))) + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); + } + + private static Stream> getDynamics(String domain, HandlerRegistry r) { + return r.getDynamicEventHandlers().getOrDefault(domain, List.of()).stream(); + } + + private static ConcurrentMap> getEventsToBind( + String domain, Map registries) { + return registries + .values().stream() + .flatMap(r -> { + if (r.getDomainEventListeners().containsKey(domain)) { + return r.getDomainEventListeners().get(domain).stream(); + } + return Stream.empty(); + }) + .collect(ConcurrentHashMap::new, (map, handler) + -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); + } +} diff --git a/async/async-commons/src/test/java/org/reactivecommons/async/commons/reply/ReactiveReplyRouterTest.java b/async/async-commons/src/test/java/org/reactivecommons/async/commons/reply/ReactiveReplyRouterTest.java index 4878fcb1..c43d8ab7 100644 --- a/async/async-commons/src/test/java/org/reactivecommons/async/commons/reply/ReactiveReplyRouterTest.java +++ b/async/async-commons/src/test/java/org/reactivecommons/async/commons/reply/ReactiveReplyRouterTest.java @@ -14,7 +14,7 @@ class ReactiveReplyRouterTest { private final ReactiveReplyRouter replyRouter = new ReactiveReplyRouter(); @Test - void shouldRouteReply(){ + void shouldRouteReply() { final String uuid = UUID.randomUUID().toString(); final Mono registered = replyRouter.register(uuid); @@ -28,18 +28,18 @@ void shouldRouteReply(){ } @Test - void shouldRouteEmptyResponse(){ + void shouldRouteEmptyResponse() { final String uuid = UUID.randomUUID().toString(); final Mono registered = replyRouter.register(uuid); replyRouter.routeEmpty(uuid); StepVerifier.create(registered) - .verifyComplete(); + .verifyComplete(); } @Test - void shouldDeRegisterProcessor(){ + void shouldDeRegisterProcessor() { final String uuid = UUID.randomUUID().toString(); final Mono registered = replyRouter.register(uuid); @@ -47,7 +47,7 @@ void shouldDeRegisterProcessor(){ replyRouter.routeEmpty(uuid); StepVerifier.create(registered.timeout(Duration.ofSeconds(1))) - .expectTimeout(Duration.ofSeconds(3)).verify(); + .expectTimeout(Duration.ofSeconds(3)).verify(); } } diff --git a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/NameGeneratorTest.java b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/NameGeneratorTest.java index 47b373c5..8a19553d 100644 --- a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/NameGeneratorTest.java +++ b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/NameGeneratorTest.java @@ -2,7 +2,9 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; class NameGeneratorTest { diff --git a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java index d93d069b..d1e7870c 100755 --- a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java +++ b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceManualTest.java @@ -8,7 +8,13 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; class KeyMatcherPerformanceManualTest { @@ -16,7 +22,7 @@ class KeyMatcherPerformanceManualTest { Map candidates = new HashMap<>(); - private KeyMatcher keyMatcher = new KeyMatcher(); + private final KeyMatcher keyMatcher = new KeyMatcher(); private List testList; private List testResultList; @@ -24,7 +30,7 @@ class KeyMatcherPerformanceManualTest { @BeforeEach public void init() { ClassLoader classLoader = getClass().getClassLoader(); - File file = new File(classLoader.getResource("candidateNamesForMatching.txt").getFile()); + File file = new File(Objects.requireNonNull(classLoader.getResource("candidateNamesForMatching.txt")).getFile()); try { Set names = new HashSet<>(Files .readAllLines(Paths.get(file.getAbsolutePath()))); diff --git a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceWildcardTest.java b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceWildcardTest.java index b607233d..eec4cb12 100755 --- a/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceWildcardTest.java +++ b/async/async-commons/src/test/java/org/reactivecommons/async/commons/utils/matcher/KeyMatcherPerformanceWildcardTest.java @@ -13,6 +13,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -21,7 +22,7 @@ class KeyMatcherPerformanceWildcardTest { Map candidates = new HashMap<>(); - private KeyMatcher keyMatcher = new KeyMatcher(); + private final KeyMatcher keyMatcher = new KeyMatcher(); private List testList; private List testResultList; @@ -29,8 +30,12 @@ class KeyMatcherPerformanceWildcardTest { @BeforeEach public void setUp() { ClassLoader classLoader = getClass().getClassLoader(); - File file = new File(classLoader.getResource("wildcard_names_for_matching.txt").getFile()); - File file2 = new File(classLoader.getResource("concrete_names_for_matching.txt").getFile()); + var file = new File(Objects.requireNonNull( + classLoader.getResource("wildcard_names_for_matching.txt")).getFile() + ); + var file2 = new File( + Objects.requireNonNull(classLoader.getResource("concrete_names_for_matching.txt")).getFile() + ); try { Set names = new HashSet<>(Files .readAllLines(Paths.get(file.getAbsolutePath()))); diff --git a/async/async-kafka/async-kafka.gradle b/async/async-kafka/async-kafka.gradle new file mode 100644 index 00000000..6f576f27 --- /dev/null +++ b/async/async-kafka/async-kafka.gradle @@ -0,0 +1,13 @@ +ext { + artifactId = 'async-kafka' + artifactDescription = 'Async Kafka' +} + +dependencies { + api project(':async-commons-api') + api project(':domain-events-api') + api project(':async-commons') + api 'io.projectreactor.kafka:reactor-kafka:1.3.23' + + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' +} \ No newline at end of file diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGateway.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGateway.java new file mode 100644 index 00000000..989f30e7 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGateway.java @@ -0,0 +1,78 @@ +package org.reactivecommons.async.kafka; + +import io.cloudevents.CloudEvent; +import org.reactivecommons.api.domain.Command; +import org.reactivecommons.async.api.AsyncQuery; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.api.From; +import reactor.core.publisher.Mono; + +public class KafkaDirectAsyncGateway implements DirectAsyncGateway { + + public static final String NOT_IMPLEMENTED_YET = "Not implemented yet"; + + @Override + public Mono sendCommand(Command command, String targetName) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(Command command, String targetName, long delayMillis) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(Command command, String targetName, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(Command command, String targetName, long delayMillis, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, long delayMillis) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, long delayMillis, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono requestReply(AsyncQuery query, String targetName, Class type) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono requestReply(AsyncQuery query, String targetName, Class type, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type, String domain) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Mono reply(T response, From from) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDomainEventBus.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDomainEventBus.java new file mode 100644 index 00000000..54de85e0 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaDomainEventBus.java @@ -0,0 +1,45 @@ +package org.reactivecommons.async.kafka; + +import io.cloudevents.CloudEvent; +import lombok.RequiredArgsConstructor; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.api.domain.RawMessage; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivestreams.Publisher; + +@RequiredArgsConstructor +public class KafkaDomainEventBus implements DomainEventBus { + public static final String NOT_IMPLEMENTED_YET = "Not implemented yet"; + private final ReactiveMessageSender sender; + + @Override + public Publisher emit(DomainEvent event) { + return sender.send(event); + } + + @Override + public Publisher emit(String domain, DomainEvent event) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Publisher emit(CloudEvent event) { + return sender.send(event); + } + + @Override + public Publisher emit(String domain, CloudEvent event) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Publisher emit(RawMessage event) { + return sender.send(event); + } + + @Override + public Publisher emit(String domain, RawMessage event) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java new file mode 100644 index 00000000..a0bf4eeb --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/KafkaMessage.java @@ -0,0 +1,57 @@ +package org.reactivecommons.async.kafka; + +import lombok.Data; +import org.apache.kafka.common.header.Headers; +import org.reactivecommons.async.commons.communications.Message; +import reactor.kafka.receiver.ReceiverRecord; + +import java.util.HashMap; +import java.util.Map; + +import static org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter.CONTENT_TYPE; + + +@Data +public class KafkaMessage implements Message { + private final byte[] body; + private final Properties properties; + private final String type; + + @Data + public static class KafkaMessageProperties implements Properties { + private long contentLength; + private String key; + private String topic; + private Map headers = new HashMap<>(); + + @Override + public String getContentType() { + return (String) headers.get(CONTENT_TYPE); + } + } + + public static KafkaMessage fromDelivery(ReceiverRecord receiverRecord) { + return fromDelivery(receiverRecord, null); + } + + public static KafkaMessage fromDelivery(ReceiverRecord receiverRecord, String type) { + return new KafkaMessage(receiverRecord.value(), createMessageProps(receiverRecord), type); + } + + private static Properties createMessageProps(ReceiverRecord receiverRecord) { + Map headers = parseHeaders(receiverRecord.headers()); + + final KafkaMessageProperties properties = new KafkaMessageProperties(); + properties.setHeaders(headers); + properties.setKey(receiverRecord.key()); + properties.setTopic(receiverRecord.topic()); + properties.setContentLength(receiverRecord.value().length); + return properties; + } + + private static Map parseHeaders(Headers headers) { + Map parsedHeaders = new HashMap<>(); + headers.forEach(header -> parsedHeaders.put(header.key(), new String(header.value()))); + return parsedHeaders; + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java new file mode 100644 index 00000000..e46f4b85 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageListener.java @@ -0,0 +1,33 @@ +package org.reactivecommons.async.kafka.communications; + +import lombok.RequiredArgsConstructor; +import reactor.core.publisher.Flux; +import reactor.kafka.receiver.KafkaReceiver; +import reactor.kafka.receiver.ReceiverOptions; +import reactor.kafka.receiver.ReceiverRecord; + +import java.util.List; + +import static org.apache.kafka.clients.consumer.ConsumerConfig.DEFAULT_MAX_POLL_RECORDS; +import static org.apache.kafka.clients.consumer.ConsumerConfig.GROUP_ID_CONFIG; +import static org.apache.kafka.clients.consumer.ConsumerConfig.MAX_POLL_RECORDS_CONFIG; + + +@RequiredArgsConstructor +public class ReactiveMessageListener { + private final ReceiverOptions receiverOptions; + + public Flux> listen(String groupId, List topics) { // Notification events + ReceiverOptions options = receiverOptions.consumerProperty(GROUP_ID_CONFIG, groupId); + return KafkaReceiver.create(options.subscription(topics)) + .receive(); + } + + public int getMaxConcurrency() { + Object property = receiverOptions.consumerProperty(MAX_POLL_RECORDS_CONFIG); + if (property instanceof Integer) { + return (int) property; + } + return DEFAULT_MAX_POLL_RECORDS; + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageSender.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageSender.java new file mode 100644 index 00000000..99d97608 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/ReactiveMessageSender.java @@ -0,0 +1,97 @@ +package org.reactivecommons.async.kafka.communications; + +import lombok.SneakyThrows; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.header.Header; +import org.apache.kafka.common.header.internals.RecordHeader; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.kafka.KafkaMessage; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoSink; +import reactor.kafka.sender.KafkaSender; +import reactor.kafka.sender.SenderRecord; +import reactor.kafka.sender.SenderResult; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +public class ReactiveMessageSender { + private final ConcurrentHashMap> confirmations = new ConcurrentHashMap<>(); + private final CopyOnWriteArrayList>> fluxSinks = + new CopyOnWriteArrayList<>(); + private final AtomicLong counter = new AtomicLong(); + + private final ExecutorService executorServiceConfirm = Executors.newFixedThreadPool(13, r -> + new Thread(r, "KMessageSender1-" + counter.getAndIncrement()) + ); + private final ExecutorService executorServiceEmit = Executors.newFixedThreadPool(13, r -> + new Thread(r, "KMessageSender2-" + counter.getAndIncrement()) + ); + + private static final int SENDER_COUNT = 4; + + private final MessageConverter messageConverter; + private final TopologyCreator topologyCreator; + + public ReactiveMessageSender(KafkaSender sender, MessageConverter messageConverter, + TopologyCreator topologyCreator) { + this.messageConverter = messageConverter; + this.topologyCreator = topologyCreator; + for (int i = 0; i < SENDER_COUNT; ++i) { + Flux> source = Flux.create(fluxSinks::add); + sender.send(source) + .doOnNext(this::confirm) + .subscribe(); + } + } + + public Mono send(V message) { + return Mono.create(sink -> { + SenderRecord senderRecord = createRecord(message); + confirmations.put(senderRecord.key(), sink); + executorServiceEmit.submit(() -> fluxSinks.get((int) (System.currentTimeMillis() % SENDER_COUNT)) + .next(senderRecord)); + }); + } + + private void confirm(SenderResult result) { + executorServiceConfirm.submit(() -> { + MonoSink sink = confirmations.remove(result.correlationMetadata()); + if (sink != null) { + if (result.exception() != null) { + sink.error(result.exception()); + } else { + sink.success(); + } + } + }); + } + + private SenderRecord createRecord(V object) { + KafkaMessage message = (KafkaMessage) messageConverter.toMessage(object); + ProducerRecord producerRecord = createProducerRecord(message); + return SenderRecord.create(producerRecord, message.getProperties().getKey()); // TODO: Review for Request-Reply + } + + @SneakyThrows + private ProducerRecord createProducerRecord(KafkaMessage message) { + topologyCreator.checkTopic(message.getProperties().getTopic()); + + List
headers = message.getProperties().getHeaders().entrySet().stream() + .map(entry -> new RecordHeader(entry.getKey(), entry.getValue() + .toString().getBytes(StandardCharsets.UTF_8))) + .collect(Collectors.toList()); + + return new ProducerRecord<>(message.getProperties().getTopic(), null, + message.getProperties().getKey(), message.getBody(), headers); + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/exceptions/TopicNotFoundException.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/exceptions/TopicNotFoundException.java new file mode 100644 index 00000000..3f8496e8 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/exceptions/TopicNotFoundException.java @@ -0,0 +1,7 @@ +package org.reactivecommons.async.kafka.communications.exceptions; + +public class TopicNotFoundException extends RuntimeException { + public TopicNotFoundException(String message) { + super(message); + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizations.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizations.java new file mode 100644 index 00000000..242b7ab9 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizations.java @@ -0,0 +1,26 @@ +package org.reactivecommons.async.kafka.communications.topology; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class KafkaCustomizations { + private Map topics = new HashMap<>(); + + public static KafkaCustomizations withTopic(String topic, TopicCustomization customization) { + KafkaCustomizations customizations = new KafkaCustomizations(); + customizations.getTopics().put(topic, customization); + return customizations; + } + + public KafkaCustomizations addTopic(String topic, TopicCustomization customization) { + this.getTopics().put(topic, customization); + return this; + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopicCustomization.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopicCustomization.java new file mode 100644 index 00000000..64821e9f --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopicCustomization.java @@ -0,0 +1,19 @@ +package org.reactivecommons.async.kafka.communications.topology; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class TopicCustomization { + private String topic; + private int partitions; + private short replicationFactor; + private Map config; +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java new file mode 100644 index 00000000..7b5e4a8c --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreator.java @@ -0,0 +1,80 @@ +package org.reactivecommons.async.kafka.communications.topology; + +import lombok.SneakyThrows; +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.admin.ListTopicsOptions; +import org.apache.kafka.clients.admin.ListTopicsResult; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.common.errors.TopicExistsException; +import org.reactivecommons.async.kafka.communications.exceptions.TopicNotFoundException; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class TopologyCreator { + public static final int TIMEOUT_MS = 60_000; + private final AdminClient adminClient; + private final KafkaCustomizations customizations; + private final Map existingTopics; + private final boolean checkTopics; + + public TopologyCreator(AdminClient adminClient, KafkaCustomizations customizations, boolean checkTopics) { + this.adminClient = adminClient; + this.customizations = customizations; + this.checkTopics = checkTopics; + this.existingTopics = getTopics(); + } + + @SneakyThrows + public Map getTopics() { + if (!checkTopics) { + return Map.of(); + } + ListTopicsResult topics = adminClient.listTopics(new ListTopicsOptions().timeoutMs(TIMEOUT_MS)); + return topics.names().get().stream().collect(Collectors.toConcurrentMap(name -> name, name -> true)); + } + + public Mono createTopics(List topics) { + return Flux.fromIterable(topics) + .map(topic -> { + if (customizations.getTopics().containsKey(topic)) { + return customizations.getTopics().get(topic); + } + return TopicCustomization.builder() + .partitions(-1) + .replicationFactor((short) -1) + .topic(topic).build(); + }) + .map(this::toNewTopic) + .flatMap(this::createTopic) + .doOnNext(topic -> existingTopics.put(topic.name(), true)) + .then(); + } + + protected Mono createTopic(NewTopic topic) { + return Mono.fromFuture(adminClient.createTopics(List.of(topic)) + .all() + .toCompletionStage() + .toCompletableFuture()) + .thenReturn(topic) + .onErrorResume(TopicExistsException.class, e -> Mono.just(topic)); + } + + protected NewTopic toNewTopic(TopicCustomization customization) { + NewTopic topic = new NewTopic(customization.getTopic(), customization.getPartitions(), customization.getReplicationFactor()); + if (customization.getConfig() != null) { + return topic.configs(customization.getConfig()); + } + return topic; + } + + public void checkTopic(String topicName) { + if (checkTopics && !existingTopics.containsKey(topicName)) { + throw new TopicNotFoundException("Topic not found: " + topicName + ". Please create it before send a message."); + // TODO: should refresh topics?? getTopics(); + } + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverter.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverter.java new file mode 100644 index 00000000..035829e5 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverter.java @@ -0,0 +1,64 @@ +package org.reactivecommons.async.kafka.converters.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.commons.exceptions.MessageConversionException; +import org.reactivecommons.async.kafka.KafkaMessage; +import org.reactivecommons.async.kafka.KafkaMessage.KafkaMessageProperties; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class KafkaJacksonMessageConverter extends JacksonMessageConverter { + + public KafkaJacksonMessageConverter(ObjectMapper objectMapper) { + super(objectMapper); + } + + @Override + public Message toMessage(Object object) { + if (object instanceof KafkaMessage) { + return (KafkaMessage) object; + } + byte[] bytes; + try { + String jsonString = this.objectMapper.writeValueAsString(object); + bytes = jsonString.getBytes(StandardCharsets.UTF_8); + } catch (IOException e) { + throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); + } + KafkaMessageProperties props = buildProperties(object); + return new KafkaMessage(bytes, props, null); + } + + private KafkaMessageProperties buildProperties(Object message) { + KafkaMessageProperties props = new KafkaMessageProperties(); + Map headers = new HashMap<>(); + props.setHeaders(headers); + + if (message instanceof CloudEvent) { + CloudEvent cloudEvent = (CloudEvent) message; + props.setKey(cloudEvent.getId()); + props.setTopic(cloudEvent.getType()); + + headers.put(CONTENT_TYPE, APPLICATION_CLOUD_EVENT_JSON); + return props; + } + + if (message instanceof DomainEvent) { + DomainEvent domainEvent = (DomainEvent) message; + props.setKey(domainEvent.getEventId()); + props.setTopic(domainEvent.getName()); + + headers.put(CONTENT_TYPE, APPLICATION_JSON); + return props; + } + // TODO: Add Command and AsyncQuery support + throw new IllegalArgumentException("Message type not supported: " + message.getClass().getName()); + } +} diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListener.java new file mode 100644 index 00000000..50a19375 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListener.java @@ -0,0 +1,75 @@ +package org.reactivecommons.async.kafka.listeners; + +import lombok.extern.java.Log; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; +import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.EventExecutor; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import reactor.core.publisher.Mono; +import reactor.kafka.receiver.ReceiverRecord; + +import java.util.function.Function; + +@Log +public class ApplicationEventListener extends GenericMessageListener { + + private final MessageConverter messageConverter; + private final HandlerResolver resolver; + + + public ApplicationEventListener(ReactiveMessageListener receiver, + HandlerResolver resolver, + MessageConverter messageConverter, + boolean withDLQRetry, + boolean createTopology, + long maxRetries, + int retryDelay, + DiscardNotifier discardNotifier, + CustomReporter errorReporter, + String appName) { + super(receiver, withDLQRetry, createTopology, maxRetries, retryDelay, discardNotifier, + "event", errorReporter, appName + "-events", resolver.getEventNames()); + this.resolver = resolver; + this.messageConverter = messageConverter; + } + + @Override + protected Function> rawMessageHandler(String executorPath) { + final RegisteredEventListener handler = resolver.getEventListener(executorPath); + + Function converter = resolveConverter(handler); + final EventExecutor executor = new EventExecutor<>(handler.getHandler(), converter); + + return msj -> executor + .execute(msj) + .cast(Object.class); + } + + protected String getExecutorPath(ReceiverRecord msj) { + return msj.topic(); + } + + @Override + protected Object parseMessageForReporter(Message msj) { + return messageConverter.readDomainEventStructure(msj); + } + + private Function resolveConverter(RegisteredEventListener registeredEventListener) { + if (registeredEventListener.getHandler() instanceof DomainEventHandler) { + final Class eventClass = registeredEventListener.getInputClass(); + return msj -> messageConverter.readDomainEvent(msj, eventClass); + } + if (registeredEventListener.getHandler() instanceof CloudEventHandler) { + return messageConverter::readCloudEvent; + } + throw new RuntimeException("Unknown handler type"); + } +} + + diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java new file mode 100644 index 00000000..2b0170e8 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/ApplicationNotificationsListener.java @@ -0,0 +1,77 @@ +package org.reactivecommons.async.kafka.listeners; + +import lombok.extern.java.Log; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; +import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.EventExecutor; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import reactor.core.publisher.Mono; +import reactor.kafka.receiver.ReceiverRecord; + +import java.util.UUID; +import java.util.function.Function; + +@Log +public class ApplicationNotificationsListener extends GenericMessageListener { + + private final MessageConverter messageConverter; + private final HandlerResolver resolver; + + + public ApplicationNotificationsListener(ReactiveMessageListener receiver, + HandlerResolver resolver, + MessageConverter messageConverter, + boolean withDLQRetry, + boolean createTopology, + long maxRetries, + int retryDelay, + DiscardNotifier discardNotifier, + CustomReporter errorReporter, + String appName) { + super(receiver, withDLQRetry, createTopology, maxRetries, retryDelay, discardNotifier, + "event", errorReporter, appName + "-notification-" + UUID.randomUUID(), + resolver.getNotificationNames()); + this.resolver = resolver; + this.messageConverter = messageConverter; + } + + @Override + protected Function> rawMessageHandler(String executorPath) { + final RegisteredEventListener handler = resolver.getNotificationListener(executorPath); + + Function converter = resolveConverter(handler); + final EventExecutor executor = new EventExecutor<>(handler.getHandler(), converter); + + return msj -> executor + .execute(msj) + .cast(Object.class); + } + + protected String getExecutorPath(ReceiverRecord msj) { + return msj.topic(); + } + + @Override + protected Object parseMessageForReporter(Message msj) { + return messageConverter.readDomainEventStructure(msj); + } + + private Function resolveConverter(RegisteredEventListener registeredEventListener) { + if (registeredEventListener.getHandler() instanceof DomainEventHandler) { + final Class eventClass = registeredEventListener.getInputClass(); + return msj -> messageConverter.readDomainEvent(msj, eventClass); + } + if (registeredEventListener.getHandler() instanceof CloudEventHandler) { + return messageConverter::readCloudEvent; + } + throw new RuntimeException("Unknown handler type"); + } +} + + diff --git a/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java new file mode 100644 index 00000000..7c14e631 --- /dev/null +++ b/async/async-kafka/src/main/java/org/reactivecommons/async/kafka/listeners/GenericMessageListener.java @@ -0,0 +1,235 @@ +package org.reactivecommons.async.kafka.listeners; + +import lombok.extern.java.Log; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.FallbackStrategy; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.utils.LoggerSubscriber; +import org.reactivecommons.async.kafka.KafkaMessage; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.core.scheduler.Schedulers; +import reactor.kafka.receiver.ReceiverRecord; +import reactor.util.retry.Retry; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.logging.Level; + +import static java.lang.String.format; +import static java.util.function.Function.identity; + +@Log +public abstract class GenericMessageListener { + private final ConcurrentHashMap>> handlers = new ConcurrentHashMap<>(); + private final ReactiveMessageListener messageListener; + private final Scheduler scheduler = Schedulers.newParallel(getClass().getSimpleName(), 12); + private final Scheduler errorReporterScheduler = Schedulers.newBoundedElastic(4, 256, + "errorReporterScheduler"); + + private final List topics; + private final String groupId; + private final boolean useDLQ; + private final boolean createTopology; + private final long maxRetries; + private final Duration retryDelay; + private final DiscardNotifier discardNotifier; + private final String objectType; + private final CustomReporter customReporter; + private volatile Flux> messageFlux; + + protected GenericMessageListener(ReactiveMessageListener listener, boolean useDLQ, boolean createTopology, + long maxRetries, long retryDelay, DiscardNotifier discardNotifier, + String objectType, CustomReporter customReporter, String groupId, + List topics) { + this.groupId = groupId; + this.topics = topics; + this.messageListener = listener; + this.createTopology = createTopology; + this.maxRetries = maxRetries; + this.retryDelay = Duration.ofMillis(retryDelay); + this.useDLQ = useDLQ; + this.discardNotifier = discardNotifier; + this.objectType = objectType; + this.customReporter = customReporter; + } + + private boolean hasRetries() { + return maxRetries != -1; + } + + protected Mono setUpBindings(TopologyCreator creator) { + return creator.createTopics(topics); + } + + public void startListener(TopologyCreator creator) { + log.log(Level.INFO, "Using max concurrency {0}, in receiver for topics: {1}", + new Object[]{messageListener.getMaxConcurrency(), topics}); + + if (useDLQ) { + log.log(Level.INFO, "ATTENTION! Using DLQ Strategy for retries with {0} + 1 Max Retries configured!", + new Object[]{maxRetries}); + } else { + log.log(Level.INFO, "ATTENTION! Using infinite fast retries as Retry Strategy"); + } + + if (createTopology) { + this.messageFlux = setUpBindings(creator) + .thenMany(this.messageListener.listen(groupId, topics) + .doOnError(err -> log.log(Level.SEVERE, "Error listening queue", err)) + .transform(this::consumeFaultTolerant)); + } else { + this.messageFlux = this.messageListener.listen(groupId, topics) + .doOnError(err -> log.log(Level.SEVERE, "Error listening queue", err)) + .transform(this::consumeFaultTolerant); + } + + onTerminate(); + } + + private void onTerminate() { + messageFlux.doOnTerminate(this::onTerminate) + .subscribe(new LoggerSubscriber<>(getClass().getName())); + } + + private Flux> consumeFaultTolerant(Flux> messageFlux) { + return messageFlux.flatMap(msj -> { + final Instant init = Instant.now(); + return handle(msj, init) + .doOnSuccess(r -> r.receiverOffset().acknowledge()) + .onErrorResume(err -> requeueOrAck(msj, err, init)); + }, messageListener.getMaxConcurrency()); + } + + protected Mono> handle(ReceiverRecord msj, Instant initTime) { + try { + final String executorPath = getExecutorPath(msj); + final Function> handler = getExecutor(executorPath); + final Message message = KafkaMessage.fromDelivery(msj, executorPath); + + Mono flow = Mono.defer(() -> handler.apply(message)) + .transform(enrichPostProcess(message)); + if (hasRetries()) { + flow = flow.retryWhen(Retry.fixedDelay(maxRetries, retryDelay)) + .onErrorMap(err -> { + if (err.getMessage() != null && err.getMessage().contains("Retries exhausted")) { + log.warning(err.getMessage()); + return err.getCause(); + } + return err; + }); + } + return flow.doOnSuccess(o -> logExecution(executorPath, initTime, true)) + .subscribeOn(scheduler) + .thenReturn(msj); + } catch (Exception e) { + log.log(Level.SEVERE, format("ATTENTION !! Outer error protection reached for %s, in Async Consumer!! " + + "Severe Warning! ", msj.key())); + return Mono.error(e); + } + } + + private Mono> requeueOrAck(ReceiverRecord msj, Throwable err, + Instant init) { + final Message message = KafkaMessage.fromDelivery(msj); + reportErrorMetric(msj, init); + sendErrorToCustomReporter(err, message, hasRetries()); + if (hasRetries()) { // Discard + logError(err, msj, FallbackStrategy.DEFINITIVE_DISCARD); + if (useDLQ) { + return discardNotifier + .notifyDiscard(message) + .doOnSuccess(_a -> msj.receiverOffset().acknowledge()) + .thenReturn(msj); + } + return Mono.just(msj); + } else { // infinity fast retries + logError(err, msj, FallbackStrategy.FAST_RETRY); + return Mono.just(msj).delayElement(retryDelay); + } + } + + private void logExecution(String executorPath, Instant initTime, boolean success) { + try { + final Instant afterExecutionTime = Instant.now(); + final long timeElapsed = Duration.between(initTime, afterExecutionTime).toMillis(); + doLogExecution(executorPath, timeElapsed); + customReporter.reportMetric(objectType, executorPath, timeElapsed, success); + } catch (Exception e) { + log.log(Level.WARNING, "Unable to send execution metrics!", e); + } + + } + + private void reportErrorMetric(ReceiverRecord msj, Instant initTime) { + String executorPath; + try { + executorPath = getExecutorPath(msj); + } catch (Exception e) { + executorPath = "unknown"; + } + logExecution(executorPath, initTime, false); + } + + private void doLogExecution(String executorPath, long timeElapsed) { + log.log(Level.FINE, String.format("%s with path %s handled, took %d ms", + objectType, executorPath, timeElapsed)); + } + + + protected void logError(Throwable err, ReceiverRecord msj, FallbackStrategy strategy) { + String messageID = msj.key(); + try { + log.log(Level.SEVERE, + format("Error encounter while processing message %s: %s", messageID, err.toString()), err + ); + log.warning(format("Message %s Headers: %s", messageID, msj.headers().toString())); + log.warning(format("Message %s Body: %s", messageID, new String(msj.value()))); + } catch (Exception e) { + log.log(Level.SEVERE, "Error Login message Content!!", e); + } finally { + log.warning(format(strategy.message, messageID)); + } + } + + private Function> getExecutor(String path) { + final Function> handler = handlers.get(path); + return handler != null ? handler : computeRawMessageHandler(path); + } + + private Function> computeRawMessageHandler(String commandId) { + return handlers.computeIfAbsent(commandId, s -> + rawMessageHandler(commandId) + ); + } + + protected abstract Function> rawMessageHandler(String executorPath); + + protected abstract String getExecutorPath(ReceiverRecord msj); + + protected Function, Mono> enrichPostProcess(Message msg) { + return identity(); + } + + private void sendErrorToCustomReporter(final Throwable err, final Message message, final boolean redelivered) { + try { + customReporter.reportError(err, message, parseMessageForReporter(message), redelivered) + .subscribeOn(errorReporterScheduler) + .doOnError(t -> log.log(Level.WARNING, "Error sending error to external reporter", t)) + .subscribe(); + } catch (Throwable t) { + log.log(Level.WARNING, "Error in scheduler when sending error to external reporter", t); + } + } + + protected abstract Object parseMessageForReporter(Message msj); +} + + diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGatewayTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGatewayTest.java new file mode 100644 index 00000000..9bdcc3ef --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDirectAsyncGatewayTest.java @@ -0,0 +1,70 @@ +package org.reactivecommons.async.kafka; + +import io.cloudevents.CloudEvent; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.Command; +import org.reactivecommons.async.api.AsyncQuery; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.api.From; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +@ExtendWith(MockitoExtension.class) +class KafkaDirectAsyncGatewayTest { + private final DirectAsyncGateway directAsyncGateway = new KafkaDirectAsyncGateway(); + private final String targetName = "targetName"; + private final String domain = "domain"; + private final long delay = 1000L; + @Mock + private CloudEvent cloudEvent; + @Mock + private Command command; + @Mock + private AsyncQuery query; + @Mock + private From from; + + @Test + void allMethodsAreNotImplemented() { + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.sendCommand(cloudEvent, targetName) + ); + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.sendCommand(cloudEvent, targetName, domain) + ); + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.sendCommand(cloudEvent, targetName, delay) + ); + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.sendCommand(cloudEvent, targetName, delay, domain) + ); + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.sendCommand(command, targetName) + ); + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.sendCommand(command, targetName, domain) + ); + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.sendCommand(command, targetName, delay) + ); + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.sendCommand(command, targetName, delay, domain) + ); + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.requestReply(cloudEvent, targetName, CloudEvent.class) + ); + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.requestReply(cloudEvent, targetName, CloudEvent.class, domain) + ); + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.requestReply(query, targetName, CloudEvent.class) + ); + assertThrows(UnsupportedOperationException.class, + () -> directAsyncGateway.requestReply(query, targetName, CloudEvent.class, domain) + ); + assertThrows(UnsupportedOperationException.class, () -> directAsyncGateway.reply(targetName, from)); + } +} diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDomainEventBusTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDomainEventBusTest.java new file mode 100644 index 00000000..f33f582a --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaDomainEventBusTest.java @@ -0,0 +1,71 @@ +package org.reactivecommons.async.kafka; + +import io.cloudevents.CloudEvent; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.RawMessage; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class KafkaDomainEventBusTest { + @Mock + private DomainEvent domainEvent; + @Mock + private CloudEvent cloudEvent; + @Mock + private RawMessage rawMessage; + @Mock + private ReactiveMessageSender sender; + @InjectMocks + private KafkaDomainEventBus kafkaDomainEventBus; + private final String domain = "domain"; + + @Test + void shouldEmitDomainEvent() { + // Arrange + when(sender.send(domainEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(kafkaDomainEventBus.emit(domainEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void shouldEmitCloudEvent() { + // Arrange + when(sender.send(cloudEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(kafkaDomainEventBus.emit(cloudEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void shouldEmitRawMessage() { + // Arrange + when(sender.send(rawMessage)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(kafkaDomainEventBus.emit(rawMessage)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void operationsShouldNotBeAbleForDomains() { + assertThrows(UnsupportedOperationException.class, () -> kafkaDomainEventBus.emit(domain, domainEvent)); + assertThrows(UnsupportedOperationException.class, () -> kafkaDomainEventBus.emit(domain, cloudEvent)); + assertThrows(UnsupportedOperationException.class, () -> kafkaDomainEventBus.emit(domain, rawMessage)); + } +} diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaMessageTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaMessageTest.java new file mode 100644 index 00000000..3433ebb4 --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/KafkaMessageTest.java @@ -0,0 +1,57 @@ +package org.reactivecommons.async.kafka; + +import org.apache.kafka.common.header.internals.RecordHeaders; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.communications.Message; +import reactor.kafka.receiver.ReceiverRecord; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class KafkaMessageTest { + + @Mock + private ReceiverRecord receiverRecord; + + @Test + void shouldParse() { + // Arrange + RecordHeaders headers = new RecordHeaders(); + headers.add("content-type", "application/json".getBytes()); + when(receiverRecord.value()).thenReturn("value".getBytes()); + when(receiverRecord.key()).thenReturn("key"); + when(receiverRecord.topic()).thenReturn("topic"); + when(receiverRecord.headers()).thenReturn(headers); + // Act + Message message = KafkaMessage.fromDelivery(receiverRecord); + // Assert + assertEquals("key", message.getProperties().getKey()); + assertEquals("topic", message.getProperties().getTopic()); + assertEquals("application/json", message.getProperties().getContentType()); + assertEquals(5, message.getProperties().getContentLength()); + assertEquals("value", new String(message.getBody())); + } + + @Test + void shouldParseWhenNoContentType() { + // Arrange + RecordHeaders headers = new RecordHeaders(); + when(receiverRecord.value()).thenReturn("value".getBytes()); + when(receiverRecord.key()).thenReturn("key"); + when(receiverRecord.topic()).thenReturn("topic"); + when(receiverRecord.headers()).thenReturn(headers); + // Act + Message message = KafkaMessage.fromDelivery(receiverRecord); + // Assert + assertEquals("key", message.getProperties().getKey()); + assertEquals("topic", message.getProperties().getTopic()); + assertNull(message.getProperties().getContentType()); + assertEquals(5, message.getProperties().getContentLength()); + assertEquals("value", new String(message.getBody())); + } +} diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java new file mode 100644 index 00000000..83b3f30b --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/KafkaCustomizationsTest.java @@ -0,0 +1,52 @@ +package org.reactivecommons.async.kafka.communications.topology; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class KafkaCustomizationsTest { + + private KafkaCustomizations customizations; + + @BeforeEach + void setUp() { + customizations = new KafkaCustomizations(); + } + + @Test + void testWithTopic() { + String topic = "testTopic"; + Map config = new HashMap<>(); + config.put("cleanup.policy", "compact"); + TopicCustomization customization = new TopicCustomization(topic, 3, (short) 1, config); + KafkaCustomizations result = KafkaCustomizations.withTopic(topic, customization); + + assertNotNull(result); + assertEquals(1, result.getTopics().size()); + assertEquals(customization, result.getTopics().get(topic)); + } + + @Test + void testAddTopic() { + String topic1 = "testTopic1"; + Map config1 = new HashMap<>(); + config1.put("cleanup.policy", "compact"); + TopicCustomization customization1 = new TopicCustomization(topic1, 3, (short) 1, config1); + customizations.addTopic(topic1, customization1); + + String topic2 = "testTopic2"; + Map config2 = new HashMap<>(); + config2.put("retention.ms", "60000"); + TopicCustomization customization2 = new TopicCustomization(topic2, 5, (short) 2, config2); + customizations.addTopic(topic2, customization2); + + assertEquals(2, customizations.getTopics().size()); + assertEquals(customization1, customizations.getTopics().get(topic1)); + assertEquals(customization2, customizations.getTopics().get(topic2)); + } +} \ No newline at end of file diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreatorTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreatorTest.java new file mode 100644 index 00000000..63da00f3 --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/communications/topology/TopologyCreatorTest.java @@ -0,0 +1,96 @@ +package org.reactivecommons.async.kafka.communications.topology; + +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.admin.CreateTopicsResult; +import org.apache.kafka.clients.admin.ListTopicsOptions; +import org.apache.kafka.clients.admin.ListTopicsResult; +import org.apache.kafka.common.internals.KafkaFutureImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.kafka.communications.exceptions.TopicNotFoundException; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class TopologyCreatorTest { + + private TopologyCreator creator; + @Mock + private AdminClient adminClient; + @Mock + private ListTopicsResult listTopicsResult; + @Mock + private CreateTopicsResult createTopicsResult; + private KafkaCustomizations customizations; + + @BeforeEach + void setUp() { + Map config = new HashMap<>(); + config.put("cleanup.policy", "compact"); + TopicCustomization customization = new TopicCustomization("topic1", 3, (short) 1, config); + customizations = KafkaCustomizations.withTopic("topic1", customization); + } + + @Test + void shouldCreateTopics() { + // Arrange + KafkaFutureImpl> names = new KafkaFutureImpl<>(); + names.complete(Set.of("topic1", "topic2")); + doReturn(names).when(listTopicsResult).names(); + when(adminClient.listTopics(any(ListTopicsOptions.class))).thenReturn(listTopicsResult); + + KafkaFutureImpl create = new KafkaFutureImpl<>(); + create.complete(null); + doReturn(create).when(createTopicsResult).all(); + when(adminClient.createTopics(any())).thenReturn(createTopicsResult); + creator = new TopologyCreator(adminClient, customizations, true); + // Act + Mono flow = creator.createTopics(List.of("topic1", "topic2")); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void shouldCheckTopics() { + // Arrange + KafkaFutureImpl> names = new KafkaFutureImpl<>(); + names.complete(Set.of("topic1", "topic2")); + doReturn(names).when(listTopicsResult).names(); + when(adminClient.listTopics(any(ListTopicsOptions.class))).thenReturn(listTopicsResult); + creator = new TopologyCreator(adminClient, customizations, true); + // Act + creator.checkTopic("topic1"); + // Assert + verify(listTopicsResult, times(1)).names(); + } + + @Test + void shouldFailWhenCheckTopics() { + // Arrange + KafkaFutureImpl> names = new KafkaFutureImpl<>(); + names.complete(Set.of("topic1", "topic2")); + doReturn(names).when(listTopicsResult).names(); + when(adminClient.listTopics(any(ListTopicsOptions.class))).thenReturn(listTopicsResult); + creator = new TopologyCreator(adminClient, customizations, true); + // Assert + assertThrows(TopicNotFoundException.class, () -> + // Act + creator.checkTopic("topic3")); + } +} diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java new file mode 100644 index 00000000..3219ebeb --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/converters/json/KafkaJacksonMessageConverterTest.java @@ -0,0 +1,101 @@ +package org.reactivecommons.async.kafka.converters.json; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; +import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; + +import java.net.URI; +import java.time.OffsetDateTime; +import java.util.Date; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class KafkaJacksonMessageConverterTest { + private static KafkaJacksonMessageConverter converter; + private static ObjectMapper objectMapper; + + @BeforeAll + static void setUp() { + ObjectMapperSupplier supplier = new DefaultObjectMapperSupplier(); + objectMapper = supplier.get(); + converter = new KafkaJacksonMessageConverter(objectMapper); + } + + @Test + void shouldSerializeDomainEvent() { + // Arrange + String id = UUID.randomUUID().toString(); + MyEvent event = new MyEvent("name", 1); + DomainEvent testEvent = new DomainEvent<>("test", id, event); + String expectedJson = "{\"name\":\"test\",\"eventId\":\"" + id + "\",\"data\":{\"name\":\"name\",\"age\":1}}"; + // Act + Message message = converter.toMessage(testEvent); + assertEquals(message, converter.toMessage(message)); + // Assert + assertEquals("test", message.getProperties().getTopic()); + assertEquals(id, message.getProperties().getKey()); + assertEquals("application/json", message.getProperties().getContentType()); + assertEquals(expectedJson, new String(message.getBody())); + } + + @Test + void shouldSerializeCloudEvent() throws JsonProcessingException { + // Arrange + String id = UUID.randomUUID().toString(); + MyEvent event = new MyEvent("name", 1); + OffsetDateTime dateTime = OffsetDateTime.now(); + CloudEvent testCloudEvent = CloudEventBuilder.v1() + .withId(id) + .withSource(URI.create("https://reactivecommons.org/events")) + .withType("test") + .withDataContentType("application/json") + .withTime(dateTime) + .withData(objectMapper.writeValueAsBytes(event)) + .build(); + + String expectedJson = "{\"specversion\":\"1.0\",\"id\":\"" + id + + "\",\"source\":\"https://reactivecommons.org/events\",\"type\":\"test\"," + + "\"datacontenttype\":\"application/json\",\"time\":\"" + dateTime + + "\",\"data\":{\"name\":\"name\",\"age\":1}}"; + JsonCloudEvent expectedJsonNode = objectMapper.readValue(expectedJson, JsonCloudEvent.class); + // Act + Message message = converter.toMessage(testCloudEvent); + // Assert + assertEquals("test", message.getProperties().getTopic()); + assertEquals(id, message.getProperties().getKey()); + assertEquals("application/cloudevents+json", message.getProperties().getContentType()); + JsonCloudEvent receivedJsonNode = objectMapper.readValue(new String(message.getBody()), JsonCloudEvent.class); + assertEquals(expectedJsonNode, receivedJsonNode); + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class MyEvent { + private String name; + private int age; + } + + @Data + public static class JsonCloudEvent { + private String specversion; + private String id; + private String source; + private String type; + private String datacontenttype; + private Date time; + private MyEvent data; + } +} diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListenerTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListenerTest.java new file mode 100644 index 00000000..e05ef076 --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/ApplicationEventListenerTest.java @@ -0,0 +1,99 @@ +package org.reactivecommons.async.kafka.listeners; + +import io.cloudevents.CloudEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; +import org.reactivecommons.async.api.handlers.EventHandler; +import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SuppressWarnings({"rawtypes", "unchecked"}) +@ExtendWith(MockitoExtension.class) +class ApplicationEventListenerTest { + + @Mock + private ReactiveMessageListener receiver; + @Mock + private HandlerResolver resolver; + @Mock + private MessageConverter messageConverter; + @Mock + private Message message; + + private ApplicationEventListener applicationEventListener; + + @BeforeEach + void setup() { + applicationEventListener = new ApplicationEventListener( + receiver, + resolver, + messageConverter, + true, + true, + 3, + 1000, + null, + null, + "testApp" + ); + + } + + @Test + void shouldHandleRawMessageSuccessfully() { + DomainEvent event = new DomainEvent<>("sample", "id", "data"); + DomainEventHandler domainEventHandler = mock(DomainEventHandler.class); + when(domainEventHandler.handle(event)).thenReturn(Mono.just("Handled")); + RegisteredEventListener registeredEventListenerMock = mock(RegisteredEventListener.class); + when(registeredEventListenerMock.getHandler()).thenReturn(domainEventHandler); + when(registeredEventListenerMock.getInputClass()).thenReturn(Object.class); + when(resolver.getEventListener(anyString())).thenReturn(registeredEventListenerMock); + when(messageConverter.readDomainEvent(any(Message.class), any(Class.class))).thenReturn(event); + + Mono flow = applicationEventListener.rawMessageHandler("executorPath").apply(message); + + StepVerifier.create(flow) + .expectNext("Handled") + .verifyComplete(); + + verify(resolver, times(1)).getEventListener(anyString()); + verify(messageConverter, times(1)).readDomainEvent(any(Message.class), any(Class.class)); + } + + @Test + void shouldHandleRawMessageSuccessfullyWhenCloudEvent() { + CloudEvent event = mock(CloudEvent.class); + EventHandler domainEventHandler = mock(CloudEventHandler.class); + when(domainEventHandler.handle(event)).thenReturn(Mono.empty()); + RegisteredEventListener registeredEventListenerMock = mock(RegisteredEventListener.class); + when(registeredEventListenerMock.getHandler()).thenReturn(domainEventHandler); + when(resolver.getEventListener(anyString())).thenReturn(registeredEventListenerMock); + when(messageConverter.readCloudEvent(any(Message.class))).thenReturn(event); + + Mono flow = applicationEventListener.rawMessageHandler("executorPath").apply(message); + + StepVerifier.create(flow) + .verifyComplete(); + + verify(resolver, times(1)).getEventListener(anyString()); + verify(messageConverter, times(1)).readCloudEvent(any(Message.class)); + } +} \ No newline at end of file diff --git a/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java new file mode 100644 index 00000000..014a7aca --- /dev/null +++ b/async/async-kafka/src/test/java/org/reactivecommons/async/kafka/listeners/GenericMessageListenerTest.java @@ -0,0 +1,119 @@ +package org.reactivecommons.async.kafka.listeners; + +import org.apache.kafka.common.header.Headers; +import org.apache.kafka.common.header.internals.RecordHeaders; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.MonoSink; +import reactor.kafka.receiver.ReceiverRecord; +import reactor.test.StepVerifier; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SuppressWarnings("unchecked") +@ExtendWith(MockitoExtension.class) +class GenericMessageListenerTest { + + @Mock + private ReactiveMessageListener receiver; + @Mock + private Message message; + @Mock + private TopologyCreator topologyCreator; + + @Mock + private RegisteredEventListener handler; + + SampleListener setup(Function> handler) { + return new SampleListener( + receiver, + true, + true, + 1, + 1, + mock(DiscardNotifier.class), + "event", + mock(CustomReporter.class), + "appName", + List.of("topic"), + handler + ); + + } + + @Test + void shouldStartListener() { + // Arrange + ReceiverRecord receiverRecord = mock(ReceiverRecord.class); + when(receiverRecord.topic()).thenReturn("topic"); + when(receiverRecord.value()).thenReturn("message".getBytes(StandardCharsets.UTF_8)); + Headers header = new RecordHeaders().add("contentType", "application/json".getBytes(StandardCharsets.UTF_8)); + when(receiverRecord.headers()).thenReturn(header); + when(receiverRecord.key()).thenReturn("key"); + + Flux> flux = Flux.just(receiverRecord); + when(receiver.listen(anyString(), any(List.class))).thenReturn(flux); + when(receiver.getMaxConcurrency()).thenReturn(1); + when(topologyCreator.createTopics(any(List.class))).thenReturn(Mono.empty()); + + final AtomicReference> sink = new AtomicReference<>(); + Mono flow = Mono.create(sink::set); + SampleListener sampleListener = setup(message1 -> { + sink.get().success(""); + return Mono.empty(); + }); + // Act + sampleListener.startListener(topologyCreator); + StepVerifier.create(flow).expectNext("").verifyComplete(); + // Assert + verify(topologyCreator, times(1)).createTopics(any(List.class)); + } + + public static class SampleListener extends GenericMessageListener { + private final Function> handler; + + public SampleListener(ReactiveMessageListener listener, boolean useDLQ, boolean createTopology, long maxRetries, + long retryDelay, DiscardNotifier discardNotifier, String objectType, + CustomReporter customReporter, String groupId, List topics, + Function> handler) { + super(listener, useDLQ, createTopology, maxRetries, retryDelay, discardNotifier, objectType, customReporter, + groupId, topics); + this.handler = handler; + } + + @Override + protected Function> rawMessageHandler(String executorPath) { + return handler; + } + + @Override + protected String getExecutorPath(ReceiverRecord msj) { + return msj.topic(); + } + + @Override + protected Object parseMessageForReporter(Message msj) { + return null; + } + } +} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java deleted file mode 100644 index f4418ec8..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/RabbitEDADirectAsyncGateway.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.reactivecommons.async; - -import io.micrometer.core.instrument.MeterRegistry; -import org.reactivecommons.async.commons.config.BrokerConfig; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; -import org.reactivecommons.async.rabbit.RabbitDirectAsyncGateway; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.ConnectionManager; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -public class RabbitEDADirectAsyncGateway extends RabbitDirectAsyncGateway { - private final ConnectionManager manager; - - public RabbitEDADirectAsyncGateway(BrokerConfig config, ReactiveReplyRouter router, ConnectionManager manager, String exchange, MessageConverter converter, MeterRegistry meterRegistry) { - super(config, router, manager.getSender(DEFAULT_DOMAIN), exchange, converter, meterRegistry); - this.manager = manager; - } - - @Override - protected ReactiveMessageSender resolveSender(String domain) { - return manager.getSender(domain); - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java deleted file mode 100644 index fcbe9833..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.CommandListenersConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(CommandListenersConfig.class) -@Configuration -public @interface EnableCommandListeners { -} - - - diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java deleted file mode 100644 index 359913fd..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.DirectAsyncGatewayConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(DirectAsyncGatewayConfig.class) -@Configuration -public @interface EnableDirectAsyncGateway { -} - - - diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java deleted file mode 100644 index 87791c20..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.EventListenersConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(EventListenersConfig.class) -@Configuration -public @interface EnableEventListeners { -} - - - diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java deleted file mode 100644 index c4bf5a80..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.CommandListenersConfig; -import org.reactivecommons.async.rabbit.config.EventListenersConfig; -import org.reactivecommons.async.rabbit.config.NotificationListenersConfig; -import org.reactivecommons.async.rabbit.config.QueryListenerConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - -/** - * This annotation enables all messages listeners (Query, Commands, Events). If you want to enable separately, please use - * EnableCommandListeners, EnableQueryListeners or EnableEventListeners. - * - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import({CommandListenersConfig.class, QueryListenerConfig.class, EventListenersConfig.class, NotificationListenersConfig.class}) -@Configuration -public @interface EnableMessageListeners { -} - - - diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java deleted file mode 100644 index 516f479e..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.NotificationListenersConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(NotificationListenersConfig.class) -@Configuration -public @interface EnableNotificationListener { -} - - - diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java deleted file mode 100644 index 0e1a5477..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationCommandListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class CommandListenersConfig { - private final AsyncPropsDomain asyncPropsDomain; - - @Bean - public ApplicationCommandListener applicationCommandListener(ConnectionManager manager, - DomainHandlers handlers, - MessageConverter converter, - CustomReporter errorReporter) { - AsyncProps asyncProps = asyncPropsDomain.getProps(DEFAULT_DOMAIN); - ApplicationCommandListener commandListener = new ApplicationCommandListener(manager.getListener(DEFAULT_DOMAIN), - asyncProps.getBrokerConfigProps().getCommandsQueue(), handlers.get(DEFAULT_DOMAIN), - asyncProps.getDirect().getExchange(), converter, asyncProps.getWithDLQRetry(), - asyncProps.getCreateTopology(), asyncProps.getDelayedCommands(), asyncProps.getMaxRetries(), - asyncProps.getRetryDelay(), asyncProps.getDirect().getMaxLengthBytes(), - manager.getDiscardNotifier(DEFAULT_DOMAIN), errorReporter); - - commandListener.startListener(); - - return commandListener; - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/ConnectionManager.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/ConnectionManager.java deleted file mode 100644 index a2ac9760..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/ConnectionManager.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; - -import java.util.Map; -import java.util.TreeMap; -import java.util.function.BiConsumer; - -public class ConnectionManager { - private final Map connections = new TreeMap<>(); - - @Builder - @Getter - public static class DomainConnections { - private final ReactiveMessageListener listener; - private final ReactiveMessageSender sender; - private final ConnectionFactoryProvider provider; - @Setter - private DiscardNotifier discardNotifier; - } - - public void forSender(BiConsumer consumer) { - connections.forEach((key, conn) -> consumer.accept(key, conn.getSender())); - } - - public void forListener(BiConsumer consumer) { - connections.forEach((key, conn) -> consumer.accept(key, conn.getListener())); - } - - public void setDiscardNotifier(String domain, DiscardNotifier discardNotifier) { - getChecked(domain).setDiscardNotifier(discardNotifier); - } - - public ConnectionManager addDomain(String domain, ReactiveMessageListener listener, ReactiveMessageSender sender, - ConnectionFactoryProvider provider) { - connections.put(domain, DomainConnections.builder() - .listener(listener) - .sender(sender) - .provider(provider) - .build()); - return this; - } - - public ReactiveMessageSender getSender(String domain) { - return getChecked(domain).getSender(); - } - - public ReactiveMessageListener getListener(String domain) { - return getChecked(domain).getListener(); - } - - private DomainConnections getChecked(String domain) { - DomainConnections domainConnections = connections.get(domain); - if (domainConnections == null) { - throw new RuntimeException("You are trying to use the domain " + domain - + " but this connection is not defined"); - } - return domainConnections; - } - - public DiscardNotifier getDiscardNotifier(String domain) { - return getChecked(domain).getDiscardNotifier(); - } - - public Map getProviders() { - return connections; - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java deleted file mode 100644 index 289a7925..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import lombok.RequiredArgsConstructor; -import lombok.extern.java.Log; -import org.reactivecommons.async.RabbitEDADirectAsyncGateway; -import org.reactivecommons.async.commons.config.BrokerConfig; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; -import org.reactivecommons.async.rabbit.RabbitDirectAsyncGateway; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationReplyListener; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; -import static reactor.rabbitmq.ExchangeSpecification.exchange; - -@Log -@Configuration -@Import(RabbitMqConfig.class) -@RequiredArgsConstructor -public class DirectAsyncGatewayConfig { - - @Bean - public RabbitDirectAsyncGateway rabbitDirectAsyncGateway(BrokerConfig config, ReactiveReplyRouter router, - ConnectionManager manager, MessageConverter converter, - MeterRegistry meterRegistry, - AsyncPropsDomain asyncPropsDomain) { - ReactiveMessageSender sender = manager.getSender(DEFAULT_DOMAIN); - AsyncProps asyncProps = asyncPropsDomain.getProps(DEFAULT_DOMAIN); - String exchangeName = asyncProps.getBrokerConfigProps().getDirectMessagesExchangeName(); - if (asyncProps.getCreateTopology()) { - sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("direct")).subscribe(); - } - return new RabbitEDADirectAsyncGateway(config, router, manager, exchangeName, converter, meterRegistry); - } - - @Bean - public ApplicationReplyListener msgListener(ReactiveReplyRouter router, AsyncPropsDomain asyncProps, - BrokerConfig config, ConnectionManager manager) { - AtomicReference localListener = new AtomicReference<>(); - - asyncProps.forEach((domain, props) -> { - if (props.isListenReplies()) { - final ApplicationReplyListener replyListener = - new ApplicationReplyListener(router, manager.getListener(domain), - props.getBrokerConfigProps().getReplyQueue(), - props.getBrokerConfigProps().getGlobalReplyExchangeName(), props.getCreateTopology()); - replyListener.startListening(config.getRoutingKey()); - - if (DEFAULT_DOMAIN.equals(domain)) { - localListener.set(replyListener); - } - } else { - log.log(Level.WARNING,"ApplicationReplyListener is disabled in AsyncProps or app.async." + domain - + ".listenReplies for domain " + domain); - } - }); - - return localListener.get(); - } - - @Bean - public ReactiveReplyRouter router() { - return new ReactiveReplyRouter(); - } - - @Bean - @ConditionalOnMissingBean(MeterRegistry.class) - public MeterRegistry defaultRabbitMeterRegistry() { - return new SimpleMeterRegistry(); - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java deleted file mode 100644 index 1a84bf42..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import org.reactivecommons.api.domain.DomainEventBus; -import org.reactivecommons.async.commons.config.BrokerConfig; -import org.reactivecommons.async.rabbit.RabbitDomainEventBus; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; -import static reactor.rabbitmq.ExchangeSpecification.exchange; - -@Configuration -@Import(RabbitMqConfig.class) -public class EventBusConfig { - - @Bean // app connection - public DomainEventBus domainEventBus(ConnectionManager manager, BrokerConfig config, - AsyncPropsDomain asyncPropsDomain) { - ReactiveMessageSender sender = manager.getSender(DEFAULT_DOMAIN); - AsyncProps asyncProps = asyncPropsDomain.getProps(DEFAULT_DOMAIN); - final String exchangeName = asyncProps.getBrokerConfigProps().getDomainEventsExchangeName(); - if (asyncProps.getCreateTopology()) { - sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("topic")).subscribe(); - } - return new RabbitDomainEventBus(sender, exchangeName, config); - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java deleted file mode 100644 index 0876112c..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationEventListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.util.concurrent.atomic.AtomicReference; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class EventListenersConfig { - - private final AsyncPropsDomain asyncPropsDomain; - - @Bean - public ApplicationEventListener eventListener(MessageConverter messageConverter, - ConnectionManager manager, DomainHandlers handlers, - CustomReporter errorReporter) { - AtomicReference external = new AtomicReference<>(); - manager.forListener((domain, receiver) -> { - AsyncProps asyncProps = asyncPropsDomain.getProps(domain); - if (!asyncProps.getDomain().isIgnoreThisListener()) { - final ApplicationEventListener listener = new ApplicationEventListener(receiver, - asyncProps.getBrokerConfigProps().getEventsQueue(), - asyncProps.getBrokerConfigProps().getDomainEventsExchangeName(), - handlers.get(domain), - messageConverter, asyncProps.getWithDLQRetry(), - asyncProps.getCreateTopology(), - asyncProps.getMaxRetries(), - asyncProps.getRetryDelay(), - asyncProps.getDomain().getEvents().getMaxLengthBytes(), - manager.getDiscardNotifier(domain), - errorReporter, - asyncProps.getAppName()); - if (DEFAULT_DOMAIN.equals(domain)) { - external.set(listener); - } - listener.startListener(); - } - }); - - return external.get(); - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java deleted file mode 100644 index 08f3bf0e..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/HandlerResolverBuilder.java +++ /dev/null @@ -1,111 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.reactivecommons.async.api.DefaultCommandHandler; -import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; -import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; -import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; -import org.reactivecommons.async.rabbit.HandlerResolver; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.stream.Stream; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -@Log4j2 -public class HandlerResolverBuilder { - - public static HandlerResolver buildResolver(String domain, - Map registries, - final DefaultCommandHandler defaultCommandHandler) { - - if (DEFAULT_DOMAIN.equals(domain)) { - final ConcurrentMap> queryHandlers = registries - .values().stream() - .flatMap(r -> r.getHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> commandHandlers = registries - .values().stream() - .flatMap(r -> r.getCommandHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> eventNotificationListener = registries - .values() - .stream() - .flatMap(r -> r.getEventNotificationListener().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> eventsToBind = getEventsToBind(domain, registries); - - final ConcurrentMap> eventHandlers = getEventHandlersWithDynamics(domain, registries); - - return new HandlerResolver(queryHandlers, eventHandlers, eventsToBind, eventNotificationListener, commandHandlers) { - @Override - @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, Object.class); - } - }; - } - - - final ConcurrentMap> eventsToBind = getEventsToBind(domain, registries); - final ConcurrentMap> eventHandlers = getEventHandlersWithDynamics(domain, registries); - - return new HandlerResolver(new ConcurrentHashMap<>(), eventHandlers, eventsToBind, new ConcurrentHashMap<>(), new ConcurrentHashMap<>()) { - @Override - @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, Object.class); - } - }; - } - - private static ConcurrentMap> getEventHandlersWithDynamics(String domain, Map registries) { - // event handlers and dynamic handlers - return registries - .values().stream() - .flatMap(r -> { - if (r.getDomainEventListeners().containsKey(domain)) { - return Stream.concat(r.getDomainEventListeners().get(domain).stream(), getDynamics(domain, r)); - } - log.warn("Domain " + domain + "does not have a connection defined in your configuration and you want to listen from it"); - return Stream.empty(); - }) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - } - - private static Stream> getDynamics(String domain, HandlerRegistry r) { - if (DEFAULT_DOMAIN.equals(domain)) { - return r.getDynamicEventHandlers().stream(); - } - return Stream.of(); - } - - private static ConcurrentMap> getEventsToBind(String domain, Map registries) { - return registries - .values().stream() - .flatMap(r -> { - if (r.getDomainEventListeners().containsKey(domain)) { - return r.getDomainEventListeners().get(domain).stream(); - } - log.warn("Domain " + domain + "does not have a connection defined in your configuration and you want to listen from it"); - return Stream.empty(); - }) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfig.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfig.java deleted file mode 100644 index e0c6b8f7..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationNotificationListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class NotificationListenersConfig { - - private final AsyncPropsDomain asyncPropsDomain; - - @Bean - public ApplicationNotificationListener eventNotificationListener(ConnectionManager manager, - DomainHandlers handlers, - MessageConverter messageConverter, - CustomReporter errorReporter) { - AsyncProps asyncProps = asyncPropsDomain.getProps(DEFAULT_DOMAIN); - final ApplicationNotificationListener listener = new ApplicationNotificationListener( - manager.getListener(DEFAULT_DOMAIN), - asyncProps.getBrokerConfigProps().getDomainEventsExchangeName(), - asyncProps.getBrokerConfigProps().getNotificationsQueue(), - asyncProps.getCreateTopology(), - handlers.get(DEFAULT_DOMAIN), - messageConverter, - manager.getDiscardNotifier(DEFAULT_DOMAIN), - errorReporter); - listener.startListener(); - return listener; - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java deleted file mode 100644 index 709b018c..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationQueryListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class QueryListenerConfig { - - private final AsyncPropsDomain asyncPropsDomain; - - @Bean - public ApplicationQueryListener queryListener(MessageConverter converter, - DomainHandlers handlers, - ConnectionManager manager, - CustomReporter errorReporter) { - AsyncProps asyncProps = asyncPropsDomain.getProps(DEFAULT_DOMAIN); - final ApplicationQueryListener listener = new ApplicationQueryListener(manager.getListener(DEFAULT_DOMAIN), - asyncProps.getBrokerConfigProps().getQueriesQueue(), handlers.get(DEFAULT_DOMAIN), - manager.getSender(DEFAULT_DOMAIN), asyncProps.getBrokerConfigProps().getDirectMessagesExchangeName(), - converter, asyncProps.getBrokerConfigProps().getGlobalReplyExchangeName(), asyncProps.getWithDLQRetry(), - asyncProps.getCreateTopology(), asyncProps.getMaxRetries(), - asyncProps.getRetryDelay(), asyncProps.getGlobal().getMaxLengthBytes(), - asyncProps.getDirect().isDiscardTimeoutQueries(), - manager.getDiscardNotifier(DEFAULT_DOMAIN), errorReporter); - - listener.startListener(); - - return listener; - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java deleted file mode 100644 index f89906ef..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import org.reactivecommons.async.rabbit.health.DomainRabbitReactiveHealthIndicator; -import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Configuration -@ConditionalOnClass(AbstractReactiveHealthIndicator.class) -public class RabbitHealthConfig { - - @Bean - public DomainRabbitReactiveHealthIndicator rabbitHealthIndicator(ConnectionManager manager) { - return new DomainRabbitReactiveHealthIndicator(manager); - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java deleted file mode 100644 index ef282676..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java +++ /dev/null @@ -1,246 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.java.Log; -import org.reactivecommons.api.domain.Command; -import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.api.domain.DomainEventBus; -import org.reactivecommons.async.api.AsyncQuery; -import org.reactivecommons.async.api.DefaultCommandHandler; -import org.reactivecommons.async.api.DefaultQueryHandler; -import org.reactivecommons.async.api.DynamicRegistry; -import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.communications.Message; -import org.reactivecommons.async.commons.config.BrokerConfig; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; -import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.DynamicRegistryImp; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.RabbitDiscardNotifier; -import org.reactivecommons.async.rabbit.RabbitDomainEventBus; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomainProperties; -import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; -import org.reactivecommons.async.rabbit.converters.json.JacksonCloudEventMessageConverter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.PropertyMapper; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.ChannelPool; -import reactor.rabbitmq.ChannelPoolFactory; -import reactor.rabbitmq.ChannelPoolOptions; -import reactor.rabbitmq.RabbitFlux; -import reactor.rabbitmq.Receiver; -import reactor.rabbitmq.ReceiverOptions; -import reactor.rabbitmq.Sender; -import reactor.rabbitmq.SenderOptions; -import reactor.rabbitmq.Utils; -import reactor.util.retry.Retry; - -import java.time.Duration; -import java.util.Map; -import java.util.logging.Level; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Log -@Configuration -@RequiredArgsConstructor -@EnableConfigurationProperties({RabbitProperties.class, AsyncPropsDomainProperties.class}) -@Import({RabbitHealthConfig.class, AsyncPropsDomain.class}) -public class RabbitMqConfig { - - private static final String LISTENER_TYPE = "listener"; - - private static final String SENDER_TYPE = "sender"; - - - @Bean - public ConnectionManager buildConnectionManager(AsyncPropsDomain props, MessageConverter converter, - BrokerConfig brokerConfig, ObjectMapperSupplier objectMapperSupplier) { - ConnectionManager connectionManager = new ConnectionManager(); - props.forEach((domain, properties) -> { - ConnectionFactoryProvider provider = createConnectionFactoryProvider(properties.getConnectionProperties()); - ReactiveMessageSender sender = createMessageSender(provider, properties, converter); - ReactiveMessageListener listener = createMessageListener(provider, properties); - connectionManager.addDomain(domain, listener, sender, provider); - - ReactiveMessageSender appDomainSender = connectionManager.getSender(domain); - DomainEventBus appDomainEventBus = new RabbitDomainEventBus(appDomainSender, props.getProps(domain) - .getBrokerConfigProps().getDomainEventsExchangeName(), brokerConfig); - DiscardNotifier notifier = new RabbitDiscardNotifier(appDomainEventBus, objectMapperSupplier.get()); - connectionManager.setDiscardNotifier(domain, notifier); - }); - return connectionManager; - } - - @Bean - public DomainHandlers buildHandlers(AsyncPropsDomain props, ApplicationContext context, - HandlerRegistry primaryRegistry, DefaultCommandHandler commandHandler) { - DomainHandlers handlers = new DomainHandlers(); - final Map registries = context.getBeansOfType(HandlerRegistry.class); - if (!registries.containsValue(primaryRegistry)) { - registries.put("primaryHandlerRegistry", primaryRegistry); - } - props.forEach((domain, properties) -> { - HandlerResolver resolver = HandlerResolverBuilder.buildResolver(domain, registries, commandHandler); - handlers.add(domain, resolver); - }); - return handlers; - } - - - private ReactiveMessageSender createMessageSender(ConnectionFactoryProvider provider, - AsyncProps props, - MessageConverter converter) { - final Sender sender = RabbitFlux.createSender(reactiveCommonsSenderOptions(props.getAppName(), provider, - props.getConnectionProperties())); - return new ReactiveMessageSender(sender, props.getAppName(), converter, new TopologyCreator(sender)); - } - - private SenderOptions reactiveCommonsSenderOptions(String appName, ConnectionFactoryProvider provider, RabbitProperties rabbitProperties) { - final Mono senderConnection = createConnectionMono(provider.getConnectionFactory(), appName, SENDER_TYPE); - final ChannelPoolOptions channelPoolOptions = new ChannelPoolOptions(); - final PropertyMapper map = PropertyMapper.get(); - - map.from(rabbitProperties.getCache().getChannel()::getSize).whenNonNull() - .to(channelPoolOptions::maxCacheSize); - - final ChannelPool channelPool = ChannelPoolFactory.createChannelPool( - senderConnection, - channelPoolOptions - ); - - return new SenderOptions() - .channelPool(channelPool) - .resourceManagementChannelMono(channelPool.getChannelMono() - .transform(Utils::cache)); - } - - public ReactiveMessageListener createMessageListener(ConnectionFactoryProvider provider, AsyncProps props) { - final Mono connection = - createConnectionMono(provider.getConnectionFactory(), props.getAppName(), LISTENER_TYPE); - final Receiver receiver = RabbitFlux.createReceiver(new ReceiverOptions().connectionMono(connection)); - final Sender sender = RabbitFlux.createSender(new SenderOptions().connectionMono(connection)); - - return new ReactiveMessageListener(receiver, - new TopologyCreator(sender), - props.getFlux().getMaxConcurrency(), - props.getPrefetchCount()); - } - - @SneakyThrows - public ConnectionFactoryProvider createConnectionFactoryProvider(RabbitProperties properties) { - final ConnectionFactory factory = new ConnectionFactory(); - PropertyMapper map = PropertyMapper.get(); - map.from(properties::determineHost).whenNonNull().to(factory::setHost); - map.from(properties::determinePort).to(factory::setPort); - map.from(properties::determineUsername).whenNonNull().to(factory::setUsername); - map.from(properties::determinePassword).whenNonNull().to(factory::setPassword); - map.from(properties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost); - factory.useNio(); - if (properties.getSsl() != null && properties.getSsl().isEnabled()) { - factory.useSslProtocol(); - } - return () -> factory; - } - - @Bean - @ConditionalOnMissingBean - public BrokerConfig brokerConfig() { - return new BrokerConfig(); - } - - @Bean - @ConditionalOnMissingBean - public ObjectMapperSupplier objectMapperSupplier() { - return new DefaultObjectMapperSupplier(); - } - - @Bean - @ConditionalOnMissingBean - public MessageConverter messageConverter(ObjectMapperSupplier objectMapperSupplier) { - return new JacksonCloudEventMessageConverter(objectMapperSupplier.get()); - } - - @Bean - @ConditionalOnMissingBean - public CustomReporter reactiveCommonsCustomErrorReporter() { - return new CustomReporter() { - @Override - public Mono reportError(Throwable ex, Message rawMessage, Command message, boolean redelivered) { - return Mono.empty(); - } - - @Override - public Mono reportError(Throwable ex, Message rawMessage, DomainEvent message, boolean redelivered) { - return Mono.empty(); - } - - @Override - public Mono reportError(Throwable ex, Message rawMessage, AsyncQuery message, boolean redelivered) { - return Mono.empty(); - } - }; - } - - Mono createConnectionMono(ConnectionFactory factory, String connectionPrefix, String connectionType) { - return Mono.fromCallable(() -> factory.newConnection(connectionPrefix + " " + connectionType)) - .doOnError(err -> - log.log(Level.SEVERE, "Error creating connection to RabbitMq Broker. Starting retry process...", err) - ) - .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(300)) - .maxBackoff(Duration.ofMillis(3000))) - .cache(); - } - - @Bean - public DynamicRegistry dynamicRegistry(ConnectionManager connectionManager, AsyncPropsDomain asyncPropsDomain, - DomainHandlers handlers) { - IBrokerConfigProps brokerConfigProps = new BrokerConfigProps(asyncPropsDomain.getProps(DEFAULT_DOMAIN)); - return new DynamicRegistryImp(handlers.get(DEFAULT_DOMAIN), - connectionManager.getListener(DEFAULT_DOMAIN).getTopologyCreator(), brokerConfigProps); - } - - @Bean - @ConditionalOnMissingBean - public DefaultQueryHandler defaultHandler() { - return (DefaultQueryHandler) command -> - Mono.error(new RuntimeException("No Handler Registered")); - } - - @Bean - @ConditionalOnMissingBean - public DefaultCommandHandler defaultCommandHandler() { - return message -> Mono.error(new RuntimeException("No Handler Registered")); - } - - @Bean - @ConditionalOnMissingBean(HandlerRegistry.class) - public HandlerRegistry defaultHandlerRegistry() { - return HandlerRegistry.register(); - } - - @Bean - @ConditionalOnMissingBean(AsyncPropsDomain.SecretFiller.class) - public AsyncPropsDomain.SecretFiller defaultSecretFiller() { - return ignored -> { - }; - } - -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java deleted file mode 100644 index c31d52da..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import lombok.Getter; -import lombok.Setter; -import org.reactivecommons.async.rabbit.config.RabbitProperties; -import org.reactivecommons.async.rabbit.config.exceptions.InvalidConfigurationException; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.util.StringUtils; - -import java.util.HashMap; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@Getter -@Setter -public class AsyncPropsDomain extends HashMap { - public AsyncPropsDomain(@Value("${spring.application.name}") String defaultAppName, - RabbitProperties defaultRabbitProperties, - AsyncPropsDomainProperties configured, - SecretFiller secretFiller) { - super(configured); - this.computeIfAbsent(DEFAULT_DOMAIN, k -> new AsyncProps()); - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(new JavaTimeModule()); - super.forEach((key, value) -> { // To ensure that each domain has an appName - if (value.getAppName() == null) { - if (defaultAppName == null || defaultAppName.isEmpty()) { - throw new InvalidConfigurationException("defaultAppName does not has value and domain " + key - + " has not set the property appName. please use app.async." + key + ".appName property or " + - " spring.application.name property or withDefaultAppName in builder"); - } - value.setAppName(defaultAppName); - } - if (value.getConnectionProperties() == null) { - if (defaultRabbitProperties == null) { - throw new InvalidConfigurationException("Domain " + key + " could not be instantiated because no" + - " RabbitProperties properties found, please use withDefaultRabbitProperties or define the" + - "default " + key + " domain with properties explicitly"); - } - value.setConnectionProperties(mapper.convertValue(defaultRabbitProperties, RabbitProperties.class)); - } - if (value.getBrokerConfigProps() == null) { - value.setBrokerConfigProps(new BrokerConfigProps(value)); - } - if (StringUtils.hasText(value.getSecret()) && secretFiller != null) { - secretFiller.fillWithSecret(value); - } - }); - } - - public AsyncProps getProps(String domain) { - AsyncProps props = get(domain); - if (props == null) { - throw new InvalidConfigurationException("Domain " + domain + " id not defined"); - } - return props; - } - - public static AsyncPropsDomainBuilder builder() { - return new AsyncPropsDomainBuilder(); - } - - public static class AsyncPropsDomainBuilder { - private String defaultAppName; - private RabbitProperties defaultRabbitProperties; - private SecretFiller secretFiller; - private final HashMap domains = new HashMap<>(); - - - public AsyncPropsDomainBuilder withDefaultRabbitProperties(RabbitProperties defaultRabbitProperties) { - this.defaultRabbitProperties = defaultRabbitProperties; - return this; - } - - - public AsyncPropsDomainBuilder withDefaultAppName(String defaultAppName) { - this.defaultAppName = defaultAppName; - return this; - } - - - public AsyncPropsDomainBuilder withSecretFiller(SecretFiller secretFiller) { - this.secretFiller = secretFiller; - return this; - } - - public AsyncPropsDomainBuilder withDomain(String domain, AsyncProps props) { - domains.put(domain, props); - return this; - } - - public AsyncPropsDomain build() { - AsyncPropsDomainProperties domainProperties = new AsyncPropsDomainProperties(domains); - if (defaultRabbitProperties == null) { - defaultRabbitProperties = new RabbitProperties(); - } - return new AsyncPropsDomain(defaultAppName, defaultRabbitProperties, domainProperties, secretFiller); - } - - } - - public interface SecretFiller { - void fillWithSecret(AsyncProps props); - } - -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java deleted file mode 100644 index 19adb140..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.util.HashMap; -import java.util.Map; - -@Getter -@Setter -@ConfigurationProperties(prefix = "app.async") -public class AsyncPropsDomainProperties extends HashMap { - - public AsyncPropsDomainProperties() { - } - - public AsyncPropsDomainProperties(Map m) { - super(m); - } - - public static AsyncPropsDomainPropertiesBuilder builder() { - return new AsyncPropsDomainPropertiesBuilder(); - } - - public static class AsyncPropsDomainPropertiesBuilder { - private final HashMap domains = new HashMap<>(); - - public AsyncPropsDomainPropertiesBuilder withDomain(String domain, AsyncProps props) { - domains.put(domain, props); - return this; - } - - public AsyncPropsDomainProperties build() { - return new AsyncPropsDomainProperties(domains); - } - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java deleted file mode 100644 index e27f87ab..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicator.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.reactivecommons.async.rabbit.health; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import lombok.extern.log4j.Log4j2; -import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; -import org.reactivecommons.async.rabbit.config.ConnectionManager; -import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import reactor.core.publisher.Mono; - -import java.net.SocketException; -import java.util.stream.Collectors; - -@Log4j2 -@RequiredArgsConstructor -public class DomainRabbitReactiveHealthIndicator extends AbstractReactiveHealthIndicator { - private static final String VERSION = "version"; - private final ConnectionManager manager; - - @Override - protected Mono doHealthCheck(Health.Builder builder) { - return Mono.zip(manager.getProviders() - .entrySet() - .stream() - .map(entry -> checkSingle(entry.getKey(), entry.getValue().getProvider())) - .collect(Collectors.toList()), this::merge); - } - - private Health merge(Object[] results) { - Health.Builder builder = Health.up(); - for (Object obj : results) { - Status status = (Status) obj; - builder.withDetail(status.getDomain(), status.getVersion()); - } - return builder.build(); - } - - private Mono checkSingle(String domain, ConnectionFactoryProvider provider) { - return Mono.defer(() -> getVersion(provider)) - .map(version -> Status.builder().version(version).domain(domain).build()); - } - - private Mono getVersion(ConnectionFactoryProvider provider) { - return Mono.just(provider) - .map(ConnectionFactoryProvider::getConnectionFactory) - .map(this::getRawVersion); - } - - @SneakyThrows - private String getRawVersion(ConnectionFactory factory) { - Connection connection = null; - try { - connection = factory.newConnection(); - return connection.getServerProperties().get(VERSION).toString(); - } catch (SocketException e) { - log.warn("Identified error", e); - throw new RuntimeException(e); - } finally { - if (connection != null) { - try { - connection.close(); - } catch (Exception e) { - log.error("Error closing health connection", e); - } - } - } - } -} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/Status.java b/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/Status.java deleted file mode 100644 index b3be58a2..00000000 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/health/Status.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.reactivecommons.async.rabbit.health; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class Status { - private final String version; - private final String domain; -} diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java deleted file mode 100644 index 67e20c5f..00000000 --- a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationCommandListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ConsumeOptions; -import reactor.rabbitmq.ExchangeSpecification; -import reactor.rabbitmq.Receiver; - -import java.lang.reflect.Field; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@ExtendWith(MockitoExtension.class) -class CommandListenersConfigTest { - - - private final AsyncProps props = new AsyncProps(); - private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() - .withDefaultAppName("appName") - .withDefaultRabbitProperties(new RabbitProperties()) - .withDomain(DEFAULT_DOMAIN, props) - .build(); - private CommandListenersConfig config = new CommandListenersConfig(asyncPropsDomain); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - private final ConnectionManager manager = new ConnectionManager(); - private final DomainHandlers handlers = new DomainHandlers(); - - @BeforeEach - public void init() { - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - manager.addDomain(DEFAULT_DOMAIN, listener, null, null); - handlers.add(DEFAULT_DOMAIN, handlerResolver); - } - - @Test - void applicationCommandListener() { - final ApplicationCommandListener commandListener = config.applicationCommandListener(manager, handlers, messageConverter, customReporter); - Assertions.assertThat(commandListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java deleted file mode 100644 index ec27f5e4..00000000 --- a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationEventListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ConsumeOptions; -import reactor.rabbitmq.ExchangeSpecification; -import reactor.rabbitmq.QueueSpecification; -import reactor.rabbitmq.Receiver; - -import java.util.Collections; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -class EventListenersConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() - .withDefaultAppName("appName") - .withDefaultRabbitProperties(new RabbitProperties()) - .withDomain(DEFAULT_DOMAIN, props) - .build(); - private final EventListenersConfig config = new EventListenersConfig(asyncPropsDomain); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final ReactiveMessageSender sender = mock(ReactiveMessageSender.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - private ConnectionManager connectionManager; - private final DomainHandlers handlers = new DomainHandlers(); - - @BeforeEach - public void init() { - when(handlerResolver.getEventListeners()).thenReturn(Collections.emptyList()); - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declare(any(QueueSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareDLQ(any(String.class), any(String.class), any(Integer.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - connectionManager = new ConnectionManager(); - connectionManager.addDomain(HandlerRegistry.DEFAULT_DOMAIN, listener, sender, null); - handlers.add(HandlerRegistry.DEFAULT_DOMAIN, handlerResolver); - } - - @Test - void eventListener() { - final ApplicationEventListener eventListener = config.eventListener( - messageConverter, - connectionManager, - handlers, - customReporter - ); - - Assertions.assertThat(eventListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java deleted file mode 100644 index 45d494ca..00000000 --- a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/NotificationListenersConfigTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationNotificationListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ConsumeOptions; -import reactor.rabbitmq.ExchangeSpecification; -import reactor.rabbitmq.QueueSpecification; -import reactor.rabbitmq.Receiver; - -import java.util.Collections; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -class NotificationListenersConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() - .withDefaultAppName("appName") - .withDefaultRabbitProperties(new RabbitProperties()) - .withDomain(DEFAULT_DOMAIN, props) - .build(); - private final NotificationListenersConfig config = new NotificationListenersConfig(asyncPropsDomain); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final DiscardNotifier discardNotifier = mock(DiscardNotifier.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - private final ConnectionManager manager = new ConnectionManager(); - private final DomainHandlers handlers = new DomainHandlers(); - - @BeforeEach - public void init() { - when(handlerResolver.getEventListeners()).thenReturn(Collections.emptyList()); - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declare(any(QueueSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareDLQ(any(String.class), any(String.class), any(Integer.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - manager.addDomain(DEFAULT_DOMAIN, listener, null, null); - handlers.add(DEFAULT_DOMAIN, handlerResolver); - } - - @Test - void eventNotificationListener() { - final ApplicationNotificationListener applicationEventListener = - config.eventNotificationListener(manager, handlers, messageConverter, customReporter); - Assertions.assertThat(applicationEventListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java deleted file mode 100644 index 13a2f2a4..00000000 --- a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; -import org.reactivecommons.async.rabbit.listeners.ApplicationQueryListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ConsumeOptions; -import reactor.rabbitmq.ExchangeSpecification; -import reactor.rabbitmq.QueueSpecification; -import reactor.rabbitmq.Receiver; - -import java.util.Collections; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -class QueryListenerConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final AsyncPropsDomain asyncPropsDomain = AsyncPropsDomain.builder() - .withDefaultAppName("appName") - .withDefaultRabbitProperties(new RabbitProperties()) - .withDomain(DEFAULT_DOMAIN, props) - .build(); - private final QueryListenerConfig config = new QueryListenerConfig(asyncPropsDomain); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - private final ReactiveMessageSender sender = mock(ReactiveMessageSender.class); - private final ConnectionManager manager = new ConnectionManager(); - private final DomainHandlers handlers = new DomainHandlers(); - - @BeforeEach - public void init() { - when(handlerResolver.getEventListeners()).thenReturn(Collections.emptyList()); - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declare(any(QueueSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareDLQ(any(String.class), any(String.class), any(Integer.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - manager.addDomain(DEFAULT_DOMAIN, listener, sender, null); - handlers.add(DEFAULT_DOMAIN, handlerResolver); - } - - @Test - void queryListener() { - final ApplicationQueryListener queryListener = config.queryListener(messageConverter, handlers, manager, customReporter); - Assertions.assertThat(queryListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java deleted file mode 100644 index 450d2f2a..00000000 --- a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import org.junit.jupiter.api.Test; -import org.reactivecommons.api.domain.Command; -import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.async.api.AsyncQuery; -import org.reactivecommons.async.commons.communications.Message; -import org.reactivecommons.async.commons.ext.CustomReporter; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class RabbitMqConfigTest { - - RabbitMqConfig config = new RabbitMqConfig(); - - @Test - void retryInitialConnection() throws IOException, TimeoutException { - final String connectionType = "sender"; - final String appName = "appName"; - final String connectionName = "appName sender"; - - final AtomicInteger count = new AtomicInteger(); - final Connection connection = mock(Connection.class); - ConnectionFactory factory = mock(ConnectionFactory.class); - when(factory.newConnection(connectionName)).thenAnswer(invocation -> { - if(count.incrementAndGet() == 10){ - return connection; - } - throw new RuntimeException(); - }); - StepVerifier.withVirtualTime(() -> config.createConnectionMono(factory, appName, connectionType)) - .thenAwait(Duration.ofMinutes(2)) - .expectNext(connection).verifyComplete(); - } - - @Test - void shouldCreateDefaultErrorReporter() { - final CustomReporter errorReporter = config.reactiveCommonsCustomErrorReporter(); - assertThat(errorReporter.reportError(mock(Throwable.class), mock(Message.class), mock(Command.class), true)).isNotNull(); - assertThat(errorReporter.reportError(mock(Throwable.class), mock(Message.class), mock(DomainEvent.class), true)).isNotNull(); - assertThat(errorReporter.reportError(mock(Throwable.class), mock(Message.class), mock(AsyncQuery.class), true)).isNotNull(); - } - - @Test - void shouldGenerateDefaultReeporter() { - final CustomReporter customReporter = config.reactiveCommonsCustomErrorReporter(); - final Mono r1 = customReporter.reportError(mock(Throwable.class), mock(Message.class), mock(Command.class), true); - final Mono r2 = customReporter.reportError(mock(Throwable.class), mock(Message.class), mock(DomainEvent.class), true); - final Mono r3 = customReporter.reportError(mock(Throwable.class), mock(Message.class), mock(AsyncQuery.class), true); - - assertThat(r1).isNotNull(); - assertThat(r2).isNotNull(); - assertThat(r3).isNotNull(); - - } -} diff --git a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java b/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java deleted file mode 100644 index fb533280..00000000 --- a/async/async-rabbit-starter-eda/src/test/java/org/reactivecommons/async/rabbit/health/DomainRabbitReactiveHealthIndicatorTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.reactivecommons.async.rabbit.health; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; -import org.reactivecommons.async.rabbit.config.ConnectionManager; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Health.Builder; -import org.springframework.boot.actuate.health.Status; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import java.io.IOException; -import java.util.Map; -import java.util.TreeMap; -import java.util.concurrent.TimeoutException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; - -@ExtendWith(MockitoExtension.class) -public class DomainRabbitReactiveHealthIndicatorTest { - @Mock - private ConnectionFactoryProvider provider; - @Mock - private ConnectionFactory factory; - @Mock - private Connection connection; - - private DomainRabbitReactiveHealthIndicator indicator; - - @BeforeEach - void setup() { - ConnectionManager connectionManager = new ConnectionManager(); - connectionManager.addDomain(DEFAULT_DOMAIN, null, null, provider); - connectionManager.addDomain("domain2", null, null, provider); - connectionManager.addDomain("domain3", null, null, provider); - indicator = new DomainRabbitReactiveHealthIndicator(connectionManager); - when(provider.getConnectionFactory()).thenReturn(factory); - } - - @Test - void shouldBeUp() throws IOException, TimeoutException { - // Arrange - Map properties = new TreeMap<>(); - properties.put("version", "1.2.3"); - when(factory.newConnection()).thenReturn(connection); - when(connection.getServerProperties()).thenReturn(properties); - // Act - Mono result = indicator.doHealthCheck(new Builder()); - // Assert - StepVerifier.create(result) - .assertNext(health -> { - assertEquals("1.2.3", health.getDetails().get(DEFAULT_DOMAIN)); - assertEquals("1.2.3", health.getDetails().get("domain2")); - assertEquals("1.2.3", health.getDetails().get("domain3")); - assertEquals(Status.UP, health.getStatus()); - }) - .verifyComplete(); - } - - @Test - void shouldBeDown() throws IOException, TimeoutException { - // Arrange - when(factory.newConnection()).thenThrow(new TimeoutException("Connection timeout")); - // Act - Mono result = indicator.doHealthCheck(new Builder()); - // Assert - StepVerifier.create(result) - .expectError(TimeoutException.class) - .verify(); - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java deleted file mode 100644 index 6820ab50..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.EventBusConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(EventBusConfig.class) -@Configuration -public @interface EnableDomainEventBus { -} - - - diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java deleted file mode 100644 index 51aebd4b..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.CommandListenersConfig; -import org.reactivecommons.async.rabbit.config.EventListenersConfig; -import org.reactivecommons.async.rabbit.config.NotificacionListenersConfig; -import org.reactivecommons.async.rabbit.config.QueryListenerConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - -/** - * This annotation enables all messages listeners (Query, Commands, Events). If you want to enable separately, please use - * EnableCommandListeners, EnableQueryListeners or EnableEventListeners. - * - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import({CommandListenersConfig.class, QueryListenerConfig.class, EventListenersConfig.class, NotificacionListenersConfig.class}) -@Configuration -public @interface EnableMessageListeners { -} - - - diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java deleted file mode 100644 index 66f97f02..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.NotificacionListenersConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(NotificacionListenersConfig.class) -@Configuration -public @interface EnableNotificationListener { -} - - - diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java deleted file mode 100644 index 6eb878b0..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.impl.config.annotations; - -import org.reactivecommons.async.rabbit.config.QueryListenerConfig; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.lang.annotation.*; - - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -@Documented -@Import(QueryListenerConfig.class) -@Configuration -public @interface EnableQueryListeners { -} - - - diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java deleted file mode 100644 index 49fe308c..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/CommandListenersConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationCommandListener; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class CommandListenersConfig { - - @Value("${spring.application.name}") - private String appName; - - private final AsyncProps asyncProps; - - @Bean - public ApplicationCommandListener applicationCommandListener(ReactiveMessageListener listener, - HandlerResolver resolver, MessageConverter converter, - DiscardNotifier discardNotifier, - IBrokerConfigProps brokerConfigProps, - CustomReporter errorReporter) { - ApplicationCommandListener commandListener = new ApplicationCommandListener(listener, - brokerConfigProps.getCommandsQueue(), resolver, - brokerConfigProps.getDirectMessagesExchangeName(), converter, asyncProps.getWithDLQRetry(), - asyncProps.getCreateTopology(), asyncProps.getDelayedCommands(), asyncProps.getMaxRetries(), - asyncProps.getRetryDelay(), asyncProps.getDirect().getMaxLengthBytes(), discardNotifier, errorReporter); - - commandListener.startListener(); - - return commandListener; - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java deleted file mode 100644 index cb85862c..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import lombok.RequiredArgsConstructor; -import lombok.extern.java.Log; -import org.reactivecommons.async.commons.config.BrokerConfig; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; -import org.reactivecommons.async.rabbit.RabbitDirectAsyncGateway; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationReplyListener; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import java.util.logging.Level; - -@Log -@Configuration -@Import(RabbitMqConfig.class) -@RequiredArgsConstructor -public class DirectAsyncGatewayConfig { - - private final IBrokerConfigProps brokerConfigProps; - - @Bean - public RabbitDirectAsyncGateway rabbitDirectAsyncGateway(BrokerConfig config, ReactiveReplyRouter router, - ReactiveMessageSender rSender, MessageConverter converter, - MeterRegistry meterRegistry) { - return new RabbitDirectAsyncGateway(config, router, rSender, brokerConfigProps.getDirectMessagesExchangeName(), - converter, meterRegistry); - } - - @Bean - public ApplicationReplyListener msgListener(ReactiveReplyRouter router, BrokerConfig config, AsyncProps asyncProps, - ReactiveMessageListener listener) { - if (!asyncProps.isListenReplies()) { - log.log(Level.WARNING, "ApplicationReplyListener is disabled in AsyncProps or app.async.listenReplies"); - return null; - } - final ApplicationReplyListener replyListener = new ApplicationReplyListener(router, listener, - brokerConfigProps.getReplyQueue(), brokerConfigProps.getGlobalReplyExchangeName(), - asyncProps.getCreateTopology()); - replyListener.startListening(config.getRoutingKey()); - return replyListener; - } - - @Bean - public ReactiveReplyRouter router() { - return new ReactiveReplyRouter(); - } - - @Bean - @ConditionalOnMissingBean(MeterRegistry.class) - public MeterRegistry defaultRabbitMeterRegistry() { - return new SimpleMeterRegistry(); - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java deleted file mode 100644 index 836655ac..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import org.reactivecommons.api.domain.DomainEventBus; -import org.reactivecommons.async.commons.config.BrokerConfig; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.rabbit.RabbitDomainEventBus; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -import static reactor.rabbitmq.ExchangeSpecification.exchange; - -@Configuration -@Import(RabbitMqConfig.class) -public class EventBusConfig { - - @Bean - public DomainEventBus domainEventBus(ReactiveMessageSender sender, IBrokerConfigProps props, AsyncProps asyncProps, - BrokerConfig config) { - final String exchangeName = props.getDomainEventsExchangeName(); - if (asyncProps.getCreateTopology()) { - sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("topic")).subscribe(); - } - return new RabbitDomainEventBus(sender, exchangeName, config); - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java deleted file mode 100644 index 6a528b5c..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/EventListenersConfig.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationEventListener; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class EventListenersConfig { - - @Value("${spring.application.name}") - private String appName; - - private final AsyncProps asyncProps; - - @Bean - public ApplicationEventListener eventListener(HandlerResolver resolver, MessageConverter messageConverter, - ReactiveMessageListener receiver, DiscardNotifier discardNotifier, - IBrokerConfigProps brokerConfigProps, CustomReporter errorReporter) { - - final ApplicationEventListener listener = new ApplicationEventListener(receiver, - brokerConfigProps.getEventsQueue(), brokerConfigProps.getDomainEventsExchangeName(), resolver, - messageConverter, asyncProps.getWithDLQRetry(), asyncProps.getCreateTopology(), - asyncProps.getMaxRetries(), asyncProps.getRetryDelay(), - asyncProps.getDomain().getEvents().getMaxLengthBytes(), discardNotifier, errorReporter, appName); - - listener.startListener(); - - return listener; - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfig.java deleted file mode 100644 index fe41588e..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationNotificationListener; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class NotificacionListenersConfig { - - private final AsyncProps asyncProps; - - @Bean - public ApplicationNotificationListener eventNotificationListener(HandlerResolver resolver, - MessageConverter messageConverter, - ReactiveMessageListener receiver, - DiscardNotifier discardNotifier, - IBrokerConfigProps brokerConfigProps, - CustomReporter errorReporter) { - final ApplicationNotificationListener listener = new ApplicationNotificationListener( - receiver, - brokerConfigProps.getDomainEventsExchangeName(), - brokerConfigProps.getNotificationsQueue(), - asyncProps.getCreateTopology(), - resolver, - messageConverter, - discardNotifier, - errorReporter); - listener.startListener(); - return listener; - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java deleted file mode 100644 index cb2391ec..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/QueryListenerConfig.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import lombok.RequiredArgsConstructor; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationQueryListener; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - -@Configuration -@RequiredArgsConstructor -@Import(RabbitMqConfig.class) -public class QueryListenerConfig { - - @Value("${spring.application.name}") - private String appName; - - private final AsyncProps asyncProps; - - @Bean - public ApplicationQueryListener queryListener(MessageConverter converter, HandlerResolver resolver, - ReactiveMessageSender sender, ReactiveMessageListener rlistener, - DiscardNotifier discardNotifier, - IBrokerConfigProps brokerConfigProps, - CustomReporter errorReporter) { - final ApplicationQueryListener listener = new ApplicationQueryListener(rlistener, - brokerConfigProps.getQueriesQueue(), resolver, sender, brokerConfigProps.getDirectMessagesExchangeName(), converter, - brokerConfigProps.getGlobalReplyExchangeName(), asyncProps.getWithDLQRetry(), asyncProps.getCreateTopology(), - asyncProps.getMaxRetries(), asyncProps.getRetryDelay(), asyncProps.getGlobal().getMaxLengthBytes(), - asyncProps.getDirect().isDiscardTimeoutQueries(), discardNotifier, errorReporter); - - listener.startListener(); - - return listener; - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java deleted file mode 100644 index cdef9c85..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitHealthConfig.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import org.reactivecommons.async.rabbit.health.RabbitReactiveHealthIndicator; -import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ConditionalOnClass(AbstractReactiveHealthIndicator.class) -public class RabbitHealthConfig { - - @Bean - public RabbitReactiveHealthIndicator rabbitHealthIndicator(ConnectionFactoryProvider provider) { - return new RabbitReactiveHealthIndicator(provider); - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java deleted file mode 100644 index 697ad7d8..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java +++ /dev/null @@ -1,272 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import lombok.RequiredArgsConstructor; -import lombok.extern.java.Log; -import org.reactivecommons.api.domain.Command; -import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.api.domain.DomainEventBus; -import org.reactivecommons.async.api.AsyncQuery; -import org.reactivecommons.async.api.DefaultCommandHandler; -import org.reactivecommons.async.api.DefaultQueryHandler; -import org.reactivecommons.async.api.DynamicRegistry; -import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; -import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; -import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.communications.Message; -import org.reactivecommons.async.commons.config.BrokerConfig; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; -import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.DynamicRegistryImp; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.RabbitDiscardNotifier; -import org.reactivecommons.async.rabbit.RabbitDomainEventBus; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.PropertyMapper; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.ChannelPool; -import reactor.rabbitmq.ChannelPoolFactory; -import reactor.rabbitmq.ChannelPoolOptions; -import reactor.rabbitmq.RabbitFlux; -import reactor.rabbitmq.Receiver; -import reactor.rabbitmq.ReceiverOptions; -import reactor.rabbitmq.Sender; -import reactor.rabbitmq.SenderOptions; -import reactor.rabbitmq.Utils; -import reactor.util.retry.Retry; - -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.time.Duration; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.logging.Level; -import java.util.stream.Stream; - -import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; -import static reactor.rabbitmq.ExchangeSpecification.exchange; - -@Log -@Configuration -@RequiredArgsConstructor -@EnableConfigurationProperties({ - RabbitProperties.class, - AsyncProps.class -}) -@Import({BrokerConfigProps.class, RabbitHealthConfig.class}) -public class RabbitMqConfig { - - private static final String LISTENER_TYPE = "listener"; - - private static final String SENDER_TYPE = "sender"; - - private final AsyncProps asyncProps; - - @Value("${spring.application.name}") - private String appName; - - @Bean - public ReactiveMessageSender messageSender(MessageConverter converter, BrokerConfigProps brokerConfigProps, SenderOptions senderOptions) { - final Sender sender = RabbitFlux.createSender(senderOptions); - return new ReactiveMessageSender(sender, brokerConfigProps.getAppName(), converter, new TopologyCreator(sender)); - } - - @Bean - public SenderOptions reactiveCommonsSenderOptions(ConnectionFactoryProvider provider, RabbitProperties rabbitProperties) { - final Mono senderConnection = createConnectionMono(provider.getConnectionFactory(), appName, SENDER_TYPE); - final ChannelPoolOptions channelPoolOptions = new ChannelPoolOptions(); - final PropertyMapper map = PropertyMapper.get(); - - map.from(rabbitProperties.getCache().getChannel()::getSize).whenNonNull() - .to(channelPoolOptions::maxCacheSize); - - final ChannelPool channelPool = ChannelPoolFactory.createChannelPool( - senderConnection, - channelPoolOptions - ); - - return new SenderOptions() - .channelPool(channelPool) - .resourceManagementChannelMono(channelPool.getChannelMono() - .transform(Utils::cache)); - } - - @Bean - public ReactiveMessageListener messageListener(ConnectionFactoryProvider provider) { - final Mono connection = - createConnectionMono(provider.getConnectionFactory(), appName, LISTENER_TYPE); - final Receiver receiver = RabbitFlux.createReceiver(new ReceiverOptions().connectionMono(connection)); - final Sender sender = RabbitFlux.createSender(new SenderOptions().connectionMono(connection)); - - return new ReactiveMessageListener(receiver, - new TopologyCreator(sender), - asyncProps.getFlux().getMaxConcurrency(), - asyncProps.getPrefetchCount()); - } - - @Bean - @ConditionalOnMissingBean - public BrokerConfig brokerConfig() { - return new BrokerConfig(); - } - - @Bean - @ConditionalOnMissingBean - public ConnectionFactoryProvider rabbitRConnectionFactory(RabbitProperties properties) - throws NoSuchAlgorithmException, KeyManagementException { - final ConnectionFactory factory = new ConnectionFactory(); - PropertyMapper map = PropertyMapper.get(); - map.from(properties::determineHost).whenNonNull().to(factory::setHost); - map.from(properties::determinePort).to(factory::setPort); - map.from(properties::determineUsername).whenNonNull().to(factory::setUsername); - map.from(properties::determinePassword).whenNonNull().to(factory::setPassword); - map.from(properties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost); - factory.useNio(); - if (properties.getSsl() != null && properties.getSsl().isEnabled()) { - factory.useSslProtocol(); - } - return () -> factory; - } - - @Bean - @ConditionalOnMissingBean - public ObjectMapperSupplier objectMapperSupplier() { - return new DefaultObjectMapperSupplier(); - } - - @Bean - @ConditionalOnMissingBean - public MessageConverter messageConverter(ObjectMapperSupplier objectMapperSupplier) { - return new JacksonMessageConverter(objectMapperSupplier.get()); - } - - @Bean - @ConditionalOnMissingBean - public DiscardNotifier rabbitDiscardNotifier(ObjectMapperSupplier objectMapperSupplier, AsyncProps asyncProps, - ReactiveMessageSender sender, BrokerConfigProps props) { - return new RabbitDiscardNotifier(domainEventBus(sender, props, asyncProps.getCreateTopology()), objectMapperSupplier.get()); - } - - @Bean - @ConditionalOnMissingBean - public CustomReporter reactiveCommonsCustomErrorReporter() { - return new CustomReporter() { - @Override - public Mono reportError(Throwable ex, Message rawMessage, Command message, boolean redelivered) { - return Mono.empty(); - } - - @Override - public Mono reportError(Throwable ex, Message rawMessage, DomainEvent message, boolean redelivered) { - return Mono.empty(); - } - - @Override - public Mono reportError(Throwable ex, Message rawMessage, AsyncQuery message, boolean redelivered) { - return Mono.empty(); - } - }; - } - - private DomainEventBus domainEventBus(ReactiveMessageSender sender, BrokerConfigProps props, boolean createExchange) { - final String exchangeName = props.getDomainEventsExchangeName(); - if (createExchange) { - sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("topic")).subscribe(); - } - return new RabbitDomainEventBus(sender, exchangeName); - } - - Mono createConnectionMono(ConnectionFactory factory, String connectionPrefix, String connectionType) { - return Mono.fromCallable(() -> factory.newConnection(connectionPrefix + " " + connectionType)) - .doOnError(err -> - log.log(Level.SEVERE, "Error creating connection to RabbitMq Broker. Starting retry process...", err) - ) - .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(300)) - .maxBackoff(Duration.ofMillis(3000))) - .cache(); - } - - @Bean - public HandlerResolver resolver(ApplicationContext context, DefaultCommandHandler defaultCommandHandler) { - final Map registries = context.getBeansOfType(HandlerRegistry.class); - - final ConcurrentMap> queryHandlers = registries - .values().stream() - .flatMap(r -> r.getHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> eventsToBind = registries - .values().stream() - .flatMap(r -> r.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - // event handlers and dynamic handlers - final ConcurrentMap> eventHandlers = registries - .values().stream() - .flatMap(r -> Stream.concat(r.getDomainEventListeners().get(DEFAULT_DOMAIN).stream(), r.getDynamicEventHandlers().stream())) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> commandHandlers = registries - .values().stream() - .flatMap(r -> r.getCommandHandlers().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - final ConcurrentMap> eventNotificationListener = registries - .values() - .stream() - .flatMap(r -> r.getEventNotificationListener().stream()) - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), - ConcurrentHashMap::putAll); - - return new HandlerResolver(queryHandlers, eventHandlers, eventsToBind, eventNotificationListener, commandHandlers) { - @Override - @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler<>("", defaultCommandHandler, Object.class); - } - }; - } - - @Bean - public DynamicRegistry dynamicRegistry(HandlerResolver resolver, ReactiveMessageListener listener, IBrokerConfigProps props) { - return new DynamicRegistryImp(resolver, listener.getTopologyCreator(), props); - } - - @Bean - @ConditionalOnMissingBean - public DefaultQueryHandler defaultHandler() { - return (DefaultQueryHandler) command -> - Mono.error(new RuntimeException("No Handler Registered")); - } - - @Bean - @ConditionalOnMissingBean - public DefaultCommandHandler defaultCommandHandler() { - return message -> Mono.error(new RuntimeException("No Handler Registered")); - } - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java deleted file mode 100644 index 1ed9934e..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java +++ /dev/null @@ -1,967 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.convert.DurationUnit; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -@ConfigurationProperties(prefix = "spring.rabbitmq") -public class RabbitProperties { - - /** - * RabbitMQ host. - */ - private String host = "localhost"; - - /** - * RabbitMQ port. - */ - private int port = 5672; - - /** - * Login user to authenticate to the communications. - */ - private String username = "guest"; - - /** - * Login to authenticate against the communications. - */ - private String password = "guest"; - - /** - * SSL configuration. - */ - private final Ssl ssl = new Ssl(); - - /** - * Virtual host to use when connecting to the communications. - */ - private String virtualHost; - - /** - * Comma-separated list of addresses to which the client should connect. - */ - private String addresses; - - /** - * Requested heartbeat timeout; zero for none. If a duration suffix is not specified, - * seconds will be used. - */ - @DurationUnit(ChronoUnit.SECONDS) - private Duration requestedHeartbeat; - - /** - * Whether to enable publisher confirms. - */ - private boolean publisherConfirms; - - /** - * Whether to enable publisher returns. - */ - private boolean publisherReturns; - - /** - * Connection timeout. Set it to zero to wait forever. - */ - private Duration connectionTimeout; - - /** - * Cache configuration. - */ - private final Cache cache = new Cache(); - - /** - * Listener container configuration. - */ - private final Listener listener = new Listener(); - - private final Template template = new Template(); - - private List
parsedAddresses; - - public String getHost() { - return this.host; - } - - /** - * Returns the host from the first address, or the configured host if no addresses - * have been set. - * @return the host - * @see #setAddresses(String) - * @see #getHost() - */ - public String determineHost() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return getHost(); - } - return this.parsedAddresses.get(0).host; - } - - public void setHost(String host) { - this.host = host; - } - - public int getPort() { - return this.port; - } - - /** - * Returns the port from the first address, or the configured port if no addresses - * have been set. - * @return the port - * @see #setAddresses(String) - * @see #getPort() - */ - public int determinePort() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return getPort(); - } - Address address = this.parsedAddresses.get(0); - return address.port; - } - - public void setPort(int port) { - this.port = port; - } - - public String getAddresses() { - return this.addresses; - } - - /** - * Returns the comma-separated addresses or a single address ({@code host:port}) - * created from the configured host and port if no addresses have been set. - * @return the addresses - */ - public String determineAddresses() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return this.host + ":" + this.port; - } - List addressStrings = new ArrayList<>(); - for (Address parsedAddress : this.parsedAddresses) { - addressStrings.add(parsedAddress.host + ":" + parsedAddress.port); - } - return StringUtils.collectionToCommaDelimitedString(addressStrings); - } - - public void setAddresses(String addresses) { - this.addresses = addresses; - this.parsedAddresses = parseAddresses(addresses); - } - - private List
parseAddresses(String addresses) { - List
parsedAddresses = new ArrayList<>(); - for (String address : StringUtils.commaDelimitedListToStringArray(addresses)) { - parsedAddresses.add(new Address(address)); - } - return parsedAddresses; - } - - public String getUsername() { - return this.username; - } - - /** - * If addresses have been set and the first address has a username it is returned. - * Otherwise returns the result of calling {@code getUsername()}. - * @return the username - * @see #setAddresses(String) - * @see #getUsername() - */ - public String determineUsername() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return this.username; - } - Address address = this.parsedAddresses.get(0); - return (address.username != null) ? address.username : this.username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return this.password; - } - - /** - * If addresses have been set and the first address has a password it is returned. - * Otherwise returns the result of calling {@code getPassword()}. - * @return the password or {@code null} - * @see #setAddresses(String) - * @see #getPassword() - */ - public String determinePassword() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return getPassword(); - } - Address address = this.parsedAddresses.get(0); - return (address.password != null) ? address.password : getPassword(); - } - - public void setPassword(String password) { - this.password = password; - } - - public Ssl getSsl() { - return this.ssl; - } - - public String getVirtualHost() { - return this.virtualHost; - } - - /** - * If addresses have been set and the first address has a virtual host it is returned. - * Otherwise returns the result of calling {@code getVirtualHost()}. - * @return the virtual host or {@code null} - * @see #setAddresses(String) - * @see #getVirtualHost() - */ - public String determineVirtualHost() { - if (CollectionUtils.isEmpty(this.parsedAddresses)) { - return getVirtualHost(); - } - Address address = this.parsedAddresses.get(0); - return (address.virtualHost != null) ? address.virtualHost : getVirtualHost(); - } - - public void setVirtualHost(String virtualHost) { - this.virtualHost = "".equals(virtualHost) ? "/" : virtualHost; - } - - public Duration getRequestedHeartbeat() { - return this.requestedHeartbeat; - } - - public void setRequestedHeartbeat(Duration requestedHeartbeat) { - this.requestedHeartbeat = requestedHeartbeat; - } - - public boolean isPublisherConfirms() { - return this.publisherConfirms; - } - - public void setPublisherConfirms(boolean publisherConfirms) { - this.publisherConfirms = publisherConfirms; - } - - public boolean isPublisherReturns() { - return this.publisherReturns; - } - - public void setPublisherReturns(boolean publisherReturns) { - this.publisherReturns = publisherReturns; - } - - public Duration getConnectionTimeout() { - return this.connectionTimeout; - } - - public void setConnectionTimeout(Duration connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - public Cache getCache() { - return this.cache; - } - - public Listener getListener() { - return this.listener; - } - - public Template getTemplate() { - return this.template; - } - - public static class Ssl { - - /** - * Whether to enable SSL support. - */ - private boolean enabled; - - /** - * Path to the key store that holds the SSL certificate. - */ - private String keyStore; - - /** - * Key store type. - */ - private String keyStoreType = "PKCS12"; - - /** - * Password used to access the key store. - */ - private String keyStorePassword; - - /** - * Trust store that holds SSL certificates. - */ - private String trustStore; - - /** - * Trust store type. - */ - private String trustStoreType = "JKS"; - - /** - * Password used to access the trust store. - */ - private String trustStorePassword; - - /** - * SSL algorithm to use. By default, configured by the Rabbit client library. - */ - private String algorithm; - - /** - * Whether to enable server side certificate validation. - */ - private boolean validateServerCertificate = true; - - /** - * Whether to enable hostname verification. - */ - private boolean verifyHostname = true; - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getKeyStore() { - return this.keyStore; - } - - public void setKeyStore(String keyStore) { - this.keyStore = keyStore; - } - - public String getKeyStoreType() { - return this.keyStoreType; - } - - public void setKeyStoreType(String keyStoreType) { - this.keyStoreType = keyStoreType; - } - - public String getKeyStorePassword() { - return this.keyStorePassword; - } - - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = keyStorePassword; - } - - public String getTrustStore() { - return this.trustStore; - } - - public void setTrustStore(String trustStore) { - this.trustStore = trustStore; - } - - public String getTrustStoreType() { - return this.trustStoreType; - } - - public void setTrustStoreType(String trustStoreType) { - this.trustStoreType = trustStoreType; - } - - public String getTrustStorePassword() { - return this.trustStorePassword; - } - - public void setTrustStorePassword(String trustStorePassword) { - this.trustStorePassword = trustStorePassword; - } - - public String getAlgorithm() { - return this.algorithm; - } - - public void setAlgorithm(String sslAlgorithm) { - this.algorithm = sslAlgorithm; - } - - public boolean isValidateServerCertificate() { - return this.validateServerCertificate; - } - - public void setValidateServerCertificate(boolean validateServerCertificate) { - this.validateServerCertificate = validateServerCertificate; - } - - public boolean getVerifyHostname() { - return this.verifyHostname; - } - - public void setVerifyHostname(boolean verifyHostname) { - this.verifyHostname = verifyHostname; - } - - } - - public static class Cache { - - private final Cache.Channel channel = new Cache.Channel(); - - private final Cache.Connection connection = new Cache.Connection(); - - public Cache.Channel getChannel() { - return this.channel; - } - - public Cache.Connection getConnection() { - return this.connection; - } - - public static class Channel { - - /** - * Number of channels to retain in the cache. When "check-timeout" > 0, max - * channels per connection. - */ - private Integer size; - - /** - * Duration to wait to obtain a channel if the cache size has been reached. If - * 0, always create a new channel. - */ - private Duration checkoutTimeout; - - public Integer getSize() { - return this.size; - } - - public void setSize(Integer size) { - this.size = size; - } - - public Duration getCheckoutTimeout() { - return this.checkoutTimeout; - } - - public void setCheckoutTimeout(Duration checkoutTimeout) { - this.checkoutTimeout = checkoutTimeout; - } - - } - - public static class Connection { - - /** - * Connection factory cache mode. - */ - private String mode = "CHANNEL"; - - /** - * Number of connections to cache. Only applies when mode is CONNECTION. - */ - private Integer size; - - public String getMode() { - return this.mode; - } - - public void setMode(String mode) { - this.mode = mode; - } - - public Integer getSize() { - return this.size; - } - - public void setSize(Integer size) { - this.size = size; - } - - } - - } - - public enum ContainerType { - - /** - * Container where the RabbitMQ consumer dispatches messages to an invoker thread. - */ - SIMPLE, - - /** - * Container where the listener is invoked directly on the RabbitMQ consumer - * thread. - */ - DIRECT - - } - - public static class Listener { - - /** - * Listener container type. - */ - private ContainerType type = ContainerType.SIMPLE; - - private final SimpleContainer simple = new SimpleContainer(); - - private final DirectContainer direct = new DirectContainer(); - - public ContainerType getType() { - return this.type; - } - - public void setType(ContainerType containerType) { - this.type = containerType; - } - - public SimpleContainer getSimple() { - return this.simple; - } - - public DirectContainer getDirect() { - return this.direct; - } - - } - - public abstract static class AmqpContainer { - - /** - * Whether to start the container automatically on startup. - */ - private boolean autoStartup = true; - - /** - * Acknowledge mode of container. - */ - private Integer acknowledgeMode; - - /** - * Maximum number of unacknowledged messages that can be outstanding at each - * consumer. - */ - private Integer prefetch; - - /** - * Whether rejected deliveries are re-queued by default. - */ - private Boolean defaultRequeueRejected; - - /** - * How often idle container events should be published. - */ - private Duration idleEventInterval; - - /** - * Optional properties for a retry interceptor. - */ - private final ListenerRetry retry = new ListenerRetry(); - - public boolean isAutoStartup() { - return this.autoStartup; - } - - public void setAutoStartup(boolean autoStartup) { - this.autoStartup = autoStartup; - } - - public Integer getAcknowledgeMode() { - return this.acknowledgeMode; - } - - public void setAcknowledgeMode(Integer acknowledgeMode) { - this.acknowledgeMode = acknowledgeMode; - } - - public Integer getPrefetch() { - return this.prefetch; - } - - public void setPrefetch(Integer prefetch) { - this.prefetch = prefetch; - } - - public Boolean getDefaultRequeueRejected() { - return this.defaultRequeueRejected; - } - - public void setDefaultRequeueRejected(Boolean defaultRequeueRejected) { - this.defaultRequeueRejected = defaultRequeueRejected; - } - - public Duration getIdleEventInterval() { - return this.idleEventInterval; - } - - public void setIdleEventInterval(Duration idleEventInterval) { - this.idleEventInterval = idleEventInterval; - } - - public abstract boolean isMissingQueuesFatal(); - - public ListenerRetry getRetry() { - return this.retry; - } - - } - - /** - * Configuration properties for {@code SimpleMessageListenerContainer}. - */ - public static class SimpleContainer extends AmqpContainer { - - /** - * Minimum number of listener invoker threads. - */ - private Integer concurrency; - - /** - * Maximum number of listener invoker threads. - */ - private Integer maxConcurrency; - - /** - * Number of messages to be processed between acks when the acknowledge mode is - * AUTO. If larger than prefetch, prefetch will be increased to this value. - */ - private Integer transactionSize; - - /** - * Whether to fail if the queues declared by the container are not available on - * the communications and/or whether to stop the container if one or more queues are - * deleted at runtime. - */ - private boolean missingQueuesFatal = true; - - public Integer getConcurrency() { - return this.concurrency; - } - - public void setConcurrency(Integer concurrency) { - this.concurrency = concurrency; - } - - public Integer getMaxConcurrency() { - return this.maxConcurrency; - } - - public void setMaxConcurrency(Integer maxConcurrency) { - this.maxConcurrency = maxConcurrency; - } - - public Integer getTransactionSize() { - return this.transactionSize; - } - - public void setTransactionSize(Integer transactionSize) { - this.transactionSize = transactionSize; - } - - @Override - public boolean isMissingQueuesFatal() { - return this.missingQueuesFatal; - } - - public void setMissingQueuesFatal(boolean missingQueuesFatal) { - this.missingQueuesFatal = missingQueuesFatal; - } - - } - - /** - * Configuration properties for {@code DirectMessageListenerContainer}. - */ - public static class DirectContainer extends AmqpContainer { - - /** - * Number of consumers per queue. - */ - private Integer consumersPerQueue; - - /** - * Whether to fail if the queues declared by the container are not available on - * the communications. - */ - private boolean missingQueuesFatal = false; - - public Integer getConsumersPerQueue() { - return this.consumersPerQueue; - } - - public void setConsumersPerQueue(Integer consumersPerQueue) { - this.consumersPerQueue = consumersPerQueue; - } - - @Override - public boolean isMissingQueuesFatal() { - return this.missingQueuesFatal; - } - - public void setMissingQueuesFatal(boolean missingQueuesFatal) { - this.missingQueuesFatal = missingQueuesFatal; - } - - } - - public static class Template { - - private final Retry retry = new Retry(); - - /** - * Whether to enable mandatory messages. - */ - private Boolean mandatory; - - /** - * Timeout for `receive()` operations. - */ - private Duration receiveTimeout; - - /** - * Timeout for `sendAndReceive()` operations. - */ - private Duration replyTimeout; - - /** - * Name of the default exchange to use for send operations. - */ - private String exchange = ""; - - /** - * Value of a default routing key to use for send operations. - */ - private String routingKey = ""; - - /** - * Name of the default queue to receive messages from when none is specified - * explicitly. - */ - private String queue; - - public Retry getRetry() { - return this.retry; - } - - public Boolean getMandatory() { - return this.mandatory; - } - - public void setMandatory(Boolean mandatory) { - this.mandatory = mandatory; - } - - public Duration getReceiveTimeout() { - return this.receiveTimeout; - } - - public void setReceiveTimeout(Duration receiveTimeout) { - this.receiveTimeout = receiveTimeout; - } - - public Duration getReplyTimeout() { - return this.replyTimeout; - } - - public void setReplyTimeout(Duration replyTimeout) { - this.replyTimeout = replyTimeout; - } - - public String getExchange() { - return this.exchange; - } - - public void setExchange(String exchange) { - this.exchange = exchange; - } - - public String getRoutingKey() { - return this.routingKey; - } - - public void setRoutingKey(String routingKey) { - this.routingKey = routingKey; - } - - public String getQueue() { - return this.queue; - } - - public void setQueue(String queue) { - this.queue = queue; - } - - } - - public static class Retry { - - /** - * Whether publishing retries are enabled. - */ - private boolean enabled; - - /** - * Maximum number of attempts to deliver a message. - */ - private int maxAttempts = 3; - - /** - * Duration between the first and second attempt to deliver a message. - */ - private Duration initialInterval = Duration.ofMillis(1000); - - /** - * Multiplier to apply to the previous retry interval. - */ - private double multiplier = 1.0; - - /** - * Maximum duration between attempts. - */ - private Duration maxInterval = Duration.ofMillis(10000); - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public int getMaxAttempts() { - return this.maxAttempts; - } - - public void setMaxAttempts(int maxAttempts) { - this.maxAttempts = maxAttempts; - } - - public Duration getInitialInterval() { - return this.initialInterval; - } - - public void setInitialInterval(Duration initialInterval) { - this.initialInterval = initialInterval; - } - - public double getMultiplier() { - return this.multiplier; - } - - public void setMultiplier(double multiplier) { - this.multiplier = multiplier; - } - - public Duration getMaxInterval() { - return this.maxInterval; - } - - public void setMaxInterval(Duration maxInterval) { - this.maxInterval = maxInterval; - } - - } - - public static class ListenerRetry extends Retry { - - /** - * Whether retries are stateless or stateful. - */ - private boolean stateless = true; - - public boolean isStateless() { - return this.stateless; - } - - public void setStateless(boolean stateless) { - this.stateless = stateless; - } - - } - - private static final class Address { - - private static final String PREFIX_AMQP = "amqp://"; - - private static final int DEFAULT_PORT = 5672; - - private String host; - - private int port; - - private String username; - - private String password; - - private String virtualHost; - - private Address(String input) { - input = input.trim(); - input = trimPrefix(input); - input = parseUsernameAndPassword(input); - input = parseVirtualHost(input); - parseHostAndPort(input); - } - - private String trimPrefix(String input) { - if (input.startsWith(PREFIX_AMQP)) { - input = input.substring(PREFIX_AMQP.length()); - } - return input; - } - - private String parseUsernameAndPassword(String input) { - if (input.contains("@")) { - String[] split = StringUtils.split(input, "@"); - if (split == null) return input; - String creds = split[0]; - input = split[1]; - split = StringUtils.split(creds, ":"); - if (split != null) { - this.username = split[0]; - if (split.length > 1) { - this.password = split[1]; - } - } - } - return input; - } - - private String parseVirtualHost(String input) { - int hostIndex = input.indexOf('/'); - if (hostIndex >= 0) { - this.virtualHost = input.substring(hostIndex + 1); - if (this.virtualHost.isEmpty()) { - this.virtualHost = "/"; - } - input = input.substring(0, hostIndex); - } - return input; - } - - private void parseHostAndPort(String input) { - int portIndex = input.indexOf(':'); - if (portIndex == -1) { - this.host = input; - this.port = DEFAULT_PORT; - } - else { - this.host = input.substring(0, portIndex); - this.port = Integer.valueOf(input.substring(portIndex + 1)); - } - } - - } - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java deleted file mode 100644 index e7f22098..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - - -@Getter -@Setter -@ConfigurationProperties(prefix = "app.async") -public class AsyncProps { - - @NestedConfigurationProperty - private FluxProps flux = new FluxProps(); - - @NestedConfigurationProperty - private DomainProps domain = new DomainProps(); - - @NestedConfigurationProperty - private DirectProps direct = new DirectProps(); - - @NestedConfigurationProperty - private GlobalProps global = new GlobalProps(); - - private boolean listenReplies = true; - - /** - * -1 will be considered default value. - * When withDLQRetry is true, it will be retried 10 times. - * When withDLQRetry is false, it will be retried indefinitely. - */ - private Integer maxRetries = -1; - - private Integer prefetchCount = 250; - - private Integer retryDelay = 1000; - - private Boolean withDLQRetry = false; - private Boolean createTopology = true; // auto delete queues will always be created and bound - private Boolean delayedCommands = false; -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java deleted file mode 100644 index 77bcaff9..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.Getter; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.springframework.beans.factory.annotation.Value; - -import java.util.concurrent.atomic.AtomicReference; - -import static org.reactivecommons.async.commons.utils.NameGenerator.fromNameWithSuffix; -import static org.reactivecommons.async.commons.utils.NameGenerator.generateNameFrom; - - -@Getter -public class BrokerConfigProps implements IBrokerConfigProps { - private final String appName; - private final AsyncProps asyncProps; - private final AtomicReference replyQueueName = new AtomicReference<>(); - private final AtomicReference notificationsQueueName = new AtomicReference<>(); - - public BrokerConfigProps(@Value("${spring.application.name}") String appName, AsyncProps asyncProps) { - this.appName = appName; - this.asyncProps = asyncProps; - } - - @Override - public String getEventsQueue() { - return fromNameWithSuffix(getAppName(), asyncProps.getDomain().getEvents().getEventsSuffix()); - } - - @Override - public String getNotificationsQueue() { - return resolveTemporaryQueue(notificationsQueueName, asyncProps.getDomain().getEvents().getNotificationSuffix()); - } - - @Override - public String getQueriesQueue() { - return fromNameWithSuffix(getAppName(), asyncProps.getDirect().getQuerySuffix()); - } - - @Override - public String getCommandsQueue() { - return fromNameWithSuffix(getAppName(), asyncProps.getDirect().getCommandSuffix()); - } - - @Override - public String getReplyQueue() { - return resolveTemporaryQueue(replyQueueName, asyncProps.getGlobal().getRepliesSuffix()); - } - - @Override - public String getDomainEventsExchangeName() { - return asyncProps.getDomain().getEvents().getExchange(); - } - - @Override - public String getDirectMessagesExchangeName() { - return asyncProps.getDirect().getExchange(); - } - - @Override - public String getGlobalReplyExchangeName() { - return asyncProps.getGlobal().getExchange(); - } - - private String resolveTemporaryQueue(AtomicReference property, String suffix) { - final String name = property.get(); - if (name == null) { - final String replyName = generateNameFrom(getAppName(), suffix); - if (property.compareAndSet(null, replyName)) { - return replyName; - } else { - return property.get(); - } - } - return name; - } -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java deleted file mode 100644 index 011b319a..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.Optional; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class DirectProps { - - @Builder.Default - private String exchange = "directMessages"; - - @Builder.Default - private String querySuffix = "query"; - - @Builder.Default - private String commandSuffix = ""; - - @Builder.Default - private Optional maxLengthBytes = Optional.empty(); - - @Builder.Default - private boolean discardTimeoutQueries = false; - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java deleted file mode 100644 index 72ed1f23..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.Optional; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class EventsProps { - - @Builder.Default - private String exchange = "domainEvents"; - @Builder.Default - private String eventsSuffix = "subsEvents"; - @Builder.Default - private String notificationSuffix = "notification"; - - @Builder.Default - private Optional maxLengthBytes = Optional.empty(); - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java deleted file mode 100644 index 33162aa2..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class FluxProps { - - @Builder.Default - private Integer maxConcurrency = 250; - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java deleted file mode 100644 index 33c2451d..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.reactivecommons.async.rabbit.config.props; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.util.Optional; - -@Getter -@Setter -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class GlobalProps { - - @Builder.Default - private String exchange = "globalReply"; - - @Builder.Default - private String repliesSuffix = "replies"; - - @Builder.Default - private Optional maxLengthBytes = Optional.empty(); - -} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java b/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java deleted file mode 100644 index ddb38a8a..00000000 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.reactivecommons.async.rabbit.health; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; -import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; -import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; -import org.springframework.boot.actuate.health.Health; -import reactor.core.publisher.Mono; - -@RequiredArgsConstructor -public class RabbitReactiveHealthIndicator extends AbstractReactiveHealthIndicator { - private static final String VERSION = "version"; - private final ConnectionFactoryProvider provider; - - @Override - protected Mono doHealthCheck(Health.Builder builder) { - return Mono.defer(this::getVersion) - .map(version -> builder.up().withDetail(VERSION, version).build()); - } - - private Mono getVersion() { - return Mono.just(provider) - .map(ConnectionFactoryProvider::getConnectionFactory) - .map(this::getRawVersion); - } - - @SneakyThrows - private String getRawVersion(ConnectionFactory factory) { - try (Connection connection = factory.newConnection()) { - return connection.getServerProperties().get(VERSION).toString(); - } - } -} diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java deleted file mode 100644 index 9ec575ab..00000000 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/CommandListenersConfigTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationCommandListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ConsumeOptions; -import reactor.rabbitmq.ExchangeSpecification; -import reactor.rabbitmq.Receiver; - -import java.lang.reflect.Field; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -class CommandListenersConfigTest { - - - private final AsyncProps props = new AsyncProps(); - private CommandListenersConfig config = new CommandListenersConfig(props); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final DiscardNotifier discardNotifier = mock(DiscardNotifier.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - private final IBrokerConfigProps brokerConfigProps = new BrokerConfigProps("appName", props); - - @BeforeEach - public void init() throws NoSuchFieldException, IllegalAccessException { - final Field appName = CommandListenersConfig.class.getDeclaredField("appName"); - appName.setAccessible(true); - appName.set(config, "queue"); - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - } - - @Test - void applicationCommandListener() { - final ApplicationCommandListener commandListener = config.applicationCommandListener( - listener, - handlerResolver, - messageConverter, - discardNotifier, - brokerConfigProps, - customReporter - ); - Assertions.assertThat(commandListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java deleted file mode 100644 index ff39891f..00000000 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/EventListenersConfigTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationEventListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.*; - -import java.util.Collections; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class EventListenersConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final EventListenersConfig config = new EventListenersConfig(props); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final DiscardNotifier discardNotifier = mock(DiscardNotifier.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - private final IBrokerConfigProps brokerConfigProps = new BrokerConfigProps("appName", props); - - @BeforeEach - public void init() { - when(handlerResolver.getEventListeners()).thenReturn(Collections.emptyList()); - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declare(any(QueueSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareDLQ(any(String.class), any(String.class), any(Integer.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - } - - @Test - void eventListener() { - final ApplicationEventListener eventListener = config.eventListener( - handlerResolver, - messageConverter, - listener, - discardNotifier, - brokerConfigProps, - customReporter - ); - - Assertions.assertThat(eventListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfigTest.java deleted file mode 100644 index a35c7b5a..00000000 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/NotificacionListenersConfigTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationNotificationListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ConsumeOptions; -import reactor.rabbitmq.ExchangeSpecification; -import reactor.rabbitmq.QueueSpecification; -import reactor.rabbitmq.Receiver; - -import java.util.Collections; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class NotificacionListenersConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final NotificacionListenersConfig config = new NotificacionListenersConfig(props); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final DiscardNotifier discardNotifier = mock(DiscardNotifier.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - - private final IBrokerConfigProps brokerConfigProps = new BrokerConfigProps("appName", props); - - @BeforeEach - public void init() { - when(handlerResolver.getEventListeners()).thenReturn(Collections.emptyList()); - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declare(any(QueueSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareDLQ(any(String.class), any(String.class), any(Integer.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - } - - @Test - void eventNotificationListener() { - final ApplicationNotificationListener applicationEventListener = config. - eventNotificationListener(handlerResolver, messageConverter, listener, discardNotifier, brokerConfigProps, customReporter); - Assertions.assertThat(applicationEventListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java deleted file mode 100644 index 61c0a1d4..00000000 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/QueryListenerConfigTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.AMQP; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.reactivecommons.async.commons.DiscardNotifier; -import org.reactivecommons.async.commons.config.IBrokerConfigProps; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; -import org.reactivecommons.async.rabbit.listeners.ApplicationQueryListener; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ConsumeOptions; -import reactor.rabbitmq.ExchangeSpecification; -import reactor.rabbitmq.QueueSpecification; -import reactor.rabbitmq.Receiver; - -import java.util.Collections; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class QueryListenerConfigTest { - - private final AsyncProps props = new AsyncProps(); - private final QueryListenerConfig config = new QueryListenerConfig(props); - private final ReactiveMessageListener listener = mock(ReactiveMessageListener.class); - private final TopologyCreator creator = mock(TopologyCreator.class); - private final HandlerResolver handlerResolver = mock(HandlerResolver.class); - private final MessageConverter messageConverter = mock(MessageConverter.class); - private final DiscardNotifier discardNotifier = mock(DiscardNotifier.class); - private final CustomReporter customReporter = mock(CustomReporter.class); - private final Receiver receiver = mock(Receiver.class); - private final ReactiveMessageSender sender = mock(ReactiveMessageSender.class); - private final IBrokerConfigProps brokerConfigProps = new BrokerConfigProps("appName", props); - - @BeforeEach - public void init() { - when(handlerResolver.getEventListeners()).thenReturn(Collections.emptyList()); - when(creator.bind(any(BindingSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); - when(creator.declare(any(ExchangeSpecification.class))).thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); - when(creator.declare(any(QueueSpecification.class))).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareDLQ(any(String.class), any(String.class), any(Integer.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(creator.declareQueue(any(String.class), any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); - when(listener.getTopologyCreator()).thenReturn(creator); - when(receiver.consumeManualAck(any(String.class), any(ConsumeOptions.class))).thenReturn(Flux.never()); - when(listener.getReceiver()).thenReturn(receiver); - when(listener.getMaxConcurrency()).thenReturn(20); - } - - @Test - void queryListener() { - final ApplicationQueryListener queryListener = config.queryListener(messageConverter, handlerResolver, sender, - listener, discardNotifier, brokerConfigProps, customReporter); - Assertions.assertThat(queryListener).isNotNull(); - } -} diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java deleted file mode 100644 index 33ca98f9..00000000 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/config/RabbitMqConfigTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.reactivecommons.async.rabbit.config; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import org.junit.jupiter.api.Test; -import org.reactivecommons.api.domain.Command; -import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.async.api.AsyncQuery; -import org.reactivecommons.async.commons.communications.Message; -import org.reactivecommons.async.commons.ext.CustomReporter; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class RabbitMqConfigTest { - - RabbitMqConfig config = new RabbitMqConfig(null); - - @Test - void retryInitialConnection() throws IOException, TimeoutException { - final String connectionType = "sender"; - final String appName = "appName"; - final String connectionName = "appName sender"; - - final AtomicInteger count = new AtomicInteger(); - final Connection connection = mock(Connection.class); - ConnectionFactory factory = mock(ConnectionFactory.class); - when(factory.newConnection(connectionName)).thenAnswer(invocation -> { - if(count.incrementAndGet() == 10){ - return connection; - } - throw new RuntimeException(); - }); - StepVerifier.withVirtualTime(() -> config.createConnectionMono(factory, appName, connectionType)) - .thenAwait(Duration.ofMinutes(2)) - .expectNext(connection).verifyComplete(); - } - - @Test - void shouldCreateDefaultErrorReporter() { - final CustomReporter errorReporter = config.reactiveCommonsCustomErrorReporter(); - assertThat(errorReporter.reportError(mock(Throwable.class), mock(Message.class), mock(Command.class), true)).isNotNull(); - assertThat(errorReporter.reportError(mock(Throwable.class), mock(Message.class), mock(DomainEvent.class), true)).isNotNull(); - assertThat(errorReporter.reportError(mock(Throwable.class), mock(Message.class), mock(AsyncQuery.class), true)).isNotNull(); - } - - @Test - void shouldGenerateDefaultReeporter() { - final CustomReporter customReporter = config.reactiveCommonsCustomErrorReporter(); - final Mono r1 = customReporter.reportError(mock(Throwable.class), mock(Message.class), mock(Command.class), true); - final Mono r2 = customReporter.reportError(mock(Throwable.class), mock(Message.class), mock(DomainEvent.class), true); - final Mono r3 = customReporter.reportError(mock(Throwable.class), mock(Message.class), mock(AsyncQuery.class), true); - - assertThat(r1).isNotNull(); - assertThat(r2).isNotNull(); - assertThat(r3).isNotNull(); - - } -} diff --git a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java b/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java deleted file mode 100644 index 2d173758..00000000 --- a/async/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.reactivecommons.async.rabbit.health; - -import com.rabbitmq.client.Connection; -import com.rabbitmq.client.ConnectionFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Health.Builder; -import org.springframework.boot.actuate.health.Status; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import java.io.IOException; -import java.util.Map; -import java.util.TreeMap; -import java.util.concurrent.TimeoutException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class RabbitReactiveHealthIndicatorTest { - @Mock - private ConnectionFactoryProvider provider; - @Mock - private ConnectionFactory factory; - @Mock - private Connection connection; - @InjectMocks - private RabbitReactiveHealthIndicator indicator; - - @BeforeEach - void setup() { - when(provider.getConnectionFactory()).thenReturn(factory); - } - - @Test - void shouldBeUp() throws IOException, TimeoutException { - // Arrange - Map properties = new TreeMap<>(); - properties.put("version", "1.2.3"); - when(factory.newConnection()).thenReturn(connection); - when(connection.getServerProperties()).thenReturn(properties); - // Act - Mono result = indicator.doHealthCheck(new Builder()); - // Assert - StepVerifier.create(result) - .assertNext(health -> { - assertEquals("1.2.3", health.getDetails().get("version")); - assertEquals(Status.UP, health.getStatus()); - }) - .verifyComplete(); - } - - @Test - void shouldBeDown() throws IOException, TimeoutException { - // Arrange - when(factory.newConnection()).thenThrow(new TimeoutException("Connection timeout")); - // Act - Mono result = indicator.doHealthCheck(new Builder()); - // Assert - StepVerifier.create(result) - .expectError(TimeoutException.class) - .verify(); - } -} diff --git a/async/async-rabbit-starter/src/test/resources/application.properties b/async/async-rabbit-starter/src/test/resources/application.properties deleted file mode 100644 index 3d8d7db0..00000000 --- a/async/async-rabbit-starter/src/test/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=test-app \ No newline at end of file diff --git a/async/async-rabbit/async-rabbit.gradle b/async/async-rabbit/async-rabbit.gradle index 1d9df2b6..d5da11f9 100644 --- a/async/async-rabbit/async-rabbit.gradle +++ b/async/async-rabbit/async-rabbit.gradle @@ -10,9 +10,11 @@ dependencies { api 'io.projectreactor:reactor-core' api 'io.projectreactor:reactor-core-micrometer' - api 'io.projectreactor.rabbitmq:reactor-rabbitmq' + api 'io.projectreactor.rabbitmq:reactor-rabbitmq:1.5.6' api 'com.rabbitmq:amqp-client' api 'com.fasterxml.jackson.core:jackson-databind' - testImplementation 'io.projectreactor:reactor-test' + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' + + testImplementation 'io.projectreactor:reactor-test' } \ No newline at end of file diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java index ab1e93d7..fcca072a 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/DynamicRegistryImp.java @@ -3,11 +3,12 @@ import lombok.RequiredArgsConstructor; import org.reactivecommons.async.api.DynamicRegistry; -import org.reactivecommons.async.api.handlers.EventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.config.IBrokerConfigProps; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import reactor.core.publisher.Mono; @@ -22,7 +23,7 @@ public class DynamicRegistryImp implements DynamicRegistry { @Override - public Mono listenEvent(String eventName, EventHandler fn, Class eventClass) { + public Mono listenEvent(String eventName, DomainEventHandler fn, Class eventClass) { resolver.addEventListener(new RegisteredEventListener<>(eventName, fn, eventClass)); return topologyCreator.bind(buildBindingSpecification(eventName)) @@ -31,7 +32,9 @@ public Mono listenEvent(String eventName, EventHandler fn, Class @Override public void serveQuery(String resource, QueryHandler handler, Class queryClass) { - resolver.addQueryHandler(new RegisteredQueryHandler<>(resource, (ignored, message) -> handler.handle(message), queryClass)); + resolver.addQueryHandler(new RegisteredQueryHandler<>(resource, + (ignored, message) -> handler.handle(message), queryClass) + ); } @Override diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/HandlerResolver.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/HandlerResolver.java deleted file mode 100644 index 009e18a8..00000000 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/HandlerResolver.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.reactivecommons.async.rabbit; - -import lombok.RequiredArgsConstructor; -import lombok.extern.java.Log; -import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; -import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; -import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; -import org.reactivecommons.async.commons.utils.matcher.KeyMatcher; -import org.reactivecommons.async.commons.utils.matcher.Matcher; - -import java.util.Collection; -import java.util.Map; -import java.util.function.Function; - -@Log -@RequiredArgsConstructor -public class HandlerResolver { - - private final Map> queryHandlers; - private final Map> eventListeners; - private final Map> eventsToBind; - private final Map> eventNotificationListeners; - private final Map> commandHandlers; - private final Matcher matcher = new KeyMatcher(); - - @SuppressWarnings("unchecked") - public RegisteredQueryHandler getQueryHandler(String path) { - return (RegisteredQueryHandler) queryHandlers - .computeIfAbsent(path, getMatchHandler(queryHandlers)); - } - - @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - return (RegisteredCommandHandler) commandHandlers - .computeIfAbsent(path, getMatchHandler(commandHandlers)); - } - - @SuppressWarnings("unchecked") - public RegisteredEventListener getEventListener(String path) { - if (eventListeners.containsKey(path)) { - return (RegisteredEventListener) eventListeners.get(path); - } - return (RegisteredEventListener) getMatchHandler(eventListeners).apply(path); - } - - - public Collection> getNotificationListeners() { - return eventNotificationListeners.values(); - } - - @SuppressWarnings("unchecked") - public RegisteredEventListener getNotificationListener(String path) { - return (RegisteredEventListener) eventNotificationListeners - .computeIfAbsent(path, getMatchHandler(eventNotificationListeners)); - } - - // Returns only the listenEvent not the handleDynamicEvents - public Collection> getEventListeners() { - return eventsToBind.values(); - } - - void addEventListener(RegisteredEventListener listener) { - eventListeners.put(listener.getPath(), listener); - } - - void addQueryHandler(RegisteredQueryHandler handler) { - if (handler.getPath().contains("*") || handler.getPath().contains("#")) { - throw new RuntimeException("avoid * or # in dynamic handlers, make sure you have no conflicts with cached patterns"); - } - queryHandlers.put(handler.getPath(), handler); - } - - private Function getMatchHandler(Map handlers) { - return name -> { - String matched = matcher.match(handlers.keySet(), name); - return handlers.get(matched); - }; - } - -} diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGateway.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGateway.java index afb5e4da..15feb971 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGateway.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGateway.java @@ -14,6 +14,8 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.rabbitmq.OutboundMessageResult; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; import java.time.Duration; import java.util.Collections; @@ -21,6 +23,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeoutException; +import java.util.function.Function; import static java.lang.Boolean.TRUE; import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; @@ -29,7 +32,6 @@ import static org.reactivecommons.async.commons.Headers.REPLY_ID; import static org.reactivecommons.async.commons.Headers.REPLY_TIMEOUT_MILLIS; import static org.reactivecommons.async.commons.Headers.SERVED_QUERY_ID; -import static reactor.core.publisher.Mono.fromCallable; public class RabbitDirectAsyncGateway implements DirectAsyncGateway { @@ -74,23 +76,31 @@ public Mono sendCommand(Command command, String targetName, String @Override public Mono sendCommand(Command command, String targetName, long delayMillis, String domain) { - Map headers = new HashMap<>(); - String realTarget = targetName; - if (delayMillis > 0) { - headers.put(DELAYED, String.valueOf(delayMillis)); - realTarget = targetName + "-delayed"; - } - return resolveSender(domain).sendWithConfirm(command, exchange, realTarget, headers, persistentCommands); + Tuple2> targetAndHeaders = validateDelay(targetName, delayMillis); + return resolveSender(domain).sendWithConfirm(command, exchange, targetAndHeaders.getT1(), + targetAndHeaders.getT2(), persistentCommands); } @Override public Mono sendCommand(CloudEvent command, String targetName) { - return sendCommand(new Command<>(command.getType(), command.getId(), command), targetName); + return sendCommand(command, targetName, 0, DEFAULT_DOMAIN); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, long delayMillis) { + return sendCommand(command, targetName, delayMillis, DEFAULT_DOMAIN); } @Override public Mono sendCommand(CloudEvent command, String targetName, String domain) { - return sendCommand(new Command<>(command.getType(), command.getId(), command), targetName, domain); + return sendCommand(command, targetName, 0, domain); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, long delayMillis, String domain) { + Tuple2> targetAndHeaders = validateDelay(targetName, delayMillis); + return resolveSender(domain).sendWithConfirm(command, exchange, targetAndHeaders.getT1(), + targetAndHeaders.getT2(), persistentCommands); } public Flux sendCommands(Flux> commands, String targetName) { @@ -105,37 +115,41 @@ public Mono requestReply(AsyncQuery query, String targetName, Class @Override public Mono requestReply(AsyncQuery query, String targetName, Class type, String domain) { + return requestReplyInternal(query, targetName, type, domain, AsyncQuery::getResource); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type) { + return requestReplyInternal(query, targetName, type, DEFAULT_DOMAIN, CloudEvent::getType); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type, String domain) { + return requestReplyInternal(query, targetName, type, domain, CloudEvent::getType); + } + + private Mono requestReplyInternal(T query, String targetName, Class type, String domain, Function queryTypeExtractor) { final String correlationID = UUID.randomUUID().toString().replaceAll("-", ""); final Mono replyHolder = router.register(correlationID) .timeout(replyTimeout) .doOnError(TimeoutException.class, e -> router.deregister(correlationID)) - .flatMap(s -> fromCallable(() -> converter.readValue(s, type))); + .map(s -> converter.readValue(s, type)); Map headers = new HashMap<>(); headers.put(REPLY_ID, config.getRoutingKey()); - headers.put(SERVED_QUERY_ID, query.getResource()); + headers.put(SERVED_QUERY_ID, queryTypeExtractor.apply(query)); headers.put(CORRELATION_ID, correlationID); headers.put(REPLY_TIMEOUT_MILLIS, replyTimeout.toMillis()); return resolveSender(domain).sendNoConfirm(query, exchange, targetName + ".query", headers, persistentQueries) .then(replyHolder) .name("async_query") - .tag("operation", query.getResource()) + .tag("operation", queryTypeExtractor.apply(query)) .tag("target", targetName) .tap(Micrometer.metrics(meterRegistry)); } - @Override - public Mono requestReply(CloudEvent query, String targetName, Class type) { - return requestReply(new AsyncQuery<>(query.getType(), query), targetName, type); - } - - @Override - public Mono requestReply(CloudEvent query, String targetName, Class type, String domain) { - return requestReply(new AsyncQuery<>(query.getType(), query), targetName, type, domain); - } - @Override public Mono reply(T response, From from) { final HashMap headers = new HashMap<>(); @@ -152,4 +166,14 @@ protected ReactiveMessageSender resolveSender(String domain) { // NOSONAR return sender; } + private Tuple2> validateDelay(String targetName, long delayMillis) { + Map headers = new HashMap<>(); + String realTarget = targetName; + if (delayMillis > 0) { + headers.put(DELAYED, String.valueOf(delayMillis)); + realTarget = targetName + "-delayed"; + } + return Tuples.of(realTarget, headers); + } + } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDomainEventBus.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDomainEventBus.java index 57f99a9d..b2bb5eda 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDomainEventBus.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitDomainEventBus.java @@ -1,17 +1,20 @@ package org.reactivecommons.async.rabbit; import io.cloudevents.CloudEvent; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.api.domain.RawMessage; import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; -import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.api.domain.DomainEventBus; import java.util.Collections; public class RabbitDomainEventBus implements DomainEventBus { + private static final String EVENT_SEND_FAILURE = "Event send failure: "; + private static final String NOT_IMPLEMENTED_YET = "Not implemented yet"; private final ReactiveMessageSender sender; private final String exchange; private final boolean persistentEvents; @@ -29,12 +32,35 @@ public RabbitDomainEventBus(ReactiveMessageSender sender, String exchange, Broke @Override public Mono emit(DomainEvent event) { return sender.sendWithConfirm(event, exchange, event.getName(), Collections.emptyMap(), persistentEvents) - .onErrorMap(err -> new RuntimeException("Event send failure: " + event.getName(), err)); + .onErrorMap(err -> new RuntimeException(EVENT_SEND_FAILURE + event.getName(), err)); + } + + @Override + public Publisher emit(String domain, DomainEvent event) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); } @Override - public Publisher emit(CloudEvent event) { - return emit(new DomainEvent<>(event.getType(), event.getId(), event)); + public Publisher emit(CloudEvent cloudEvent) { + return sender.sendWithConfirm(cloudEvent, exchange, cloudEvent.getType(), + Collections.emptyMap(), persistentEvents) + .onErrorMap(err -> new RuntimeException(EVENT_SEND_FAILURE + cloudEvent.getType(), err)); } + @Override + public Publisher emit(String domain, CloudEvent event) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } + + @Override + public Publisher emit(RawMessage rawEvent) { + return sender.sendWithConfirm(rawEvent, exchange, rawEvent.getType(), + Collections.emptyMap(), persistentEvents) + .onErrorMap(err -> new RuntimeException(EVENT_SEND_FAILURE + rawEvent.getType(), err)); + } + + @Override + public Publisher emit(String domain, RawMessage event) { + throw new UnsupportedOperationException(NOT_IMPLEMENTED_YET); + } } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java index 9dcdfc8c..cc35549f 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/RabbitMessage.java @@ -11,21 +11,30 @@ public class RabbitMessage implements Message { private final byte[] body; private final Properties properties; + private final String type; @Data - public static class RabbitMessageProperties implements Properties{ + public static class RabbitMessageProperties implements Properties { private String contentType; private String contentEncoding; + private long timestamp; private long contentLength; private Map headers = new HashMap<>(); } - public static RabbitMessage fromDelivery(Delivery delivery){ - return new RabbitMessage(delivery.getBody(), createMessageProps(delivery)); + public static RabbitMessage fromDelivery(Delivery delivery) { + return fromDelivery(delivery, null); + } + + public static RabbitMessage fromDelivery(Delivery delivery, String executorPath) { + return new RabbitMessage(delivery.getBody(), createMessageProps(delivery), executorPath); } private static Message.Properties createMessageProps(Delivery msj) { final RabbitMessage.RabbitMessageProperties properties = new RabbitMessage.RabbitMessageProperties(); + if (msj.getProperties().getTimestamp() != null) { + properties.setTimestamp(msj.getProperties().getTimestamp().getTime()); + } properties.setHeaders(msj.getProperties().getHeaders()); properties.setContentType(msj.getProperties().getContentType()); properties.setContentEncoding(msj.getProperties().getContentEncoding()); diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java index 51bfdacb..3ae9856a 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSender.java @@ -1,6 +1,7 @@ package org.reactivecommons.async.rabbit.communications; import com.rabbitmq.client.AMQP; +import lombok.Getter; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.exceptions.SendFailureNoAckException; @@ -26,57 +27,72 @@ import static org.reactivecommons.async.commons.Headers.SOURCE_APPLICATION; public class ReactiveMessageSender { + @Getter private final Sender sender; private final String sourceApplication; private final MessageConverter messageConverter; + @Getter private final TopologyCreator topologyCreator; - private final int numberOfSenderSubscriptions = 4; + private static final int NUMBER_OF_SENDER_SUBSCRIPTIONS = 4; private final CopyOnWriteArrayList> fluxSinkConfirm = new CopyOnWriteArrayList<>(); private volatile FluxSink fluxSinkNoConfirm; private final AtomicLong counter = new AtomicLong(); - private final ExecutorService executorService = Executors.newFixedThreadPool(13, r -> new Thread(r, "RMessageSender1-" + counter.getAndIncrement())); - private final ExecutorService executorService2 = Executors.newFixedThreadPool(13, r -> new Thread(r, "RMessageSender2-" + counter.getAndIncrement())); + private final ExecutorService executorService = Executors.newFixedThreadPool( + 13, r -> new Thread(r, "RMessageSender1-" + counter.getAndIncrement()) + ); + private final ExecutorService executorService2 = Executors.newFixedThreadPool( + 13, r -> new Thread(r, "RMessageSender2-" + counter.getAndIncrement()) + ); - public ReactiveMessageSender(Sender sender, String sourceApplication, MessageConverter messageConverter, TopologyCreator topologyCreator) { + public ReactiveMessageSender(Sender sender, String sourceApplication, + MessageConverter messageConverter, TopologyCreator topologyCreator) { this.sender = sender; this.sourceApplication = sourceApplication; this.messageConverter = messageConverter; this.topologyCreator = topologyCreator; - for (int i = 0; i < numberOfSenderSubscriptions; ++i) { + for (int i = 0; i < NUMBER_OF_SENDER_SUBSCRIPTIONS; ++i) { final Flux messageSource = Flux.create(fluxSinkConfirm::add); - sender.sendWithTypedPublishConfirms(messageSource).doOnNext((OutboundMessageResult outboundMessageResult) -> { - final Consumer ackNotifier = outboundMessageResult.getOutboundMessage().getAckNotifier(); - executorService.submit(() -> ackNotifier.accept(outboundMessageResult.isAck())); - }).subscribe(); + sender.sendWithTypedPublishConfirms(messageSource) + .doOnNext((OutboundMessageResult outboundMessageResult) -> { + final Consumer ackNotifier = outboundMessageResult.getOutboundMessage().getAckNotifier(); + executorService.submit(() -> ackNotifier.accept(outboundMessageResult.isAck())); + }).subscribe(); } - final Flux messageSourceNoConfirm = Flux.create(fluxSink -> { - this.fluxSinkNoConfirm = fluxSink; - }); + final Flux messageSourceNoConfirm = Flux.create(fluxSink -> + this.fluxSinkNoConfirm = fluxSink + ); sender.send(messageSourceNoConfirm).subscribe(); } - public Mono sendWithConfirm(T message, String exchange, String routingKey, Map headers, boolean persistent) { + public Mono sendWithConfirm(T message, String exchange, String routingKey, + Map headers, boolean persistent) { return Mono.create(monoSink -> { Consumer notifier = new AckNotifier(monoSink); - final MyOutboundMessage outboundMessage = toOutboundMessage(message, exchange, routingKey, headers, notifier, persistent); - executorService2.submit(() -> fluxSinkConfirm.get((int) (System.currentTimeMillis() % numberOfSenderSubscriptions)).next(outboundMessage)); + final MyOutboundMessage outboundMessage = toOutboundMessage( + message, exchange, routingKey, headers, notifier, persistent + ); + executorService2.submit(() -> fluxSinkConfirm.get( + (int) (System.currentTimeMillis() % NUMBER_OF_SENDER_SUBSCRIPTIONS)).next(outboundMessage) + ); }); } - public Mono sendNoConfirm(T message, String exchange, String routingKey, Map headers, boolean persistent) { + public Mono sendNoConfirm(T message, String exchange, String routingKey, + Map headers, boolean persistent) { fluxSinkNoConfirm.next(toOutboundMessage(message, exchange, routingKey, headers, persistent)); return Mono.empty(); } - public Flux sendWithConfirmBatch(Flux messages, String exchange, String routingKey, Map headers, boolean persistent) { + public Flux sendWithConfirmBatch(Flux messages, String exchange, String routingKey, + Map headers, boolean persistent) { return messages.map(message -> toOutboundMessage(message, exchange, routingKey, headers, persistent)) .as(sender::sendWithPublishConfirms) .flatMap(result -> result.isAck() ? @@ -94,7 +110,7 @@ public AckNotifier(MonoSink monoSink) { @Override public void accept(Boolean ack) { - if (ack) { + if (Boolean.TRUE.equals(ack)) { monoSink.success(); } else { monoSink.error(new SendFailureNoAckException("No ACK when sending message")); @@ -103,33 +119,37 @@ public void accept(Boolean ack) { } + @Getter static class MyOutboundMessage extends OutboundMessage { private final Consumer ackNotifier; - public MyOutboundMessage(String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body, Consumer ackNotifier) { + public MyOutboundMessage( + String exchange, String routingKey, AMQP.BasicProperties properties, + byte[] body, Consumer ackNotifier + ) { super(exchange, routingKey, properties, body); this.ackNotifier = ackNotifier; } - - public Consumer getAckNotifier() { - return ackNotifier; - } } - private MyOutboundMessage toOutboundMessage(T object, String exchange, String routingKey, Map headers, Consumer ackNotifier, boolean persistent) { + private MyOutboundMessage toOutboundMessage(T object, String exchange, + String routingKey, Map headers, + Consumer ackNotifier, boolean persistent) { final Message message = messageConverter.toMessage(object); final AMQP.BasicProperties props = buildMessageProperties(message, headers, persistent); return new MyOutboundMessage(exchange, routingKey, props, message.getBody(), ackNotifier); } - private OutboundMessage toOutboundMessage(T object, String exchange, String routingKey, Map headers, boolean persistent) { + private OutboundMessage toOutboundMessage(T object, String exchange, String routingKey, + Map headers, boolean persistent) { final Message message = messageConverter.toMessage(object); final AMQP.BasicProperties props = buildMessageProperties(message, headers, persistent); return new OutboundMessage(exchange, routingKey, props, message.getBody()); } - private AMQP.BasicProperties buildMessageProperties(Message message, Map headers, boolean persistent) { + private AMQP.BasicProperties buildMessageProperties(Message message, Map headers, + boolean persistent) { final Message.Properties properties = message.getProperties(); final Map baseHeaders = new HashMap<>(properties.getHeaders()); baseHeaders.putAll(headers); @@ -147,13 +167,5 @@ private AMQP.BasicProperties buildMessageProperties(Message message, Map unbind(BindingSpecification binding) { .onErrorMap(TopologyDefException::new); } - public Mono declareDLQ(String originQueue, String retryTarget, int retryTime, Optional maxLengthBytesOpt) { + public Mono declareDLQ(String originQueue, String retryTarget, int retryTime, + Optional maxLengthBytesOpt) { final Map args = new HashMap<>(); args.put("x-dead-letter-exchange", retryTarget); args.put("x-message-ttl", retryTime); @@ -55,7 +56,8 @@ public Mono declareDLQ(String originQueue, String retryTar return declare(specification); } - public Mono declareQueue(String name, String dlqExchange, Optional maxLengthBytesOpt) { + public Mono declareQueue(String name, String dlqExchange, + Optional maxLengthBytesOpt) { return declareQueue(name, dlqExchange, maxLengthBytesOpt, Optional.empty()); } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverter.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverter.java deleted file mode 100644 index b4791e53..00000000 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverter.java +++ /dev/null @@ -1,205 +0,0 @@ -package org.reactivecommons.async.rabbit.converters.json; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.cloudevents.CloudEvent; -import io.cloudevents.core.provider.EventFormatProvider; -import io.cloudevents.jackson.JsonFormat; -import lombok.Data; -import org.reactivecommons.api.domain.Command; -import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.async.api.AsyncQuery; -import org.reactivecommons.async.commons.communications.Message; -import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.commons.exceptions.MessageConversionException; -import org.reactivecommons.async.rabbit.RabbitMessage; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Base64; - -public class JacksonCloudEventMessageConverter implements MessageConverter { - private static final String CONTENT_TYPE = "application/json"; - public static final String FAILED_TO_CONVERT_MESSAGE_CONTENT = "Failed to convert Message content"; - - private final ObjectMapper objectMapper; - - - public JacksonCloudEventMessageConverter(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; - } - - @Override - public AsyncQuery readAsyncQuery(Message message, Class bodyClass) { - try { - final AsyncQueryJson asyncQueryJson = readValue(message, AsyncQueryJson.class); - T value = extractData(bodyClass, asyncQueryJson.getQueryData()); - return new AsyncQuery<>(asyncQueryJson.getResource(), value); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - } - - @Override - public DomainEvent readDomainEvent(Message message, Class bodyClass) { - try { - final DomainEventJson domainEventJson = readValue(message, DomainEventJson.class); - - T value = extractData(bodyClass, domainEventJson.getData()); - - return new DomainEvent<>(domainEventJson.getName(), domainEventJson.getEventId(), value); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - } - - @Override - public Command readCommand(Message message, Class bodyClass) { - try { - final CommandJson commandJson = readValue(message, CommandJson.class); - T value = extractData(bodyClass, commandJson.getData()); - return new Command<>(commandJson.getName(), commandJson.getCommandId(), value); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - } - - @Override - @SuppressWarnings("unchecked") - public T readValue(Message message, Class valueClass) { - try { - if(valueClass == CloudEvent.class){ - - return (T) EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .deserialize(objectMapper.readValue(message.getBody(), byte[].class)); - - } - return objectMapper.readValue(message.getBody(), valueClass); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - } - - @Override - @SuppressWarnings("unchecked") - public Command readCommandStructure(Message message) { - final CommandJson commandJson = readValue(message, CommandJson.class); - return new Command<>(commandJson.getName(), commandJson.getCommandId(), (T) commandJson.getData()); - } - - @Override - @SuppressWarnings("unchecked") - public DomainEvent readDomainEventStructure(Message message) { - final DomainEventJson eventJson = readValue(message, DomainEventJson.class); - return new DomainEvent<>(eventJson.getName(), eventJson.getEventId(), (T) eventJson.getData()); - } - - @Override - @SuppressWarnings("unchecked") - public AsyncQuery readAsyncQueryStructure(Message message) { - final AsyncQueryJson asyncQueryJson = readValue(message, AsyncQueryJson.class); - return new AsyncQuery<>(asyncQueryJson.getResource(), (T) asyncQueryJson.getQueryData()); - } - - - public Message commandToMessage(Command object) { - byte[] data = EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .serialize(object.getData()); - - return getRabbitMessage(new Command<>(object.getName(), object.getCommandId(), data)); - } - - - public Message eventToMessage(DomainEvent object) { - byte[] data = EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .serialize(object.getData()); - - return getRabbitMessage(new DomainEvent<>(object.getName(), object.getEventId(), data)); - } - - - public Message queryToMessage(AsyncQuery object) { - byte[] data = EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .serialize(object.getQueryData()); - - return getRabbitMessage(new AsyncQuery<>(object.getResource(), data)); - } - @Override - public Message toMessage(Object object) { - if(object instanceof DomainEvent - && ((DomainEvent) object).getData() instanceof CloudEvent){ - return eventToMessage((DomainEvent) object); - - } - if(object instanceof Command - && ((Command) object).getData() instanceof CloudEvent){ - return commandToMessage((Command) object); - } - if(object instanceof AsyncQuery - && ((AsyncQuery) object).getQueryData() instanceof CloudEvent){ - return queryToMessage((AsyncQuery) object); - } - return getRabbitMessage(object); - } - - private RabbitMessage getRabbitMessage(Object object) { - byte[] bytes; - try { - String jsonString = this.objectMapper.writeValueAsString(object); - bytes = jsonString.getBytes(StandardCharsets.UTF_8); - } catch (IOException e) { - throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); - } - RabbitMessage.RabbitMessageProperties props = new RabbitMessage.RabbitMessageProperties(); - props.setContentType(CONTENT_TYPE); - props.setContentEncoding(StandardCharsets.UTF_8.name()); - props.setContentLength(bytes.length); - return new RabbitMessage(bytes, props); - } - - private T extractData(Class bodyClass, JsonNode node) throws JsonProcessingException { - T value; - if(bodyClass == CloudEvent.class){ - - value = (T) EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .deserialize(Base64.getDecoder() - .decode(node.asText())); - - } - else{ - value = objectMapper.treeToValue(node, bodyClass); - } - return value; - } - - @Data - private static class AsyncQueryJson { - private String resource; - private JsonNode queryData; - } - - @Data - private static class DomainEventJson { - private String name; - private String eventId; - private JsonNode data; - } - - @Data - private static class CommandJson { - private String name; - private String commandId; - private JsonNode data; - } -} diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/RabbitJacksonMessageConverter.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/RabbitJacksonMessageConverter.java new file mode 100644 index 00000000..1cff647e --- /dev/null +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/converters/json/RabbitJacksonMessageConverter.java @@ -0,0 +1,41 @@ +package org.reactivecommons.async.rabbit.converters.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.commons.exceptions.MessageConversionException; +import org.reactivecommons.async.rabbit.RabbitMessage; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class RabbitJacksonMessageConverter extends JacksonMessageConverter { + + public RabbitJacksonMessageConverter(ObjectMapper objectMapper) { + super(objectMapper); + } + + @Override + public Message toMessage(Object object) { + if (object instanceof RabbitMessage) { + return (RabbitMessage) object; + } + byte[] bytes; + try { + String jsonString = this.objectMapper.writeValueAsString(object); + bytes = jsonString.getBytes(StandardCharsets.UTF_8); + } catch (IOException e) { + throw new MessageConversionException(FAILED_TO_CONVERT_MESSAGE_CONTENT, e); + } + RabbitMessage.RabbitMessageProperties props = new RabbitMessage.RabbitMessageProperties(); + if (object instanceof CloudEvent) { + props.setContentType(APPLICATION_CLOUD_EVENT_JSON); + } else { + props.setContentType(APPLICATION_JSON); + } + props.setContentEncoding(StandardCharsets.UTF_8.name()); + props.setContentLength(bytes.length); + return new RabbitMessage(bytes, props, null); + } +} diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java index 64d48bac..42316813 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListener.java @@ -1,15 +1,17 @@ package org.reactivecommons.async.rabbit.listeners; +import com.fasterxml.jackson.databind.JsonNode; import com.rabbitmq.client.AMQP; import lombok.extern.java.Log; -import org.reactivecommons.api.domain.Command; +import org.reactivecommons.async.api.handlers.CloudCommandHandler; +import org.reactivecommons.async.api.handlers.DomainCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.commons.CommandExecutor; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; import org.reactivecommons.async.rabbit.RabbitMessage; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; @@ -24,6 +26,10 @@ @Log public class ApplicationCommandListener extends GenericMessageListener { + private static final String DQL = ".DLQ"; + private static final String NAME = "name"; + private static final String COMMAND_ID = "commandId"; + private static final String TYPE = "type"; private final MessageConverter messageConverter; private final HandlerResolver resolver; private final String directExchange; @@ -58,13 +64,25 @@ public ApplicationCommandListener(ReactiveMessageListener listener, } protected Mono setUpBindings(TopologyCreator creator) { - final Mono declareExchange = creator.declare(ExchangeSpecification.exchange(directExchange).durable(true).type("direct")); + final Mono declareExchange = creator.declare( + ExchangeSpecification.exchange(directExchange).durable(true).type("direct") + ); if (withDLQRetry) { - final Mono declareExchangeDLQ = creator.declare(ExchangeSpecification.exchange(directExchange + ".DLQ").durable(true).type("direct")); - final Mono declareQueue = creator.declareQueue(queueName, directExchange + ".DLQ", maxLengthBytes); - final Mono declareDLQ = creator.declareDLQ(queueName, directExchange, retryDelay, maxLengthBytes); - final Mono binding = creator.bind(BindingSpecification.binding(directExchange, queueName, queueName)); - final Mono bindingDLQ = creator.bind(BindingSpecification.binding(directExchange + ".DLQ", queueName, queueName + ".DLQ")); + final Mono declareExchangeDLQ = creator.declare( + ExchangeSpecification.exchange(directExchange + DQL).durable(true).type("direct") + ); + final Mono declareQueue = creator.declareQueue( + queueName, directExchange + DQL, maxLengthBytes + ); + final Mono declareDLQ = creator.declareDLQ( + queueName, directExchange, retryDelay, maxLengthBytes + ); + final Mono binding = creator.bind(BindingSpecification.binding( + directExchange, queueName, queueName) + ); + final Mono bindingDLQ = creator.bind( + BindingSpecification.binding(directExchange + DQL, queueName, queueName + DQL) + ); return declareExchange.then(declareExchangeDLQ) .then(declareDLQ) .then(declareQueue) @@ -74,7 +92,9 @@ protected Mono setUpBindings(TopologyCreator creator) { .then(); } else { final Mono declareQueue = creator.declareQueue(queueName, maxLengthBytes); - final Mono binding = creator.bind(BindingSpecification.binding(directExchange, queueName, queueName)); + final Mono binding = creator.bind( + BindingSpecification.binding(directExchange, queueName, queueName) + ); return declareExchange.then(declareQueue).then(binding).then(declareDelayedTopology(creator)).then(); } } @@ -82,8 +102,12 @@ protected Mono setUpBindings(TopologyCreator creator) { private Mono declareDelayedTopology(TopologyCreator creator) { if (delayedCommands) { String delayedQueue = queueName + "-delayed"; - final Mono declareQueue = creator.declareQueue(delayedQueue, directExchange, maxLengthBytes, Optional.of(queueName)); - final Mono binding = creator.bind(BindingSpecification.binding(directExchange, delayedQueue, delayedQueue)); + final Mono declareQueue = creator.declareQueue( + delayedQueue, directExchange, maxLengthBytes, Optional.of(queueName) + ); + final Mono binding = creator.bind( + BindingSpecification.binding(directExchange, delayedQueue, delayedQueue) + ); return declareQueue.then(binding).then(); } return Mono.empty(); @@ -92,16 +116,19 @@ private Mono declareDelayedTopology(TopologyCreator creator) { @Override protected Function> rawMessageHandler(String executorPath) { - final RegisteredCommandHandler handler = resolver.getCommandHandler(executorPath); - final Class eventClass = handler.getInputClass(); - Function> converter = msj -> messageConverter.readCommand(msj, eventClass); + final RegisteredCommandHandler handler = resolver.getCommandHandler(executorPath); + Function converter = resolveConverter(handler); final CommandExecutor executor = new CommandExecutor<>(handler.getHandler(), converter); return msj -> executor.execute(msj).cast(Object.class); } protected String getExecutorPath(AcknowledgableDelivery msj) { - final Command command = messageConverter.readCommandStructure(RabbitMessage.fromDelivery(msj)); - return command.getName(); + RabbitMessage rabbitMessage = RabbitMessage.fromDelivery(msj); + JsonNode jsonNode = messageConverter.readValue(rabbitMessage, JsonNode.class); + if (jsonNode.get(COMMAND_ID) != null) { + return jsonNode.get(NAME).asText(); + } + return jsonNode.get(TYPE).asText(); } @Override @@ -109,6 +136,15 @@ protected Object parseMessageForReporter(Message message) { return messageConverter.readCommandStructure(message); } + private Function resolveConverter(RegisteredCommandHandler registeredCommandHandler) { + if (registeredCommandHandler.getHandler() instanceof DomainCommandHandler) { + final Class commandClass = registeredCommandHandler.getInputClass(); + return msj -> messageConverter.readCommand(msj, commandClass); + } else if (registeredCommandHandler.getHandler() instanceof CloudCommandHandler) { + return messageConverter::readCloudEvent; + } + throw new RuntimeException("Unknown handler type"); + } } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java index d57edc12..bd10d077 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListener.java @@ -2,14 +2,16 @@ import com.rabbitmq.client.AMQP; import lombok.extern.java.Log; -import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; +import org.reactivecommons.async.api.handlers.RawEventHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.commons.DiscardNotifier; import org.reactivecommons.async.commons.EventExecutor; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import reactor.core.publisher.Flux; @@ -60,18 +62,42 @@ public ApplicationEventListener(ReactiveMessageListener receiver, } protected Mono setUpBindings(TopologyCreator creator) { - final Mono declareExchange = creator.declare(ExchangeSpecification.exchange(eventsExchange).durable(true).type("topic")); - final Flux bindings = fromIterable(resolver.getEventListeners()).flatMap(listener -> creator.bind(BindingSpecification.binding(eventsExchange, listener.getPath(), queueName))); + final Mono declareExchange = creator.declare( + ExchangeSpecification.exchange(eventsExchange).durable(true).type("topic") + ); + final Flux bindings = fromIterable(resolver.getEventListeners()) + .flatMap(listener -> creator.bind( + BindingSpecification.binding(eventsExchange, listener.getPath(), queueName) + )); if (withDLQRetry) { final String eventsDLQExchangeName = format("%s.%s.DLQ", appName, eventsExchange); final String retryExchangeName = format("%s.%s", appName, eventsExchange); - final Mono retryExchange = creator.declare(ExchangeSpecification.exchange(retryExchangeName).durable(true).type("topic")); - final Mono declareExchangeDLQ = creator.declare(ExchangeSpecification.exchange(eventsDLQExchangeName).durable(true).type("topic")); - final Mono declareDLQ = creator.declareDLQ(queueName, retryExchangeName, retryDelay, maxLengthBytes); - final Mono declareQueue = creator.declareQueue(queueName, eventsDLQExchangeName, maxLengthBytes); - final Mono bindingDLQ = creator.bind(BindingSpecification.binding(eventsDLQExchangeName, "#", queueName + ".DLQ")); - final Mono retryBinding = creator.bind(BindingSpecification.binding(retryExchangeName, "#", queueName)); - return declareExchange.then(retryExchange).then(declareExchangeDLQ).then(declareQueue).then(declareDLQ).thenMany(bindings).then(bindingDLQ).then(retryBinding).then(); + final Mono retryExchange = creator.declare( + ExchangeSpecification.exchange(retryExchangeName).durable(true).type("topic") + ); + final Mono declareExchangeDLQ = creator.declare( + ExchangeSpecification.exchange(eventsDLQExchangeName).durable(true).type("topic") + ); + final Mono declareDLQ = creator.declareDLQ( + queueName, retryExchangeName, retryDelay, maxLengthBytes + ); + final Mono declareQueue = creator.declareQueue( + queueName, eventsDLQExchangeName, maxLengthBytes + ); + final Mono bindingDLQ = creator.bind( + BindingSpecification.binding(eventsDLQExchangeName, "#", queueName + ".DLQ") + ); + final Mono retryBinding = creator.bind( + BindingSpecification.binding(retryExchangeName, "#", queueName) + ); + return declareExchange.then(retryExchange) + .then(declareExchangeDLQ) + .then(declareQueue) + .then(declareDLQ) + .thenMany(bindings) + .then(bindingDLQ) + .then(retryBinding) + .then(); } else { final Mono declareQueue = creator.declareQueue(queueName, maxLengthBytes); return declareExchange.then(declareQueue).thenMany(bindings).then(); @@ -81,11 +107,9 @@ protected Mono setUpBindings(TopologyCreator creator) { @Override protected Function> rawMessageHandler(String executorPath) { - final RegisteredEventListener handler = resolver.getEventListener(executorPath); - - final Class eventClass = handler.getInputClass(); - Function> converter = msj -> messageConverter.readDomainEvent(msj, eventClass); + final RegisteredEventListener handler = resolver.getEventListener(executorPath); + Function converter = resolveConverter(handler); final EventExecutor executor = new EventExecutor<>(handler.getHandler(), converter); return msj -> executor @@ -102,6 +126,19 @@ protected Object parseMessageForReporter(Message msj) { return messageConverter.readDomainEventStructure(msj); } + private Function resolveConverter(RegisteredEventListener registeredEventListener) { + if (registeredEventListener.getHandler() instanceof DomainEventHandler) { + final Class eventClass = registeredEventListener.getInputClass(); + return msj -> messageConverter.readDomainEvent(msj, eventClass); + } + if (registeredEventListener.getHandler() instanceof CloudEventHandler) { + return messageConverter::readCloudEvent; + } + if (registeredEventListener.getHandler() instanceof RawEventHandler) { + return message -> message; + } + throw new RuntimeException("Unknown handler type"); + } } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListener.java index cd01c6b2..2f0a7e03 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListener.java @@ -2,14 +2,15 @@ import com.rabbitmq.client.AMQP; import lombok.extern.java.Log; -import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.api.handlers.CloudEventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.commons.DiscardNotifier; import org.reactivecommons.async.commons.EventExecutor; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import reactor.core.publisher.Flux; @@ -50,50 +51,54 @@ public ApplicationNotificationListener(ReactiveMessageListener receiver, } protected Mono setUpBindings(TopologyCreator creator) { - final Mono declareExchange = creator.declare(exchange(exchangeName) - .type("topic") - .durable(true)); final Mono declareQueue = creator.declare( - queue(queueName) - .durable(false) - .autoDelete(true) - .exclusive(true)); + queue(queueName).durable(false).autoDelete(true).exclusive(true) + ); final Flux bindings = fromIterable(resolver.getNotificationListeners()) - .flatMap(listener -> creator.bind(binding(exchangeName, listener.getPath(), queueName))); + .flatMap(listener -> creator.bind( + binding(exchangeName, listener.getPath(), queueName)) + ); if (createTopology) { - return declareExchange + return creator.declare(exchange(exchangeName).type("topic").durable(true)) .then(declareQueue) .thenMany(bindings) .then(); - } else { - return declareQueue - .thenMany(bindings) - .then(); } + return declareQueue.thenMany(bindings).then(); } @Override protected Function> rawMessageHandler(String executorPath) { - final RegisteredEventListener eventListener = resolver.getNotificationListener(executorPath); - final Function> converter = message -> messageConverter - .readDomainEvent(message, eventListener.getInputClass()); + final RegisteredEventListener eventListener = resolver.getNotificationListener(executorPath); + + Function converter = resolveConverter(eventListener); final EventExecutor executor = new EventExecutor<>(eventListener.getHandler(), converter); - return message -> executor - .execute(message) - .cast(Object.class); + + return message -> executor.execute(message).cast(Object.class); } @Override protected String getExecutorPath(AcknowledgableDelivery message) { - return message.getEnvelope() - .getRoutingKey(); + return message.getEnvelope().getRoutingKey(); } @Override protected Object parseMessageForReporter(Message msj) { return messageConverter.readDomainEventStructure(msj); } + + private Function resolveConverter(RegisteredEventListener registeredEventListener) { + if (registeredEventListener.getHandler() instanceof DomainEventHandler) { + final Class eventClass = registeredEventListener.getInputClass(); + return msj -> messageConverter.readDomainEvent(msj, eventClass); + } + if (registeredEventListener.getHandler() instanceof CloudEventHandler) { + return messageConverter::readCloudEvent; + } + throw new RuntimeException("Unknown handler type"); + } + } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java index 978f2ef8..142ee1a9 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListener.java @@ -2,16 +2,14 @@ import com.rabbitmq.client.AMQP; import io.cloudevents.CloudEvent; -import io.cloudevents.core.provider.EventFormatProvider; -import io.cloudevents.jackson.JsonFormat; import lombok.extern.java.Log; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.QueryExecutor; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivecommons.async.rabbit.communications.TopologyCreator; @@ -34,7 +32,6 @@ import static org.reactivecommons.async.commons.Headers.SERVED_QUERY_ID; @Log -//TODO: Organizar inferencia de tipos de la misma forma que en comandos y eventos public class ApplicationQueryListener extends GenericMessageListener { private final MessageConverter converter; private final HandlerResolver handlerResolver; @@ -69,26 +66,52 @@ public ApplicationQueryListener(ReactiveMessageListener listener, String queueNa protected Function> rawMessageHandler(String executorPath) { final RegisteredQueryHandler handler = handlerResolver.getQueryHandler(executorPath); if (handler == null) { - return message -> Mono.error(new RuntimeException("Handler Not registered for Query: " + executorPath)); + return message -> + Mono.error(new RuntimeException("Handler Not registered for Query: " + executorPath)); } - final Class handlerClass = handler.getQueryClass(); - Function messageConverter = msj -> converter.readAsyncQuery(msj, handlerClass).getQueryData(); + Function messageConverter = resolveConverter(handler.getQueryClass()); final QueryExecutor executor = new QueryExecutor<>(handler.getHandler(), messageConverter); return executor::execute; } + private Function resolveConverter(Class handlerClass) { + if (handlerClass == CloudEvent.class) { + return converter::readCloudEvent; + } + return msj -> converter.readAsyncQuery(msj, handlerClass).getQueryData(); + } + protected Mono setUpBindings(TopologyCreator creator) { - final Mono declareExchange = creator.declare(ExchangeSpecification.exchange(directExchange).durable(true).type("direct")); + final Mono declareExchange = creator.declare( + ExchangeSpecification.exchange(directExchange).durable(true).type("direct") + ); if (withDLQRetry) { - final Mono declareExchangeDLQ = creator.declare(ExchangeSpecification.exchange(directExchange + ".DLQ").durable(true).type("direct")); - final Mono declareQueue = creator.declareQueue(queueName, directExchange + ".DLQ", maxLengthBytes); - final Mono declareDLQ = creator.declareDLQ(queueName, directExchange, retryDelay, maxLengthBytes); - final Mono binding = creator.bind(BindingSpecification.binding(directExchange, queueName, queueName)); - final Mono bindingDLQ = creator.bind(BindingSpecification.binding(directExchange + ".DLQ", queueName, queueName + ".DLQ")); - return declareExchange.then(declareExchangeDLQ).then(declareQueue).then(declareDLQ).then(binding).then(bindingDLQ).then(); + final Mono declareExchangeDLQ = creator.declare( + ExchangeSpecification.exchange(directExchange + ".DLQ").durable(true).type("direct") + ); + final Mono declareQueue = creator.declareQueue( + queueName, directExchange + ".DLQ", maxLengthBytes + ); + final Mono declareDLQ = creator.declareDLQ( + queueName, directExchange, retryDelay, maxLengthBytes + ); + final Mono binding = creator.bind( + BindingSpecification.binding(directExchange, queueName, queueName) + ); + final Mono bindingDLQ = creator.bind( + BindingSpecification.binding(directExchange + ".DLQ", queueName, queueName + ".DLQ") + ); + return declareExchange + .then(declareExchangeDLQ) + .then(declareQueue) + .then(declareDLQ) + .then(binding) + .then(bindingDLQ) + .then(); } else { final Mono declareQueue = creator.declareQueue(queueName, maxLengthBytes); - final Mono binding = creator.bind(BindingSpecification.binding(directExchange, queueName, queueName)); + final Mono binding = creator.bind( + BindingSpecification.binding(directExchange, queueName, queueName)); return declareExchange.then(declareQueue).then(binding).then(); } } @@ -152,14 +175,6 @@ protected Function, Mono> enrichPostProcess(Message msg) { final HashMap headers = new HashMap<>(); headers.put(CORRELATION_ID, correlationID); Object response = signal.get(); - if (response instanceof CloudEvent) { - byte[] serialized = EventFormatProvider - .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) - .serialize((CloudEvent) response); - return sender.sendNoConfirm(serialized, replyExchange, replyID, headers, false); - } - return sender.sendNoConfirm(response, replyExchange, replyID, headers, false); }); } diff --git a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListener.java b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListener.java index 1fd937c2..b5158a95 100644 --- a/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListener.java +++ b/async/async-rabbit/src/main/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListener.java @@ -33,6 +33,7 @@ import static reactor.core.publisher.Mono.defer; @Log +@SuppressWarnings({"unchecked", "rawtypes"}) public abstract class GenericMessageListener { public static final int DEFAULT_RETRIES_DLQ = 10; private final ConcurrentHashMap>> handlers = new ConcurrentHashMap<>(); @@ -51,9 +52,10 @@ public abstract class GenericMessageListener { private final CustomReporter customReporter; private volatile Flux messageFlux; - public GenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, - boolean createTopology, long maxRetries, long retryDelay, DiscardNotifier discardNotifier, - String objectType, CustomReporter customReporter) { + protected GenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, + boolean createTopology, long maxRetries, long retryDelay, + DiscardNotifier discardNotifier, String objectType, + CustomReporter customReporter) { this.receiver = listener.getReceiver(); this.queueName = queueName; this.messageListener = listener; @@ -79,9 +81,13 @@ protected Mono setUpBindings(TopologyCreator creator) { } public void startListener() { - log.log(Level.INFO, "Using max concurrency {0}, in queue: {1}", new Object[]{messageListener.getMaxConcurrency(), queueName}); + log.log(Level.INFO, "Using max concurrency {0}, in queue: {1}", + new Object[]{messageListener.getMaxConcurrency(), queueName} + ); if (useDLQRetries) { - log.log(Level.INFO, "ATTENTION! Using DLQ Strategy for retries with {0} + 1 Max Retries configured!", new Object[]{maxRetries}); + log.log(Level.INFO, "ATTENTION! Using DLQ Strategy for retries with {0} + 1 Max Retries configured!", + new Object[]{maxRetries} + ); } else { log.log(Level.INFO, "ATTENTION! Using infinite fast retries as Retry Strategy"); } @@ -99,20 +105,23 @@ public void startListener() { .transform(this::consumeFaultTolerant); } - onTerminate(); } - private void onTerminate() { - messageFlux.doOnTerminate(this::onTerminate) - .subscribe(new LoggerSubscriber<>(getClass().getName())); + private Flux consumeFaultTolerant(Flux messageFlux) { + return messageFlux.flatMap(msj -> { + final Instant init = Instant.now(); + return handle(msj, init) + .doOnSuccess(AcknowledgableDelivery::ack) + .onErrorResume(err -> requeueOrAck(msj, err, init)); + }, messageListener.getMaxConcurrency()); } protected Mono handle(AcknowledgableDelivery msj, Instant initTime) { try { final String executorPath = getExecutorPath(msj); final Function> handler = getExecutor(executorPath); - final Message message = RabbitMessage.fromDelivery(msj); + final Message message = RabbitMessage.fromDelivery(msj, executorPath); Mono flow = defer(() -> handler.apply(message)) .transform(enrichPostProcess(message)); @@ -134,6 +143,12 @@ protected Mono handle(AcknowledgableDelivery msj, Instan } } + private void onTerminate() { + messageFlux + .doOnTerminate(this::onTerminate) + .subscribe(new LoggerSubscriber<>(getClass().getName())); + } + private void logExecution(String executorPath, Instant initTime, boolean success) { try { final Instant afterExecutionTime = Instant.now(); @@ -161,20 +176,13 @@ private void doLogExecution(String executorPath, long timeElapsed) { objectType, executorPath, timeElapsed)); } - private Flux consumeFaultTolerant(Flux messageFlux) { - return messageFlux.flatMap(msj -> { - final Instant init = Instant.now(); - return handle(msj, init) - .doOnSuccess(AcknowledgableDelivery::ack) - .onErrorResume(err -> requeueOrAck(msj, err, init)); - }, messageListener.getMaxConcurrency()); - } - protected void logError(Throwable err, AcknowledgableDelivery msj, FallbackStrategy strategy) { String messageID = msj.getProperties().getMessageId(); try { - log.log(Level.SEVERE, format("Error encounter while processing message %s: %s", messageID, err.toString()), err); + log.log(Level.SEVERE, + format("Error encounter while processing message %s: %s", messageID, err.toString()), err + ); log.warning(format("Message %s Headers: %s", messageID, msj.getProperties().getHeaders().toString())); log.warning(format("Message %s Body: %s", messageID, new String(msj.getBody()))); } catch (Exception e) { @@ -185,14 +193,7 @@ protected void logError(Throwable err, AcknowledgableDelivery msj, FallbackStrat } private Function> getExecutor(String path) { - final Function> handler = handlers.get(path); - return handler != null ? handler : computeRawMessageHandler(path); - } - - private Function> computeRawMessageHandler(String commandId) { - return handlers.computeIfAbsent(commandId, s -> - rawMessageHandler(commandId) - ); + return handlers.computeIfAbsent(path, this::rawMessageHandler); } protected abstract Function> rawMessageHandler(String executorPath); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java index 028b0b88..35dc28d2 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/helpers/TestStubs.java @@ -1,9 +1,11 @@ package org.reactivecommons.async.helpers; -import org.reactivecommons.async.rabbit.RabbitMessage; import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.rabbit.RabbitMessage; -import static org.reactivecommons.async.commons.Headers.*; +import static org.reactivecommons.async.commons.Headers.CORRELATION_ID; +import static org.reactivecommons.async.commons.Headers.REPLY_ID; +import static org.reactivecommons.async.commons.Headers.SERVED_QUERY_ID; public class TestStubs { @@ -12,7 +14,7 @@ public static Message mockMessage() { properties.getHeaders().put(REPLY_ID, "reply"); properties.getHeaders().put(CORRELATION_ID, "correlation"); properties.getHeaders().put(SERVED_QUERY_ID, "my-query"); - return new RabbitMessage("{\"id\":\"id\",\"name\":\"name\",\"date\":\"2020-10-22T17:03:26.062Z\"}".getBytes() - , properties); + return new RabbitMessage("{\"id\":\"id\",\"name\":\"name\",\"date\":\"2020-10-22T17:03:26.062Z\"}".getBytes(), + properties, null); } } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java index b7b769cc..111c63c6 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/DynamicRegistryImpTest.java @@ -11,6 +11,7 @@ import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.config.IBrokerConfigProps; import org.reactivecommons.async.rabbit.communications.TopologyCreator; import reactor.core.publisher.Mono; @@ -22,7 +23,10 @@ import java.util.concurrent.ConcurrentHashMap; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static reactor.core.publisher.Mono.just; @@ -43,10 +47,10 @@ class DynamicRegistryImpTest { @BeforeEach @SuppressWarnings("rawtypes") void setUp() { - Map> commandHandlers = new ConcurrentHashMap<>(); - Map> eventListeners = new ConcurrentHashMap<>(); - Map> eventsToBind = new ConcurrentHashMap<>(); - Map> notificationEventListeners = new ConcurrentHashMap<>(); + Map> commandHandlers = new ConcurrentHashMap<>(); + Map> eventListeners = new ConcurrentHashMap<>(); + Map> eventsToBind = new ConcurrentHashMap<>(); + Map> notificationEventListeners = new ConcurrentHashMap<>(); Map> queryHandlers = new ConcurrentHashMap<>(); resolver = new HandlerResolver(queryHandlers, eventListeners, eventsToBind, notificationEventListeners, commandHandlers); dynamicRegistry = new DynamicRegistryImp(resolver, topologyCreator, props); @@ -58,7 +62,7 @@ void registerEventListener() { when(topologyCreator.bind(any())).thenReturn(just(mock(BindOk.class))); dynamicRegistry.listenEvent("event1", message -> Mono.empty(), Long.class); - final RegisteredEventListener listener = resolver.getEventListener("event1"); + final RegisteredEventListener listener = resolver.getEventListener("event1"); assertThat(listener).isNotNull(); } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java index 0f696fa9..f7e0d099 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/HandlerResolverTest.java @@ -6,6 +6,7 @@ import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; +import org.reactivecommons.async.commons.HandlerResolver; import reactor.core.publisher.Mono; import java.util.Collection; @@ -17,23 +18,35 @@ class HandlerResolverTest { @BeforeEach void setup() { - Map> commandHandlers = new ConcurrentHashMap<>(); - Map> eventListeners = new ConcurrentHashMap<>(); - eventListeners.put("event.name", new RegisteredEventListener<>("event.name", message -> Mono.empty(), String.class)); - eventListeners.put("event.name2", new RegisteredEventListener<>("event.name2", message -> Mono.empty(), String.class)); - eventListeners.put("some.*", new RegisteredEventListener<>("some.*", message -> Mono.empty(), String.class)); - Map> eventsToBind = new ConcurrentHashMap<>(); - eventsToBind.put("event.name", new RegisteredEventListener<>("event.name", message -> Mono.empty(), String.class)); - eventsToBind.put("event.name2", new RegisteredEventListener<>("event.name2", message -> Mono.empty(), String.class)); - Map> notificationEventListeners = new ConcurrentHashMap<>(); + Map> commandHandlers = new ConcurrentHashMap<>(); + Map> eventListeners = new ConcurrentHashMap<>(); + eventListeners.put("event.name", new RegisteredEventListener<>( + "event.name", message -> Mono.empty(), String.class) + ); + eventListeners.put("event.name2", new RegisteredEventListener<>( + "event.name2", message -> Mono.empty(), String.class) + ); + eventListeners.put("some.*", new RegisteredEventListener<>( + "some.*", message -> Mono.empty(), String.class) + ); + Map> eventsToBind = new ConcurrentHashMap<>(); + eventsToBind.put("event.name", new RegisteredEventListener<>( + "event.name", message -> Mono.empty(), String.class) + ); + eventsToBind.put("event.name2", new RegisteredEventListener<>( + "event.name2", message -> Mono.empty(), String.class) + ); + Map> notificationEventListeners = new ConcurrentHashMap<>(); Map> queryHandlers = new ConcurrentHashMap<>(); - resolver = new HandlerResolver(queryHandlers, eventListeners, eventsToBind, notificationEventListeners, commandHandlers); + resolver = new HandlerResolver( + queryHandlers, eventListeners, eventsToBind, notificationEventListeners, commandHandlers + ); } @Test void shouldGetOnlyTheBindingEvents() { // Act - Collection> eventListener = resolver.getEventListeners(); + Collection> eventListener = resolver.getEventListeners(); // Assert Assertions.assertThat(eventListener.size()).isEqualTo(2); } @@ -41,17 +54,37 @@ void shouldGetOnlyTheBindingEvents() { @Test void shouldMatchForAWildcardEvent() { // Act - RegisteredEventListener eventListener = resolver.getEventListener("some.sample"); + RegisteredEventListener eventListener = resolver.getEventListener("some.sample"); // Assert Assertions.assertThat(eventListener.getPath()).isEqualTo("some.*"); } + @Test + void shouldThrowError() { + // Arrange + RegisteredQueryHandler handler = new RegisteredQueryHandler<>("*", + (from, message) -> Mono.empty(), + String.class); + // Act + // Assert + Assertions.assertThatThrownBy(() -> resolver.addQueryHandler(handler)).isInstanceOf(RuntimeException.class); + } + @Test void shouldMatchForAnExactEvent() { // Act - RegisteredEventListener eventListener = resolver.getEventListener("event.name"); + RegisteredEventListener eventListener = resolver.getEventListener("event.name"); // Assert Assertions.assertThat(eventListener.getPath()).isEqualTo("event.name"); } + @Test + void shouldCheckIfHasListenerTypes() { + // Act + // Assert + Assertions.assertThat(resolver.hasCommandHandlers()).isFalse(); + Assertions.assertThat(resolver.hasQueryHandlers()).isFalse(); + Assertions.assertThat(resolver.hasNotificationListeners()).isFalse(); + } + } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/QueryExecutorTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/QueryExecutorTest.java index 0324dc06..777f43aa 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/QueryExecutorTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/QueryExecutorTest.java @@ -8,10 +8,10 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.async.api.From; import org.reactivecommons.async.api.handlers.QueryHandlerDelegate; +import org.reactivecommons.async.commons.QueryExecutor; +import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.helpers.SampleClass; import org.reactivecommons.async.helpers.TestStubs; -import org.reactivecommons.async.commons.communications.Message; -import org.reactivecommons.async.commons.QueryExecutor; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java index 5522ad75..05c408a5 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDirectAsyncGatewayTest.java @@ -19,7 +19,7 @@ import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -59,7 +59,7 @@ class RabbitDirectAsyncGatewayTest { private final BrokerConfig config = new BrokerConfig(); private final Semaphore semaphore = new Semaphore(0); - private final MessageConverter converter = new JacksonMessageConverter(new DefaultObjectMapperSupplier().get()); + private final MessageConverter converter = new RabbitJacksonMessageConverter(new DefaultObjectMapperSupplier().get()); @Mock private ReactiveReplyRouter router; @Mock @@ -68,14 +68,14 @@ class RabbitDirectAsyncGatewayTest { private final MeterRegistry meterRegistry = new SimpleMeterRegistry(); private RabbitDirectAsyncGateway asyncGateway; - public void init(ReactiveMessageSender sender) { + void init(ReactiveMessageSender sender) { asyncGateway = new RabbitDirectAsyncGateway(config, router, sender, "exchange", converter, meterRegistry); } @Test void shouldReleaseRouterResourcesOnTimeout() { - BrokerConfig config = new BrokerConfig(false, false, false, Duration.ofSeconds(1)); - asyncGateway = new RabbitDirectAsyncGateway(config, router, senderMock, "ex", converter, meterRegistry); + BrokerConfig brokerConfig = new BrokerConfig(false, false, false, Duration.ofSeconds(1)); + asyncGateway = new RabbitDirectAsyncGateway(brokerConfig, router, senderMock, "ex", converter, meterRegistry); when(router.register(anyString())).thenReturn(Mono.never()); when(senderMock.sendNoConfirm(any(), anyString(), anyString(), anyMap(), anyBoolean())) .thenReturn(Mono.empty()); @@ -98,8 +98,10 @@ void shouldSendInOptimalTime() throws InterruptedException { int messageCount = 40000; final Flux> messages = createMessagesHot(messageCount); final Flux target = - messages.flatMap(dummyMessageCommand -> asyncGateway.sendCommand(dummyMessageCommand, "testTarget") - .doOnSuccess(aVoid -> semaphore.release())); + messages.flatMap(dummyMessageCommand -> + asyncGateway.sendCommand(dummyMessageCommand, "testTarget") + .doOnSuccess(aVoid -> semaphore.release()) + ); final long init = System.currentTimeMillis(); target.subscribe(); @@ -118,7 +120,7 @@ void shouldSendInOptimalTime() throws InterruptedException { @Test @SuppressWarnings("unchecked") - public void shouldReplyQuery() { + void shouldReplyQuery() { // Arrange senderMock(); @@ -132,13 +134,15 @@ public void shouldReplyQuery() { StepVerifier.create(result).verifyComplete(); ArgumentCaptor> headersCaptor = ArgumentCaptor.forClass(Map.class); verify(senderMock, times(1)) - .sendNoConfirm(eq(response), eq("globalReply"), eq("replyId"), headersCaptor.capture(), anyBoolean()); + .sendNoConfirm(eq(response), eq("globalReply"), eq("replyId"), + headersCaptor.capture(), anyBoolean() + ); assertThat(headersCaptor.getValue().get(CORRELATION_ID)).isEqualTo("correlationId"); } @Test @SuppressWarnings("unchecked") - public void shouldReplyQueryWithout() { + void shouldReplyQueryWithout() { // Arrange senderMock(); @@ -158,7 +162,7 @@ public void shouldReplyQueryWithout() { @Test @SuppressWarnings("unchecked") - public void shouldHandleRequestReply() throws JsonProcessingException { + void shouldHandleRequestReply() throws JsonProcessingException { // Arrange senderMock(); mockReply(); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDomainEventBusTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDomainEventBusTest.java new file mode 100644 index 00000000..ed03150d --- /dev/null +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitDomainEventBusTest.java @@ -0,0 +1,84 @@ +package org.reactivecommons.async.rabbit; + +import io.cloudevents.CloudEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.RawMessage; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class RabbitDomainEventBusTest { + @Mock + private DomainEvent domainEvent; + @Mock + private CloudEvent cloudEvent; + @Mock + private RawMessage rawMessage; + @Mock + private ReactiveMessageSender sender; + private RabbitDomainEventBus rabbitDomainEventBus; + private final String domain = "domain"; + + @BeforeEach + void setUp() { + rabbitDomainEventBus = new RabbitDomainEventBus(sender, "exchange"); + } + + @Test + void shouldEmitDomainEvent() { + // Arrange + when(domainEvent.getName()).thenReturn("event"); + when(sender.sendWithConfirm(any(DomainEvent.class), anyString(), anyString(), any(), anyBoolean())) + .thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(rabbitDomainEventBus.emit(domainEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void shouldEmitCloudEvent() { + // Arrange + when(cloudEvent.getType()).thenReturn("event"); + when(sender.sendWithConfirm(any(CloudEvent.class), anyString(), anyString(), any(), anyBoolean())) + .thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(rabbitDomainEventBus.emit(cloudEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void shouldEmitRawMessage() { + // Arrange + when(rawMessage.getType()).thenReturn("event"); + when(sender.sendWithConfirm(any(RawMessage.class), anyString(), anyString(), any(), anyBoolean())) + .thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(rabbitDomainEventBus.emit(rawMessage)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + } + + @Test + void operationsShouldNotBeAbleForDomains() { + assertThrows(UnsupportedOperationException.class, () -> rabbitDomainEventBus.emit(domain, domainEvent)); + assertThrows(UnsupportedOperationException.class, () -> rabbitDomainEventBus.emit(domain, cloudEvent)); + assertThrows(UnsupportedOperationException.class, () -> rabbitDomainEventBus.emit(domain, rawMessage)); + } +} diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java index c7b48327..f93864ab 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/RabbitMessageTest.java @@ -4,7 +4,6 @@ import com.rabbitmq.client.Delivery; import com.rabbitmq.client.Envelope; import org.junit.jupiter.api.Test; -import org.reactivecommons.async.rabbit.RabbitMessage; import java.util.HashMap; import java.util.Map; @@ -17,7 +16,9 @@ class RabbitMessageTest { void shouldCreateFromDelivery() { Envelope env = new Envelope(2, false, "exchange", "routeKey"); Map headers = new HashMap<>(); - AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().contentType("content").contentEncoding("utf8").headers(headers).build(); + AMQP.BasicProperties props = new AMQP.BasicProperties.Builder() + .contentType("content").contentEncoding("utf8").headers(headers) + .build(); byte[] body = new byte[]{3, 4, 5, 6}; Delivery delivery = new Delivery(env, props, body); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java index 1c499905..95e74a4e 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/communications/ReactiveMessageSenderTest.java @@ -10,8 +10,7 @@ import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -26,10 +25,9 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) +@SuppressWarnings("unchecked") class ReactiveMessageSenderTest { - private final String sourceApplication = "TestApp"; - private ReactiveMessageSender messageSender; private final ObjectMapper objectMapper = new ObjectMapper(); @@ -38,18 +36,17 @@ class ReactiveMessageSenderTest { private Sender sender; @Spy - private final MessageConverter messageConverter = new JacksonMessageConverter(objectMapper); + private final MessageConverter messageConverter = new RabbitJacksonMessageConverter(objectMapper); @BeforeEach public void init() { when(sender.sendWithTypedPublishConfirms(any(Publisher.class))).then(invocation -> { final Flux argument = invocation.getArgument(0); - return argument.map(myOutboundMessage -> { - OutboundMessageResult outboundMessageResult = new OutboundMessageResult<>(myOutboundMessage, true); - return outboundMessageResult; - }); + return argument + .map(myOutboundMessage -> new OutboundMessageResult<>(myOutboundMessage, true)); }); when(sender.send(any(Publisher.class))).thenReturn(Mono.empty()); + String sourceApplication = "TestApp"; messageSender = new ReactiveMessageSender(sender, sourceApplication, messageConverter, null); } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java index 03335367..2136f319 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/CloudEventBuilderExtTest.java @@ -1,18 +1,16 @@ package org.reactivecommons.async.rabbit.converters.json; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; import java.util.Date; import static org.assertj.core.api.Assertions.assertThat; -public class CloudEventBuilderExtTest { - - private final ObjectMapper objectMapper = new ObjectMapper(); +class CloudEventBuilderExtTest { @Test - void asBytes(){ + void asBytes() { Date date = new Date(); SampleClass result = new SampleClass("35", "name1", date); byte[] arrayByte = CloudEventBuilderExt.asBytes(result); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverterTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverterTest.java deleted file mode 100644 index 71da4ecc..00000000 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonCloudEventMessageConverterTest.java +++ /dev/null @@ -1,171 +0,0 @@ -package org.reactivecommons.async.rabbit.converters.json; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.cloudevents.CloudEvent; -import io.cloudevents.core.builder.CloudEventBuilder; -import org.junit.jupiter.api.Test; -import org.reactivecommons.api.domain.Command; -import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.async.api.AsyncQuery; -import org.reactivecommons.async.commons.communications.Message; - -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.time.OffsetDateTime; -import java.util.Date; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; - -public class JacksonCloudEventMessageConverterTest { - - private final ObjectMapper objectMapper = new ObjectMapper(); - private final JacksonCloudEventMessageConverter converter = new JacksonCloudEventMessageConverter(objectMapper); - - @Test - void readAsyncQuery() throws JsonProcessingException { - Date date = new Date(); - CloudEvent query = CloudEventBuilder.v1() // - .withId(UUID.randomUUID().toString()) // - .withSource(URI.create("https://spring.io/foos"))// - .withType("query") - .withData("application/json", CloudEventBuilderExt.asBytes(new SampleClass("35", "name1", date))) - .build(); - final Message message = converter.toMessage(new AsyncQuery<>(query.getType(), query)); - final AsyncQuery value = converter.readAsyncQuery(message, CloudEvent.class); - byte[] bytes = value.getQueryData().getData().toBytes(); - String stringData = new String(bytes, StandardCharsets.UTF_8); - SampleClass result = objectMapper.readValue(stringData, SampleClass.class); - assertThat(result) - .extracting(SampleClass::getId, SampleClass::getName, SampleClass::getDate) - .containsExactly("35", "name1", date); - - } - - @Test - void readDomainEvent() throws JsonProcessingException { - Date date = new Date(); - CloudEvent event = CloudEventBuilder.v1() // - .withId(UUID.randomUUID().toString()) // - .withSource(URI.create("https://spring.io/foos"))// - .withType("event") - .withData("application/json", CloudEventBuilderExt.asBytes(new SampleClass("35", "name1", date))) - .build(); - final Message message = converter.toMessage(new DomainEvent<>(event.getType(), event.getId(), event)); - final DomainEvent value = converter.readDomainEvent(message, CloudEvent.class); - byte[] bytes = value.getData().getData().toBytes(); - String stringData = new String(bytes, StandardCharsets.UTF_8); - SampleClass result = objectMapper.readValue(stringData, SampleClass.class); - assertThat(result) - .extracting(SampleClass::getId, SampleClass::getName, SampleClass::getDate) - .containsExactly("35", "name1", date); - } - - @Test - void readCommand() throws JsonProcessingException { - Date date = new Date(); - CloudEvent command = CloudEventBuilder.v1() // - .withId(UUID.randomUUID().toString()) // - .withSource(URI.create("https://spring.io/foos"))// - .withType("command") - .withData("application/json", CloudEventBuilderExt.asBytes(new SampleClass("35", "name1", date))) - .build(); - final Message message = converter.toMessage(new Command<>(command.getType(), command.getId(), command)); - final Command value = converter.readCommand(message, CloudEvent.class); - byte[] bytes = value.getData().getData().toBytes(); - String stringData = new String(bytes, StandardCharsets.UTF_8); - SampleClass result = objectMapper.readValue(stringData, SampleClass.class); - assertThat(result) - .extracting(SampleClass::getId, SampleClass::getName, SampleClass::getDate) - .containsExactly("35", "name1", date); - } - @Test - void toMessage() { - final Message message = converter.toMessage(new SampleClass("42", "Daniel", new Date())); - assertThat(new String(message.getBody())).contains("42").contains("Daniel"); - } - - @Test - void toMessageWhenDataIsNull() throws IOException { - final Message message = converter.toMessage(null); - - final JsonNode jsonNode = objectMapper.readTree(message.getBody()); - assertThat(jsonNode.isNull()).isTrue(); - } - - @Test - void toMessageWhenDataIsEmpty() throws IOException { - final Message message = converter.toMessage(""); - - final JsonNode jsonNode = objectMapper.readTree(message.getBody()); - assertThat(jsonNode.asText()).isEmpty(); - } - - @Test - void readValue() { - Date date = new Date(); - final Message message = converter.toMessage(new SampleClass("35", "name1", date)); - final SampleClass value = converter.readValue(message, SampleClass.class); - assertThat(value).extracting(SampleClass::getId, SampleClass::getName, SampleClass::getDate) - .containsExactly("35", "name1", date); - } - - @Test - void readValueString() { - final Message message = converter.toMessage("Hi!"); - final String value = converter.readValue(message, String.class); - assertThat(value).isEqualTo("Hi!"); - } - - @Test - void shouldConvertToCommandStructure() { - final SampleClass data = new SampleClass("35", "name1", new Date()); - final Message message = converter.toMessage(new Command<>("cmd.name", "42", data)); - final Command command = converter.readCommandStructure(message); - - assertThat(command.getData()).isInstanceOf(JsonNode.class); - assertThat(command.getName()).isEqualTo("cmd.name"); - } - - @Test - void shouldConvertToDomainEventStructure() { - final SampleClass data = new SampleClass("35", "name1", new Date()); - final Message message = converter.toMessage(new DomainEvent<>("event.name", "42", data)); - final DomainEvent event = converter.readDomainEventStructure(message); - - assertThat(event.getData()).isInstanceOf(JsonNode.class); - assertThat(event.getName()).isEqualTo("event.name"); - final JsonNode jsonNode = (JsonNode) event.getData(); - assertThat(jsonNode.findValue("name").asText()).isEqualTo("name1"); - } - - @Test - void shouldConvertToQueryStructure() { - final SampleClass data = new SampleClass("35", "sample1", new Date()); - final Message message = converter.toMessage(new AsyncQuery<>("query.name", data)); - final AsyncQuery query = converter.readAsyncQueryStructure(message); - - assertThat(query.getQueryData()).isInstanceOf(JsonNode.class); - assertThat(query.getResource()).isEqualTo("query.name"); - final JsonNode jsonNode = (JsonNode) query.getQueryData(); - assertThat(jsonNode.findValue("name").asText()).isEqualTo("sample1"); - } - - @Test - void shouldNotFailWithTilde() { - // Arrange - final String name = "example with word containing tilde áéíóúñ"; - final SampleClass data = new SampleClass("35", name, new Date()); - final Message message = converter.toMessage(new AsyncQuery<>("query.name", data)); - // Act - final AsyncQuery query = converter.readAsyncQueryStructure(message); - // Assert - assertThat(query.getQueryData()).isInstanceOf(JsonNode.class); - assertThat(query.getResource()).isEqualTo("query.name"); - final JsonNode jsonNode = (JsonNode) query.getQueryData(); - assertThat(jsonNode.findValue("name").asText()).isEqualTo(name); - } -} diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java index bae8220e..1e39a7ad 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/JacksonMessageConverterTest.java @@ -2,25 +2,40 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.jackson.JsonCloudEventData; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.reactivecommons.api.domain.Command; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.async.api.AsyncQuery; import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; import java.io.IOException; +import java.net.URI; import java.util.Date; +import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; class JacksonMessageConverterTest { - private final ObjectMapper objectMapper = new ObjectMapper(); - private final JacksonMessageConverter converter = new JacksonMessageConverter(objectMapper); + private static RabbitJacksonMessageConverter converter; + private static ObjectMapper objectMapper; + + @BeforeAll + static void setUp() { + objectMapper = new DefaultObjectMapperSupplier().get(); + converter = new RabbitJacksonMessageConverter(objectMapper); + } @Test void toMessage() { final Message message = converter.toMessage(new SampleClass("42", "Daniel", new Date())); + assertEquals(message, converter.toMessage(message)); assertThat(new String(message.getBody())).contains("42").contains("Daniel"); } @@ -49,6 +64,23 @@ void readValue() { .containsExactly("35", "name1", date); } + @Test + void readCloudEvent() { + Date date = new Date(); + CloudEvent command = CloudEventBuilder.v1() // + .withId(UUID.randomUUID().toString()) // + .withSource(URI.create("https://spring.io/foos"))// + .withType("command") + .withData("application/json", JsonCloudEventData.wrap( + objectMapper.valueToTree(new SampleClass("35", "name1", date))) + ) + .build(); + Message message = converter.toMessage(command); + CloudEvent result = converter.readCloudEvent(message); + + assertThat(result).usingRecursiveComparison().isEqualTo(command); + } + @Test void readValueString() { final Message message = converter.toMessage("Hi!"); diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/SampleClass.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/SampleClass.java index 151c4809..705c3da8 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/SampleClass.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/converters/json/SampleClass.java @@ -1,14 +1,16 @@ package org.reactivecommons.async.rabbit.converters.json; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; import java.util.Date; -@RequiredArgsConstructor -@Getter +@Data +@AllArgsConstructor +@NoArgsConstructor class SampleClass { - private final String id; - private final String name; - private final Date date; + private String id; + private String name; + private Date date; } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/ext/CustomReporterTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/ext/CustomReporterTest.java index f3802467..672cf727 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/ext/CustomReporterTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/ext/CustomReporterTest.java @@ -1,10 +1,11 @@ -package org.reactivecommons.async.commons.ext; +package org.reactivecommons.async.rabbit.ext; import org.junit.jupiter.api.Test; import org.reactivecommons.api.domain.Command; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.async.api.AsyncQuery; import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.ext.CustomReporter; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import reactor.test.publisher.PublisherProbe; diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java index ba05c195..3b63f13b 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerPerfTest.java @@ -10,64 +10,73 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.Command; import org.reactivecommons.async.api.DefaultCommandHandler; import org.reactivecommons.async.api.HandlerRegistry; import org.reactivecommons.async.api.handlers.registered.RegisteredCommandHandler; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import org.reactivecommons.async.utils.TestUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.rabbitmq.AcknowledgableDelivery; -import reactor.rabbitmq.BindingSpecification; -import reactor.rabbitmq.ExchangeSpecification; import reactor.rabbitmq.Receiver; import java.math.BigInteger; import java.time.Duration; -import java.util.*; -import java.util.concurrent.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; +import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.stream.IntStream; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; import static reactor.core.publisher.Flux.range; -import static reactor.core.publisher.Mono.just; @ExtendWith(MockitoExtension.class) class ApplicationCommandListenerPerfTest { + private static final CountDownLatch latch = new CountDownLatch(12 + 1); + private static final int MESSAGE_COUNT = 40000; + private final Semaphore semaphore = new Semaphore(0); @Mock private Receiver receiver; - @Mock private TopologyCreator topologyCreator; - @Mock private DiscardNotifier discardNotifier; - @Mock private CustomReporter errorReporter; - private StubGenericMessageListener messageListener; - private static final CountDownLatch latch = new CountDownLatch(12 + 1); - - private static final int messageCount = 40000; - private final Semaphore semaphore = new Semaphore(0); - private MessageConverter messageConverter = new JacksonMessageConverter(new DefaultObjectMapperSupplier().get()); + private final MessageConverter messageConverter = new RabbitJacksonMessageConverter( + new DefaultObjectMapperSupplier().get() + ); private ReactiveMessageListener reactiveMessageListener; + private static BigInteger makeHardWork() { + final long number = ThreadLocalRandom.current().nextLong(100) + 2700; + BigInteger fact = new BigInteger("1"); + for (long i = 1; i <= number; i++) { + fact = fact.multiply(BigInteger.valueOf(i)); + } + return fact; + } + @BeforeEach public void setUp() { reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator); @@ -92,19 +101,21 @@ void shouldProcessMessagesInOptimalTime() throws JsonProcessingException, Interr HandlerResolver handlerResolver = createHandlerResolver(HandlerRegistry.register() .handleCommand("app.command.test", this::handleTestMessage, DummyMessage.class) ); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, discardNotifier, "command", handlerResolver, messageConverter, errorReporter); - Flux messageFlux = createSource(messageCount); + messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, + discardNotifier, "command", handlerResolver, messageConverter, errorReporter + ); + Flux messageFlux = createSource(MESSAGE_COUNT); TestUtils.instructSafeReceiverMock(receiver, messageFlux); messageListener.startListener(); final long init = System.currentTimeMillis(); latch.countDown(); - semaphore.acquire(messageCount); + semaphore.acquire(MESSAGE_COUNT); final long end = System.currentTimeMillis(); final long total = end - init; - final double microsPerMessage = ((total + 0.0) / messageCount) * 1000; - System.out.println("Message count: " + messageCount); + final double microsPerMessage = ((total + 0.0) / MESSAGE_COUNT) * 1000; + System.out.println("Message count: " + MESSAGE_COUNT); System.out.println("Total Execution Time: " + total + "ms"); System.out.println("Microseconds per message: " + microsPerMessage + "us"); if (System.getProperty("env.ci") == null) { @@ -121,18 +132,20 @@ void shouldProcessAsyncMessagesConcurrent() throws JsonProcessingException, Inte HandlerResolver handlerResolver = createHandlerResolver(HandlerRegistry.register() .handleCommand("app.command.test", this::handleTestMessageDelay, DummyMessage.class) ); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, discardNotifier, "command", handlerResolver, messageConverter, errorReporter); - Flux messageFlux = createSource(messageCount); + messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, + discardNotifier, "command", handlerResolver, messageConverter, errorReporter + ); + Flux messageFlux = createSource(MESSAGE_COUNT); TestUtils.instructSafeReceiverMock(receiver, messageFlux); System.out.println("Permits before: " + semaphore.availablePermits()); final long init = System.currentTimeMillis(); messageListener.startListener(); - semaphore.acquire(messageCount); + semaphore.acquire(MESSAGE_COUNT); final long end = System.currentTimeMillis(); final long total = end - init; - final double microsPerMessage = ((total + 0.0) / messageCount) * 1000; - System.out.println("Message count: " + messageCount); + final double microsPerMessage = ((total + 0.0) / MESSAGE_COUNT) * 1000; + System.out.println("Message count: " + MESSAGE_COUNT); System.out.println("Total Execution Time: " + total + "ms"); System.out.println("Microseconds per message: " + microsPerMessage + "us"); if (System.getProperty("env.ci") == null) { @@ -169,15 +182,6 @@ private void liveLock(int delay) { for (long end = System.currentTimeMillis() + delay; System.currentTimeMillis() < end; ) ; } - private static BigInteger makeHardWork() { - final long number = ThreadLocalRandom.current().nextLong(100) + 2700; - BigInteger fact = new BigInteger("1"); - for (long i = 1; i <= number; i++) { - fact = fact.multiply(BigInteger.valueOf(i)); - } - return fact; - } - @Test void shouldProcessCPUMessagesInParallel() throws JsonProcessingException, InterruptedException { @@ -186,7 +190,9 @@ void shouldProcessCPUMessagesInParallel() throws JsonProcessingException, Interr ); int messageCount = 2000; reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator, 250, 250); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, discardNotifier, "command", handlerResolver, messageConverter, errorReporter); + messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, + discardNotifier, "command", handlerResolver, messageConverter, errorReporter + ); Flux messageFlux = createSource(messageCount); TestUtils.instructSafeReceiverMock(receiver, messageFlux); @@ -214,7 +220,8 @@ void shouldProcessCPUWorkMessagesInParallel() throws JsonProcessingException, In ); int messageCount = 2000; reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator, 500, 250); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, discardNotifier, "command", handlerResolver, messageConverter, errorReporter); + messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, + discardNotifier, "command", handlerResolver, messageConverter, errorReporter); Flux messageFlux = createSource(messageCount); TestUtils.instructSafeReceiverMock(receiver, messageFlux); @@ -242,7 +249,8 @@ void shouldProcessPasiveBlockingMessagesInParallel() throws JsonProcessingExcept ); int messageCount = 2000; reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator, 500, 250); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, discardNotifier, "command", handlerResolver, messageConverter, errorReporter); + messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, 10, + discardNotifier, "command", handlerResolver, messageConverter, errorReporter); Flux messageFlux = createSource(messageCount); TestUtils.instructSafeReceiverMock(receiver, messageFlux); @@ -264,20 +272,26 @@ void shouldProcessPasiveBlockingMessagesInParallel() throws JsonProcessingExcept } private HandlerResolver createHandlerResolver(final HandlerRegistry initialRegistry) { - final HandlerRegistry registry = range(0, 20).reduce(initialRegistry, (r, i) -> r.handleCommand("app.command.name" + i, message -> Mono.empty(), Map.class)).block(); - final ConcurrentMap> commandHandlers = registry.getCommandHandlers().stream() - .collect(ConcurrentHashMap::new, (map, handler) -> map.put(handler.getPath(), handler), ConcurrentHashMap::putAll); + final HandlerRegistry registry = range(0, 20) + .reduce(initialRegistry, (r, i) -> + r.handleCommand("app.command.name" + i, message -> Mono.empty(), Map.class) + ) + .block(); + final ConcurrentMap> commandHandlers = registry.getCommandHandlers() + .get(DEFAULT_DOMAIN) + .stream() + .collect(ConcurrentHashMap::new, (map, handler) -> + map.put(handler.getPath(), handler), ConcurrentHashMap::putAll + ); return new HandlerResolver(null, null, null, null, commandHandlers) { @Override @SuppressWarnings("unchecked") - public RegisteredCommandHandler getCommandHandler(String path) { - final RegisteredCommandHandler handler = super.getCommandHandler(path); - return handler != null ? handler : new RegisteredCommandHandler("", new DefaultCommandHandler() { - @Override - public Mono handle(Command message) { - return Mono.error(new RuntimeException("Default handler in Test")); - } - }, Object.class); + public RegisteredCommandHandler getCommandHandler(String path) { + final RegisteredCommandHandler handler = super.getCommandHandler(path); + return handler != null ? handler : new RegisteredCommandHandler<>( + "", (DefaultCommandHandler) message -> + Mono.error(new RuntimeException("Default handler in Test")), Object.class + ); } }; } @@ -285,7 +299,9 @@ public Mono handle(Command message) { private Flux createSource(int count) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); - Command command = new Command<>("app.command.test", UUID.randomUUID().toString(), new DummyMessage()); + Command command = new Command<>( + "app.command.test", UUID.randomUUID().toString(), new DummyMessage() + ); String data = mapper.writeValueAsString(command); final List list = IntStream.range(0, count).mapToObj(value -> { AMQP.BasicProperties props = new AMQP.BasicProperties(); @@ -315,10 +331,14 @@ private AMQP.BasicProperties createProps() { null); } - class StubGenericMessageListener extends ApplicationCommandListener { + static class StubGenericMessageListener extends ApplicationCommandListener { - public StubGenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, long maxRetries, DiscardNotifier discardNotifier, String objectType, HandlerResolver handlerResolver, MessageConverter messageConverter, CustomReporter errorReporter) { - super(listener, queueName, handlerResolver, "directExchange", messageConverter, true, false, false, 10, 10, Optional.empty(), discardNotifier, errorReporter); + public StubGenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, + long maxRetries, DiscardNotifier discardNotifier, String objectType, + HandlerResolver handlerResolver, MessageConverter messageConverter, + CustomReporter errorReporter) { + super(listener, queueName, handlerResolver, "directExchange", messageConverter, true, false, false, 10, 10, + Optional.empty(), discardNotifier, errorReporter); } } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java index cea00632..fa72d55a 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationCommandListenerTest.java @@ -5,7 +5,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.Command; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import reactor.core.publisher.Mono; import java.util.Optional; @@ -14,34 +14,48 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.longThat; import static org.mockito.Mockito.verify; -import static reactor.core.publisher.Mono.*; +import static reactor.core.publisher.Mono.error; @ExtendWith(MockitoExtension.class) -public class ApplicationCommandListenerTest extends ListenerReporterTestSuperClass{ +@SuppressWarnings("unchecked") +public class ApplicationCommandListenerTest extends ListenerReporterTestSuperClass { - private final Command command = new Command<>("app.command.test", UUID.randomUUID().toString(), new DummyMessage()); - private final Command command2 = new Command<>("app.command.test2", UUID.randomUUID().toString(), new DummyMessage()); + private final Command command = new Command<>( + "app.command.test", UUID.randomUUID().toString(), new DummyMessage() + ); + private final Command command2 = new Command<>( + "app.command.test2", UUID.randomUUID().toString(), new DummyMessage() + ); @Test void shouldSendErrorToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() - .handleCommand("app.command.test", m -> error(new RuntimeException("testEx")), DummyMessage.class); + .handleCommand("app.command.test", m -> + error(new RuntimeException("testEx")), DummyMessage.class + ); assertSendErrorToCustomReporter(registry, createSource(Command::getName, command)); } @Test void shouldSendErrorMetricToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() - .handleCommand("app.command.test", m -> error(new RuntimeException("testEx")), DummyMessage.class); + .handleCommand("app.command.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ); assertSendErrorToCustomReporter(registry, createSource(Command::getName, command)); - verify(errorReporter).reportMetric(eq("command"), eq("app.command.test"), longThat(time -> time > 0 ), eq(false)); + verify(errorReporter) + .reportMetric(eq("command"), eq("app.command.test"), longThat(time -> time >= 0), eq(false)); } @Test void shouldContinueAfterReportError() throws InterruptedException { final HandlerRegistry handlerRegistry = HandlerRegistry.register() - .handleCommand("app.command.test", m -> error(new RuntimeException("testEx")), DummyMessage.class) - .handleCommand("app.command.test2", m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class); + .handleCommand("app.command.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ) + .handleCommand("app.command.test2", + m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class + ); assertContinueAfterSendErrorToCustomReporter(handlerRegistry, createSource(Command::getName, command, command2)); } @@ -54,7 +68,8 @@ protected GenericMessageListener createMessageListener(HandlerResolver handlerRe class StubGenericMessageListener extends ApplicationCommandListener { public StubGenericMessageListener(HandlerResolver handlerResolver) { - super(reactiveMessageListener, "queueName", handlerResolver, "directExchange", messageConverter, true, false,false,10, 10, Optional.empty(), discardNotifier, errorReporter); + super(reactiveMessageListener, "queueName", handlerResolver, "directExchange", messageConverter, true, + false, false, 10, 10, Optional.empty(), discardNotifier, errorReporter); } } } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java index 4733aa4e..ff2b79b2 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationEventListenerTest.java @@ -1,38 +1,75 @@ package org.reactivecommons.async.rabbit.listeners; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import reactor.core.publisher.Mono; +import java.net.URI; import java.util.Optional; import java.util.UUID; import static reactor.core.publisher.Mono.error; @ExtendWith(MockitoExtension.class) +@SuppressWarnings("unchecked") public class ApplicationEventListenerTest extends ListenerReporterTestSuperClass { - private DomainEvent event1 = new DomainEvent<>("app.event.test", UUID.randomUUID().toString(), new DummyMessage()); - private DomainEvent event2 = new DomainEvent<>("app.event.test2", UUID.randomUUID().toString(), new DummyMessage()); + private final DomainEvent event1 = new DomainEvent<>( + "app.event.test", UUID.randomUUID().toString(), new DummyMessage() + ); + + private final DomainEvent event2 = new DomainEvent<>( + "app.event.test2", UUID.randomUUID().toString(), new DummyMessage() + ); + + private final CloudEvent cloudEvent = CloudEventBuilder.v1() + .withType("app.event.test") + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("/test")) + .withData("application/json", "{}".getBytes()) + .build(); @Test void shouldSendErrorToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() - .listenEvent("app.event.test", m -> error(new RuntimeException("testEx")), DummyMessage.class); + .listenEvent("app.event.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ); assertSendErrorToCustomReporter(registry, createSource(DomainEvent::getName, event1)); } + @Test + void shouldResolveCorrectCloudEventHandler() throws InterruptedException { + final HandlerRegistry registry = HandlerRegistry.register() + .listenCloudEvent("app.event.test", m -> error(new RuntimeException("testEx"))); + assertSendErrorToCustomReporter(registry, createSource(CloudEvent::getType, cloudEvent)); + } + + @Test + void shouldResolveCorrectRawHandler() throws InterruptedException { + final HandlerRegistry registry = HandlerRegistry.register() + .listenDomainRawEvent("domain","app.event.test", m -> error(new RuntimeException("testEx"))); + assertSendErrorToCustomReporter(registry, createSource(CloudEvent::getType, cloudEvent)); + } + @Test void shouldContinueAfterReportError() throws InterruptedException { final HandlerRegistry handlerRegistry = HandlerRegistry.register() - .listenEvent("app.event.test", m -> error(new RuntimeException("testEx")), DummyMessage.class) - .listenEvent("app.event.test2", m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class); + .listenEvent("app.event.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ) + .listenEvent("app.event.test2", + m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class + ); - assertContinueAfterSendErrorToCustomReporter(handlerRegistry, createSource(DomainEvent::getName, event1, event2)); + assertContinueAfterSendErrorToCustomReporter(handlerRegistry, createSource(DomainEvent::getName, event1, + event2)); } @Override @@ -42,7 +79,8 @@ protected GenericMessageListener createMessageListener(HandlerResolver handlerRe class StubGenericMessageListener extends ApplicationEventListener { public StubGenericMessageListener(HandlerResolver handlerResolver) { - super(reactiveMessageListener, "queueName", "domainEvents", handlerResolver, messageConverter, true, true,10, 10, Optional.empty(), discardNotifier, errorReporter, ""); + super(reactiveMessageListener, "queueName", "domainEvents", handlerResolver, messageConverter, true, true, + 10, 10, Optional.empty(), discardNotifier, errorReporter, ""); } } } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java index 2265b55a..afcc5fff 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationNotificationListenerTest.java @@ -8,7 +8,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import reactor.core.publisher.Mono; import reactor.rabbitmq.QueueSpecification; @@ -22,8 +22,12 @@ @ExtendWith(MockitoExtension.class) public class ApplicationNotificationListenerTest extends ListenerReporterTestSuperClass { - private DomainEvent event1 = new DomainEvent<>("app.event.test", UUID.randomUUID().toString(), new DummyMessage()); - private DomainEvent event2 = new DomainEvent<>("app.event.test2", UUID.randomUUID().toString(), new DummyMessage()); + private final DomainEvent event1 = new DomainEvent<>( + "app.event.test", UUID.randomUUID().toString(), new DummyMessage() + ); + private final DomainEvent event2 = new DomainEvent<>( + "app.event.test2", UUID.randomUUID().toString(), new DummyMessage() + ); @BeforeEach public void initCreator() { @@ -33,15 +37,20 @@ public void initCreator() { @Test void shouldSendErrorToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() - .listenNotificationEvent("app.event.test", m -> error(new RuntimeException("testEx")), DummyMessage.class); + .listenNotificationEvent("app.event.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class); assertSendErrorToCustomReporter(registry, createSource(DomainEvent::getName, event1)); } @Test void shouldContinueAfterReportError() throws InterruptedException { final HandlerRegistry handlerRegistry = HandlerRegistry.register() - .listenNotificationEvent("app.event.test", m -> error(new RuntimeException("testEx")), DummyMessage.class) - .listenNotificationEvent("app.event.test2", m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class); + .listenNotificationEvent("app.event.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ) + .listenNotificationEvent("app.event.test2", + m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class + ); assertContinueAfterSendErrorToCustomReporter(handlerRegistry, createSource(DomainEvent::getName, event1, event2)); } @@ -54,7 +63,8 @@ protected GenericMessageListener createMessageListener(HandlerResolver handlerRe class StubGenericMessageListener extends ApplicationNotificationListener { public StubGenericMessageListener(HandlerResolver handlerResolver) { - super(reactiveMessageListener, "exchange", "queue", true, handlerResolver, messageConverter, discardNotifier, errorReporter); + super(reactiveMessageListener, "exchange", "queue", true, handlerResolver, + messageConverter, discardNotifier, errorReporter); } } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java index a1cc33bc..a2ed493c 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerErrorTest.java @@ -5,7 +5,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.async.api.AsyncQuery; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import reactor.core.publisher.Mono; @@ -17,21 +17,27 @@ @ExtendWith(MockitoExtension.class) public class ApplicationQueryListenerErrorTest extends ListenerReporterTestSuperClass { - private AsyncQuery event1 = new AsyncQuery<>("app.query.test", new DummyMessage()); - private AsyncQuery event2 = new AsyncQuery<>("app.query.test2", new DummyMessage()); + private final AsyncQuery event1 = new AsyncQuery<>("app.query.test", new DummyMessage()); + private final AsyncQuery event2 = new AsyncQuery<>("app.query.test2", new DummyMessage()); @Test void shouldSendErrorToCustomErrorReporter() throws InterruptedException { final HandlerRegistry registry = HandlerRegistry.register() - .serveQuery("app.query.test", m -> error(new RuntimeException("testEx")), DummyMessage.class); + .serveQuery("app.query.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ); assertSendErrorToCustomReporter(registry, createSource(AsyncQuery::getResource, event1)); } @Test void shouldContinueAfterReportError() throws InterruptedException { final HandlerRegistry handlerRegistry = HandlerRegistry.register() - .serveQuery("app.query.test", m -> error(new RuntimeException("testEx")), DummyMessage.class) - .serveQuery("app.query.test2", m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class); + .serveQuery("app.query.test", + m -> error(new RuntimeException("testEx")), DummyMessage.class + ) + .serveQuery("app.query.test2", + m -> Mono.fromRunnable(successSemaphore::release), DummyMessage.class + ); assertContinueAfterSendErrorToCustomReporter(handlerRegistry, createSource(AsyncQuery::getResource, event1, event2)); } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerTest.java index 70b4a88e..a4e39f2d 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ApplicationQueryListenerTest.java @@ -8,29 +8,27 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.async.api.AsyncQuery; import org.reactivecommons.async.api.handlers.QueryHandler; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; import org.reactivecommons.async.commons.ext.CustomReporter; import org.reactivecommons.async.helpers.SampleClass; import org.reactivecommons.async.helpers.TestStubs; -import org.reactivecommons.async.rabbit.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import reactor.core.publisher.Mono; import reactor.rabbitmq.AcknowledgableDelivery; import reactor.rabbitmq.Receiver; import reactor.test.StepVerifier; -import reactor.test.publisher.PublisherProbe; import java.time.Instant; import java.util.Date; @@ -39,11 +37,23 @@ import java.util.Optional; import java.util.function.Function; -import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyMap; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import static org.mockito.Mockito.*; -import static org.reactivecommons.async.commons.Headers.*; -import static reactor.core.publisher.Mono.*; +import static org.reactivecommons.async.commons.Headers.CORRELATION_ID; +import static org.reactivecommons.async.commons.Headers.REPLY_ID; +import static org.reactivecommons.async.commons.Headers.REPLY_TIMEOUT_MILLIS; +import static org.reactivecommons.async.commons.Headers.SERVED_QUERY_ID; +import static reactor.core.publisher.Mono.empty; +import static reactor.core.publisher.Mono.error; +import static reactor.core.publisher.Mono.just; @ExtendWith(MockitoExtension.class) class ApplicationQueryListenerTest { @@ -51,7 +61,7 @@ class ApplicationQueryListenerTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final MessageConverter messageConverter = - new JacksonMessageConverter(new DefaultObjectMapperSupplier().get()); + new RabbitJacksonMessageConverter(new DefaultObjectMapperSupplier().get()); @Mock @@ -86,7 +96,7 @@ public void setUp() { HandlerResolver resolver = new HandlerResolver(handlers, null, null, null, null); applicationQueryListener = new ApplicationQueryListener(reactiveMessageListener, "queue", resolver, sender, "directExchange", messageConverter, "replyExchange", false, - true,1, 100, maxLengthBytes, true, discardNotifier, errorReporter); + true, 1, 100, maxLengthBytes, true, discardNotifier, errorReporter); } @Test diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java index 7f0f40c4..1016683b 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/GenericMessageListenerPerfTest.java @@ -12,11 +12,11 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.reactivecommons.api.domain.Command; -import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.commons.ext.CustomReporter; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.commons.ext.CustomReporter; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.rabbitmq.AcknowledgableDelivery; @@ -61,10 +61,12 @@ class GenericMessageListenerPerfTest { private final Semaphore semaphore = new Semaphore(0); @BeforeEach - public void init() { + void init() { // when(errorReporter.reportError(any(Throwable.class), any(Message.class), any(Object.class))).thenReturn(Mono.empty()); ReactiveMessageListener reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator); - messageListener = new StubGenericMessageListener("test-queue", reactiveMessageListener, true, true,10, discardNotifier, "command", errorReporter); + messageListener = new StubGenericMessageListener( + "test-queue", reactiveMessageListener, true, true, 10, discardNotifier, "command", errorReporter + ); } @@ -107,7 +109,9 @@ void referenceTime() throws JsonProcessingException, InterruptedException { private Flux createSource(int count) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); - Command command = new Command<>("some.command.name", UUID.randomUUID().toString(), new DummyMessage()); + Command command = new Command<>( + "some.command.name", UUID.randomUUID().toString(), new DummyMessage() + ); String data = mapper.writeValueAsString(command); final List list = IntStream.range(0, count).mapToObj(value -> { AMQP.BasicProperties props = new AMQP.BasicProperties(); @@ -139,8 +143,11 @@ private AMQP.BasicProperties createProps() { class StubGenericMessageListener extends GenericMessageListener { - public StubGenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, boolean createTopology, long maxRetries, DiscardNotifier discardNotifier, String objectType, CustomReporter errorReporter) { - super(queueName, listener, useDLQRetries, createTopology, maxRetries, 200, discardNotifier, objectType, errorReporter); + public StubGenericMessageListener(String queueName, ReactiveMessageListener listener, boolean useDLQRetries, + boolean createTopology, long maxRetries, DiscardNotifier discardNotifier, + String objectType, CustomReporter errorReporter) { + super(queueName, listener, useDLQRetries, createTopology, maxRetries, 200, discardNotifier, + objectType, errorReporter); } @Override @@ -563,7 +570,10 @@ public String basicConsume(String queue, boolean autoAck, String consumerTag, bo } @Override - public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, DeliverCallback deliverCallback, CancelCallback cancelCallback, ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { + public String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, + Map arguments, DeliverCallback deliverCallback, + CancelCallback cancelCallback, + ConsumerShutdownSignalCallback shutdownSignalCallback) throws IOException { return null; } diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java index 7e9b5ad2..da384558 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/rabbit/listeners/ListenerReporterTestSuperClass.java @@ -13,15 +13,15 @@ import org.reactivecommons.async.api.handlers.registered.RegisteredEventListener; import org.reactivecommons.async.api.handlers.registered.RegisteredQueryHandler; import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; import org.reactivecommons.async.commons.Headers; import org.reactivecommons.async.commons.communications.Message; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; import org.reactivecommons.async.commons.ext.CustomReporter; -import org.reactivecommons.async.rabbit.HandlerResolver; import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; import org.reactivecommons.async.rabbit.communications.TopologyCreator; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import org.reactivecommons.async.utils.TestUtils; import reactor.core.publisher.Flux; import reactor.rabbitmq.AcknowledgableDelivery; @@ -54,30 +54,41 @@ import static reactor.core.publisher.Mono.empty; import static reactor.core.publisher.Mono.just; +@SuppressWarnings("unchecked") public abstract class ListenerReporterTestSuperClass { - private final ObjectMapper mapper = new ObjectMapper(); - - private final Receiver receiver = mock(Receiver.class); protected final TopologyCreator topologyCreator = mock(TopologyCreator.class); protected final DiscardNotifier discardNotifier = mock(DiscardNotifier.class); - protected final MessageConverter messageConverter = new JacksonMessageConverter(new DefaultObjectMapperSupplier().get()); + protected final MessageConverter messageConverter = new RabbitJacksonMessageConverter( + new DefaultObjectMapperSupplier().get() + ); protected final CustomReporter errorReporter = mock(CustomReporter.class); - private GenericMessageListener messageListener; - protected final Semaphore semaphore = new Semaphore(0); protected final Semaphore successSemaphore = new Semaphore(0); - protected final ReactiveMessageListener reactiveMessageListener = new ReactiveMessageListener(receiver, topologyCreator); + private final ObjectMapper mapper = new DefaultObjectMapperSupplier().get(); + private final Receiver receiver = mock(Receiver.class); + protected final ReactiveMessageListener reactiveMessageListener = new ReactiveMessageListener( + receiver, topologyCreator + ); + private GenericMessageListener messageListener; @BeforeEach public void init() { - Mockito.when(topologyCreator.declare(any(ExchangeSpecification.class))).thenReturn(just(mock(AMQP.Exchange.DeclareOk.class))); - Mockito.when(topologyCreator.declareDLQ(any(String.class), any(String.class), any(Integer.class), any(Optional.class))).thenReturn(just(mock(AMQP.Queue.DeclareOk.class))); - Mockito.when(topologyCreator.declareQueue(any(String.class), any(String.class), any(Optional.class))).thenReturn(just(mock(AMQP.Queue.DeclareOk.class))); - Mockito.when(topologyCreator.bind(any(BindingSpecification.class))).thenReturn(just(mock(AMQP.Queue.BindOk.class))); + Mockito.when(topologyCreator.declare(any(ExchangeSpecification.class))) + .thenReturn(just(mock(AMQP.Exchange.DeclareOk.class))); + Mockito.when(topologyCreator.declareDLQ + (any(String.class), any(String.class), any(Integer.class), any(Optional.class)) + ) + .thenReturn(just(mock(AMQP.Queue.DeclareOk.class))); + Mockito.when(topologyCreator.declareQueue(any(String.class), any(String.class), any(Optional.class))) + .thenReturn(just(mock(AMQP.Queue.DeclareOk.class))); + Mockito.when(topologyCreator.bind(any(BindingSpecification.class))) + .thenReturn(just(mock(AMQP.Queue.BindOk.class))); } - protected void assertContinueAfterSendErrorToCustomReporter(HandlerRegistry handlerRegistry, Flux source) throws InterruptedException { + protected void assertContinueAfterSendErrorToCustomReporter(HandlerRegistry handlerRegistry, + Flux source) + throws InterruptedException { final HandlerResolver handlerResolver = createHandlerResolver(handlerRegistry); when(errorReporter.reportError(any(Throwable.class), any(Message.class), any(Object.class), any(Boolean.class))) .then(inv -> empty().doOnSuccess(o -> semaphore.release())); @@ -95,7 +106,8 @@ protected void assertContinueAfterSendErrorToCustomReporter(HandlerRegistry hand assertThat(processed).isTrue(); } - protected void assertSendErrorToCustomReporter(HandlerRegistry handlerRegistry, Flux source) throws InterruptedException { + protected void assertSendErrorToCustomReporter(HandlerRegistry handlerRegistry, Flux source) + throws InterruptedException { final HandlerResolver handlerResolver = createHandlerResolver(handlerRegistry); when(errorReporter.reportError(any(Throwable.class), any(Message.class), any(Object.class), any(Boolean.class))) .then(inv -> empty().doOnSuccess(o -> semaphore.release())); @@ -109,7 +121,8 @@ protected void assertSendErrorToCustomReporter(HandlerRegistry handlerRegistry, ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class); final boolean reported = semaphore.tryAcquire(1, TimeUnit.SECONDS); assertThat(reported).isTrue(); - verify(errorReporter).reportError(throwableCaptor.capture(), any(Message.class), any(Object.class), any(Boolean.class)); + verify(errorReporter + ).reportError(throwableCaptor.capture(), any(Message.class), any(Object.class), any(Boolean.class)); assertThat(throwableCaptor.getValue().getMessage()).isEqualTo("testEx"); } @@ -121,7 +134,9 @@ protected Flux createSource(Function rout .headers(Collections.singletonMap(Headers.SERVED_QUERY_ID, routeExtractor.apply(value))) .build(); - final Envelope envelope = new Envelope(new Random().nextInt(), true, "exchange", routeExtractor.apply(value)); + final Envelope envelope = new Envelope( + new Random().nextInt(), true, "exchange", routeExtractor.apply(value) + ); final Delivery delivery = new Delivery(envelope, props, data.getBytes()); return new AcknowledgableDelivery(delivery, new ChannelDummy(), null); }).collect(Collectors.toList()); @@ -132,11 +147,27 @@ protected Flux createSource(Function rout protected abstract GenericMessageListener createMessageListener(final HandlerResolver handlerResolver); private HandlerResolver createHandlerResolver(final HandlerRegistry registry) { - final Map> eventHandlers = Stream.concat(registry.getDynamicEventHandlers().stream(), registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()).collect(toMap(RegisteredEventListener::getPath, identity())); - final Map> eventsToBind = registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream().collect(toMap(RegisteredEventListener::getPath, identity())); - final Map> notificationHandlers = registry.getEventNotificationListener().stream().collect(toMap(RegisteredEventListener::getPath, identity())); - final Map> queryHandlers = registry.getHandlers().stream().collect(toMap(RegisteredQueryHandler::getPath, identity())); - final Map> commandHandlers = registry.getCommandHandlers().stream().collect(toMap(RegisteredCommandHandler::getPath, identity())); + Stream> listenerStream = Stream.concat( + registry.getDynamicEventHandlers().get(DEFAULT_DOMAIN).stream(), + registry.getDomainEventListeners().get(DEFAULT_DOMAIN).stream()); + if (registry.getDomainEventListeners().containsKey("domain")) { + listenerStream = Stream.concat( + listenerStream, + registry.getDomainEventListeners().get("domain").stream()); + } + final Map> eventHandlers = listenerStream + .collect(toMap(RegisteredEventListener::getPath, identity())); + final Map> eventsToBind = registry.getDomainEventListeners() + .get(DEFAULT_DOMAIN).stream().collect(toMap(RegisteredEventListener::getPath, identity())); + final Map> notificationHandlers = registry.getEventNotificationListener() + .get(DEFAULT_DOMAIN) + .stream().collect(toMap(RegisteredEventListener::getPath, identity())); + final Map> queryHandlers = registry.getHandlers().get(DEFAULT_DOMAIN) + .stream() + .collect(toMap(RegisteredQueryHandler::getPath, identity())); + final Map> commandHandlers = registry.getCommandHandlers() + .get(DEFAULT_DOMAIN) + .stream().collect(toMap(RegisteredCommandHandler::getPath, identity())); return new HandlerResolver( new ConcurrentHashMap<>(queryHandlers), new ConcurrentHashMap<>(eventHandlers), diff --git a/async/async-rabbit/src/test/java/org/reactivecommons/async/utils/TestUtils.java b/async/async-rabbit/src/test/java/org/reactivecommons/async/utils/TestUtils.java index 10e3079f..c42709fe 100644 --- a/async/async-rabbit/src/test/java/org/reactivecommons/async/utils/TestUtils.java +++ b/async/async-rabbit/src/test/java/org/reactivecommons/async/utils/TestUtils.java @@ -1,6 +1,7 @@ package org.reactivecommons.async.utils; -import lombok.experimental.UtilityClass; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import reactor.core.publisher.Flux; import reactor.rabbitmq.AcknowledgableDelivery; import reactor.rabbitmq.ConsumeOptions; @@ -14,8 +15,8 @@ import static reactor.core.publisher.Flux.defer; -@UtilityClass -public class TestUtils { +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class TestUtils { public static void instructSafeReceiverMock(final Receiver receiver, final Flux source) { final AtomicReference> sourceReference = new AtomicReference<>(source); diff --git a/build.gradle b/build.gradle index 251626ad..f36490db 100644 --- a/build.gradle +++ b/build.gradle @@ -1,30 +1,21 @@ buildscript { repositories { mavenCentral() - maven { url 'https://repo.spring.io/milestone' } - maven { url 'https://repo.spring.io/snapshot' } + maven { url = 'https://repo.spring.io/milestone' } + maven { url = 'https://repo.spring.io/snapshot' } } dependencies { - classpath('com.github.ben-manes:gradle-versions-plugin:0.11.1') + classpath('com.github.ben-manes:gradle-versions-plugin:0.52.0') } } plugins { id 'jacoco' - id 'org.sonarqube' version '5.1.0.4882' - id 'org.springframework.boot' version '3.3.1' apply false + id 'org.sonarqube' version '6.1.0.5360' + id 'org.springframework.boot' version '3.4.4' apply false id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' - id 'co.com.bancolombia.cleanArchitecture' version '3.17.13' -} - -sonar { - properties { - property 'sonar.projectKey', 'reactive-commons_reactive-commons-java' - property 'sonar.coverage.exclusions', 'samples/**/*' - property 'sonar.organization', 'reactive-commons' - property 'sonar.host.url', 'https://sonarcloud.io' - } + id 'co.com.bancolombia.cleanArchitecture' version '3.20.15' } repositories { diff --git a/docs/docs/reactive-commons-eda/1-getting-started.md b/docs/docs/reactive-commons-eda/1-getting-started.md deleted file mode 100644 index 55cc1eaf..00000000 --- a/docs/docs/reactive-commons-eda/1-getting-started.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Getting Started - -## What Changes in this Variant? - -Before start this guide please review base [Reactive Commons](/reactive-commons-java/docs/category/reactive-commons) - -### Multi Broker or Multi Domain support - -This variant enables to you the ability to listen events from different domains, send commands to different domains and make queries to different domains. - -### Cloud Events - -This variant also includes the Cloud Events specification - -## Setup - -### Current version -![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Frepo1.maven.org%2Fmaven2%2Forg%2Freactivecommons%2Fasync-commons-rabbit-starter-eda%2Fmaven-metadata.xml) - -### Dependency - -```groovy -dependencies { - implementation "org.reactivecommons:async-commons-rabbit-starter-eda:" -} -``` - -### Configuration properties - -Also you need to include the name for your app in the `application.properties`, it is important because this value will be used -to name the application queues inside RabbitMQ: - -```properties -spring.application.name=MyAppName -``` - -Or in your `application.yaml` - -```yaml -spring: - application: - name: MyAppName -``` - -You can set the RabbitMQ connection properties through spring boot with the [`spring.rabbitmq.*` properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) - -```yaml -spring: - rabbitmq: - host: localhost - port: 5672 - username: guest - password: guest - virtual-host: / -``` - -You can also set it in runtime for example from a secret, so you can create the `RabbitProperties` bean like: - -```java title="org.reactivecommons.async.rabbit.config.RabbitProperties" -@Configuration -public class MyRabbitMQConfig { - - @Bean - @Primary - public RabbitProperties customRabbitProperties(){ - RabbitProperties properties = new RabbitProperties(); - properties.setHost("localhost"); - properties.setPort(5672); - properties.setVirtualHost("/"); - properties.setUsername("guest"); - properties.setPassword("guest"); - return properties; - } -} -``` \ No newline at end of file diff --git a/docs/docs/reactive-commons-eda/2-creating-a-cloud-event.md b/docs/docs/reactive-commons-eda/2-creating-a-cloud-event.md deleted file mode 100644 index a0bce0b6..00000000 --- a/docs/docs/reactive-commons-eda/2-creating-a-cloud-event.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -sidebar_position: 2 ---- - -# Creating a CloudEvent - -## Aditional Dependencies - -To start using this approach you should know the base of `Events`, `Commands` and `AsyncQuery` - -This varian includes an object mapper that allows to you to emit CloudEvent serialize and deserialize. - -Each API includes overloads related to emit CloudEvent events, send CloudEvent commands and make CloudEvent async queries. - -In order to instantiate a CloudEvent you need to include the dependencies: - -```groovy -implementation 'io.cloudevents:cloudevents-core:3.0.0' -implementation 'io.cloudevents:cloudevents-http-basic:3.0.0' -implementation 'io.cloudevents:cloudevents-json-jackson:3.0.0' -implementation 'io.cloudevents:cloudevents-spring:3.0.0' -``` -## Creating a CloudEvent instance - -```java -ObjectMapper om = new ObjectMapper(); - -CloudEvent commandCloudEvent = CloudEventBuilder.v1() - .withId(UUID.randomUUID().toString()) - .withSource(URI.create("https://reactivecommons.org/foos")) - .withType("some.command.name") - .withTime(OffsetDateTime.now()) - .withData("application/json", om.writeValueAsBytes(command)) // command is your own object - .build(); - -CloudEvent queryCloudEvent = CloudEventBuilder.v1() - .withId(UUID.randomUUID().toString()) - .withSource(URI.create("https://reactivecommons.org/foos")) - .withType("some.query.name") - .withTime(OffsetDateTime.now()) - .withData("application/json", om.writeValueAsBytes(query)) // query is your own object - .build(); - -CloudEvent eventCloudEvent = CloudEventBuilder.v1() - .withId(UUID.randomUUID().toString()) - .withSource(URI.create("https://reactivecommons.org/foos")) - .withType("some.event.name") - .withDataContentType("application/json") - .withTime(OffsetDateTime.now()) - .withData("application/json", om.writeValueAsBytes(event)) // event is your own object - .build(); -``` \ No newline at end of file diff --git a/docs/docs/reactive-commons-eda/3-additional-api-methods.md b/docs/docs/reactive-commons-eda/3-additional-api-methods.md deleted file mode 100644 index 0a0e8d8a..00000000 --- a/docs/docs/reactive-commons-eda/3-additional-api-methods.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Additional API Definitions - -Returning to the base API, the additional methods enabled for EDA and CloudEvents are indicated below. - -## Aditional Methods in Senders - -### DomainEventBus interface - -This interface has one additional method specific for EDA - -```java -public interface DomainEventBus { - Publisher emit(CloudEvent event); // Emit with CloudEvent format - - //... other base definitions -} -``` - -### DirectAsyncGateway interface - -Ths interface adds some actions for commands and async queries - -```java -public interface DirectAsyncGateway { - Mono sendCommand(Command command, String targetName, String domain); // Send to specific domain - - Mono sendCommand(Command command, String targetName, long delayMillis, String domain); // Send to specific domain with delay - - Mono sendCommand(CloudEvent command, String targetName); // Send with CloudEvent format - - Mono sendCommand(CloudEvent command, String targetName, String domain); // Send with CloudEvent format to an specific domain - - Mono requestReply(AsyncQuery query, String targetName, Class type, String domain); // Query to specific domain - - Mono requestReply(CloudEvent query, String targetName, Class type); // Query with CloudEvent format - - Mono requestReply(CloudEvent query, String targetName, Class type, String domain); // Query with CloudEvent format to specific domain -} -``` - -## Aditional Listener capabilities - -On the other hand, for listener you can use additional capabilities like listen from another domain - -### HandlerRegistry class - -```java -public class HandlerRegistry { - public HandlerRegistry listenDomainEvent(String domain, String eventName, EventHandler handler, Class eventClass) {...} // Class could be CloudEvent.class - - // ... other base methods -} -``` \ No newline at end of file diff --git a/docs/docs/reactive-commons-eda/4-configuration-properties.md b/docs/docs/reactive-commons-eda/4-configuration-properties.md deleted file mode 100644 index 6cfdcd0c..00000000 --- a/docs/docs/reactive-commons-eda/4-configuration-properties.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -sidebar_position: 4 ---- - -# Configuration Properties - -You can customize some predefined variables of Reactive Commons - -This can be done by Spring Boot `application.yaml` or by overriding the [AsyncPropsDomainProperties](https://github.com/reactive-commons/reactive-commons-java/blob/master/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomainProperties.java) bean. - -```yaml -app: - async: - app: # this is the name of the default domain - withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. - maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. - retryDelay: 1000 # interval for message retries, with and without DLQRetry - listenReplies: true # if you will not use ReqReply patter you can set it to false - createTopology: true # if your organization have restricctions with automatic topology creation you can set it to false and create it manually or by your organization process. - delayedCommands: false # Enable to send a delayed command to an external target - prefetchCount: 250 # is the maximum number of in flight messages you can reduce it to process less concurrent messages, this settings acts per instance of your service - flux: - maxConcurrency: 250 # max concurrency of listener flow - domain: - ignoreThisListener: false # Allows you to disable event listener for this specific domain - events: - exchange: domainEvents # you can change the exchange, but you should do it in all applications consistently - eventsSuffix: subsEvents # events queue name suffix, name will be like ${spring.application.name}.${app.async.domain.events.eventsSuffix} - notificationSuffix: notification # notification events queue name suffix - direct: - exchange: directMessages # you can change the exchange, but you should do it in all applications - querySuffix: query # queries queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} - commandSuffix: '' # commands queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} or ${spring.application.name} if empty by default - discardTimeoutQueries: false # enable to discard this condition - global: - exchange: globalReply # you can change the exchange, but you should do it in all applications - repliesSuffix: replies # async query replies events queue name suffix - connectionProperties: # you can override the connection properties of each domain - host: localhost - port: 5672 - username: guest - password: guest - virtual-host: / - accounts: # this is a second domain name and can have another independent settup - connectionProperties: # you can override the connection properties of each domain - host: localhost - port: 5672 - username: guest - password: guest - virtual-host: /accounts -``` - -You can override this settings programatically through a `AsyncPropsDomainProperties` bean. - -```java -package sample; - -import org.reactivecommons.async.rabbit.config.RabbitProperties; -import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomainProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; - -@Configuration -public class MyDomainConfig { - - @Bean - @Primary - public AsyncPropsDomainProperties customDomainProperties() { - RabbitProperties propertiesApp = new RabbitProperties(); - propertiesApp.setHost("localhost"); - propertiesApp.setPort(5672); - propertiesApp.setVirtualHost("/"); - propertiesApp.setUsername("guest"); - propertiesApp.setPassword("guest"); - - RabbitProperties propertiesAccounts = new RabbitProperties(); - propertiesAccounts.setHost("localhost"); - propertiesAccounts.setPort(5672); - propertiesAccounts.setVirtualHost("/accounts"); - propertiesAccounts.setUsername("guest"); - propertiesAccounts.setPassword("guest"); - - return AsyncPropsDomainProperties.builder() - .withDomain("app", AsyncProps.builder() - .connectionProperties(propertiesApp) - .build()) - .withDomain("accounts", AsyncProps.builder() - .connectionProperties(propertiesAccounts) - .build()) - .build(); - } -} -``` \ No newline at end of file diff --git a/docs/docs/reactive-commons-eda/_category_.json b/docs/docs/reactive-commons-eda/_category_.json deleted file mode 100644 index bf403fcf..00000000 --- a/docs/docs/reactive-commons-eda/_category_.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "label": "Reactive Commons EDA", - "position": 3, - "link": { - "type": "generated-index", - "description": "Learn how to build reactive systems using the Reactive Commons library." - } -} diff --git a/docs/docs/reactive-commons/1-getting-started.md b/docs/docs/reactive-commons/1-getting-started.md index f0d53b65..4d400ca8 100644 --- a/docs/docs/reactive-commons/1-getting-started.md +++ b/docs/docs/reactive-commons/1-getting-started.md @@ -4,7 +4,14 @@ sidebar_position: 1 # Getting Started -This quick start tutorial sets up a single node RabbitMQ and runs the sample reactive sender and consumer using Reactive Commons. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +This quick start tutorial sets up a single node RabbitMQ and runs the sample reactive sender and consumer using Reactive +Commons. ## Requirements @@ -16,7 +23,7 @@ You also need to install RabbitMQ. Follow the [instructions from the website](ht Start RabbitMQ on your local machine with all the defaults (e.g. AMQP port is 5672). -### Contenerized +### Containerized You can run it with Docker or Podman @@ -26,23 +33,37 @@ podman run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management-al ## Spring Boot Application -The Spring Boot sample publishes and consumes messages with the `DomainEventBus`. This application illustrates how to configure Reactive Commons using RabbitMQ in a Spring Boot environment. +The Spring Boot sample publishes and consumes messages with the `DomainEventBus`. This application illustrates how to +configure Reactive Commons using RabbitMQ in a Spring Boot environment. To build your own application using the Reactive Commons API, you need to include a dependency to Reactive Commons. ### Current version + ![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Frepo1.maven.org%2Fmaven2%2Forg%2Freactivecommons%2Fasync-commons-rabbit-starter%2Fmaven-metadata.xml) ### Dependency ```groovy dependencies { - implementation "org.reactivecommons:async-commons-rabbit-starter:" + implementation "org.reactivecommons:async-commons-rabbit-starter:" } ``` + +Note: If you will use Cloud Events, you should include the Cloud Events dependency: + +```groovy +dependencies { + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' +} +``` + +```groovy + ### Configuration properties -Also you need to include the name for your app in the `application.properties`, it is important because this value will be used +Also you need to include the name for your app in the `application.properties`, it is important because this value will +be used to name the application queues inside RabbitMQ: ```properties @@ -57,27 +78,29 @@ spring: name: MyAppName ``` -You can set the RabbitMQ connection properties through spring boot with the [`spring.rabbitmq.*` properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) +You can set the RabbitMQ connection properties through spring boot with +the [`spring.rabbitmq.*` properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) ```yaml spring: rabbitmq: - host: localhost - port: 5672 - username: guest - password: guest - virtual-host: / + host: localhost + port: 5672 + username: guest + password: guest + virtual-host: / ``` You can also set it in runtime for example from a secret, so you can create the `RabbitProperties` bean like: ```java title="org.reactivecommons.async.rabbit.config.RabbitProperties" + @Configuration public class MyRabbitMQConfig { @Bean @Primary - public RabbitProperties customRabbitProperties(){ + public RabbitProperties customRabbitProperties() { RabbitProperties properties = new RabbitProperties(); properties.setHost("localhost"); properties.setPort(5672); @@ -87,4 +110,177 @@ public class MyRabbitMQConfig { return properties; } } -``` \ No newline at end of file +``` + +Please refer to [Configuration Properties](/reactive-commons-java/docs/reactive-commons/configuration-properties) +Or with secrets [Loading properties from a secret](/reactive-commons-java/docs/reactive-commons/configuration-properties#loading-properties-from-a-secret) + +The 5.x.x stable version of Reactive Commons has been merged with the deprecated `-eda` variant, this means that +the `async-commons-rabbit-starter` artifact is now the only one to use. + +This merge allows you to: + +### Multi Broker Instances of RabbitMQ or Multi Domain support + +Enables to you the ability to listen events from different domains, send commands to different domains and make queries +to different domains. + +### Cloud Events + +Includes the Cloud Events specification. + +If you want to use it, you should read the [Creating a CloudEvent guide](11-creating-a-cloud-event.md) + + + + This quick start tutorial sets up a single node Kafka and runs the sample reactive sender and consumer using Reactive +Commons. + +## Requirements + +You need Java JRE installed (Java 17 or later). + +## Start Kafka + +Start a Kafka broker on your local machine with all the defaults (e.g. port is 9092). + +### Containerized + +You can run it with Docker or Podman. + +The following docker compose has a Kafka broker, a Zookeeper and a Kafka UI. + +docker-compose.yml +```yaml +services: + zookeeper: + image: confluentinc/cp-zookeeper:7.4.1 + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + ports: + - "2181:2181" + + kafka: + image: confluentinc/cp-kafka:7.4.1 + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + ports: + - "9092:9092" + depends_on: + - zookeeper + + kafka-ui: + image: provectuslabs/kafka-ui:latest + environment: + KAFKA_CLUSTERS_0_NAME: local + KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092 + KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181 + ports: + - "8081:8080" + depends_on: + - kafka +``` + +```shell +docker-compose up +``` + +You may set in /etc/hosts (or equivalent) the following entry: + +```txt +127.0.0.1 kafka +``` + +To enter the Kafka UI, open your browser and go to `http://localhost:8081` + +## Spring Boot Application + +The Spring Boot sample publishes and consumes messages with the `DomainEventBus`. This application illustrates how to +configure Reactive Commons using Kafka in a Spring Boot environment. + +To build your own application using the Reactive Commons API, you need to include a dependency to Reactive Commons. + +### Current version + +![Maven metadata URL](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Frepo1.maven.org%2Fmaven2%2Forg%2Freactivecommons%2Fasync-commons-rabbit-starter%2Fmaven-metadata.xml) + +### Dependency + +```groovy +dependencies { + implementation "org.reactivecommons:async-kafka-starter:" +} +``` + +Note: If you will use Cloud Events, you should include the Cloud Events dependency: + +```groovy +dependencies { + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' +} +``` + +### Configuration properties + +Also you need to include the name for your app in the `application.properties`, it is important because this value will +be used +to name the application group-id inside Kafka: + +```properties +spring.application.name=MyAppName +``` + +Or in your `application.yaml` + +```yaml +spring: + application: + name: MyAppName +``` + +You can set the Kafka connection properties through spring boot with +the [`spring.kafka.*` properties](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html) + +```yaml +spring: + kafka: + bootstrap-servers: localhost:9092 +``` + +You can also set it in runtime for example from a secret, so you can create the `KafkaProperties` bean like: + +```java title="org.reactivecommons.async.kafka.config.KafkaProperties" + +@Configuration +public class MyKafkaConfig { + + @Bean + @Primary + public KafkaProperties myRCKafkaProperties() { + KafkaProperties properties = new KafkaProperties(); + properties.setBootstrapServers(List.of("localhost:9092")); + return properties; + } +} +``` + +### Multi Broker Instances of Kafka or Multi Domain support + +Enables to you the ability to listen events from different domains. + +### Cloud Events + +Includes the Cloud Events specification. + +If you want to use it, you should read the [Creating a CloudEvent guide](11-creating-a-cloud-event.md) + + + + diff --git a/docs/docs/reactive-commons/10-wildcards.md b/docs/docs/reactive-commons/10-wildcards.md index 20270a1c..89b1c8dd 100644 --- a/docs/docs/reactive-commons/10-wildcards.md +++ b/docs/docs/reactive-commons/10-wildcards.md @@ -4,9 +4,18 @@ sidebar_position: 10 # Wildcards -You may need to listen variable event names that have the same structure, in that case you have the method `handleDynamicEvents` in the `HandlerRegistry`, so you can specify a pattern with '*' wildcard, it does not creates a binding in the broker, but allows that you do it dynamically through a `DynamicRegistry` class. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; -You can also create binding with '#' wildcard, it is used to listen multiple words, for example `animals.#` will listen to `animals.dog`, `animals.dog.bark`, `animals.cat`, `animals.cat.meow`, etc. + + + +You may need to listen variable event names that have the same structure, in that case you have the +method `handleDynamicEvents` in the `HandlerRegistry`, so you can specify a pattern with '*' wildcard, it does not +creates a binding in the broker, but allows that you do it dynamically through a `DynamicRegistry` class. + +You can also create binding with '#' wildcard, it is used to listen multiple words, for example `animals.#` will listen +to `animals.dog`, `animals.dog.bark`, `animals.cat`, `animals.cat.meow`, etc. ## DynamicRegistry API @@ -22,16 +31,18 @@ public interface DynamicRegistry { } ``` -To start listening a new event dynamically at runtime, you should inject and call a method of the DynamicReggistry, for example: +To start listening a new event dynamically at runtime, you should inject and call a method of the DynamicReggistry, for +example: ```java + @Component @AllArgsConstructor public class DynamicSubscriber { private final DynamicRegistry registry; public Mono listenNewEvent(String eventName) { - return registry.startListeningEvent(eventName); + return registry.startListeningEvent(eventName); } } ``` @@ -41,6 +52,7 @@ The conditions for a success dynamic registry functionality are: - You should handle dynamic events with specific wildcard ```java + @Configuration public class HandlerRegistryConfiguration { @@ -58,12 +70,14 @@ public class HandlerRegistryConfiguration { registry.startListeningEvent("purchase.cancelled"); ``` -You also can listen with * wildcard or # wildcard, the * wildcard is for a single word and # wildcard is for multiple words, for example: +You also can listen with * wildcard or # wildcard, the * wildcard is for a single word and # wildcard is for multiple +words, for example: `animals.*` will listen to `animals.dog`, `animals.cat`, `animals.bird`, etc. `animals.#` will listen to `animals.dog`, `animals.dog.bark`, `animals.cat`, `animals.cat.meow`, etc. ```java + @Configuration public class HandlerRegistryConfiguration { @@ -76,37 +90,54 @@ public class HandlerRegistryConfiguration { } ``` -This last approach is useful when you have a dynamic event name, for example, you can have a `purchase.cancelled` event, but you can also have a `purchase.cancelled.2021` event, so you can listen to all of them with `purchase.*` or `purchase.#` respectively. +This last approach is useful when you have a dynamic event name, for example, you can have a `purchase.cancelled` event, +but you can also have a `purchase.cancelled.2021` event, so you can listen to all of them with `purchase.*` +or `purchase.#` respectively. ## Priorities -The handlers with wildcards have the lowest priority, so if you have a specific handler for an event name, it will be called before the wildcard handler. +The handlers with wildcards have the lowest priority, so if you have a specific handler for an event name, it will be +called before the wildcard handler. -The wildcard handler will be called if there is no specific handler for the event name. And the wildcard matches the pattern. +The wildcard handler will be called if there is no specific handler for the event name. And the wildcard matches the +pattern. General conditions for handler priority are: + - fixed words has priority over wildcard - wildcard with * has priority over wildcard with # - wildcard with # has the lowest priority The next code will help you to avoid unexpected behaviors, which indicates you the handler that will be called. + ```java public static void main(String[] args) { - Set names = Set.of("prefix.*.*", "prefix.*.#"); - String target = "prefix.middle.suffix"; - String handler = new KeyMatcher().match(names, target); - System.out.println(handler); - } + Set names = Set.of("prefix.*.*", "prefix.*.#"); + String target = "prefix.middle.suffix"; + String handler = new KeyMatcher().match(names, target); + System.out.println(handler); +} ``` ## Possible issues -Unprocessed events will be notified with an event, so please handle it properly and ensure you don't have wildcards that match all events, because it can cause unexpected behaviors. +Unprocessed events will be notified with an event, so please handle it properly and ensure you don't have wildcards that +match all events, because it can cause unexpected behaviors. -For example if you have a handler for `my.event.#` and you start listening to `my.event`, it will match all events that start with `my.event`, so it will match `my.event.any` if your handler has dlq retries or has local retries, when your handler fails the retry limit, an event with name `my.event.any.dlq` will be sent. +For example if you have a handler for `my.event.#` and you start listening to `my.event`, it will match all events that +start with `my.event`, so it will match `my.event.any` if your handler has dlq retries or has local retries, when your +handler fails the retry limit, an event with name `my.event.any.dlq` will be sent. -And if you have the same binding `my.event.#` the event will be handled by the handler, so you will have two different event structures for the same handler, and it can cause unexpected behaviors. +And if you have the same binding `my.event.#` the event will be handled by the handler, so you will have two different +event structures for the same handler, and it can cause unexpected behaviors. ## Example -You can see a real example at [samples/async/async-receiver-responder](https://github.com/reactive-commons/reactive-commons-java/tree/master/samples/async/async-receiver-responder) \ No newline at end of file +You can see a real example +at [samples/async/async-receiver-responder](https://github.com/reactive-commons/reactive-commons-java/tree/master/samples/async/async-receiver-responder) + + + + Comming soon... + + \ No newline at end of file diff --git a/docs/docs/reactive-commons/11-creating-a-cloud-event.md b/docs/docs/reactive-commons/11-creating-a-cloud-event.md new file mode 100644 index 00000000..30296a20 --- /dev/null +++ b/docs/docs/reactive-commons/11-creating-a-cloud-event.md @@ -0,0 +1,98 @@ +--- +sidebar_position: 11 +--- + +# Creating a CloudEvent + +## Additional Dependencies + +To start using this approach you should know the base of `Events`, `Commands` and `AsyncQuery` + +This variant includes an object mapper that allows to you to emit CloudEvent serialize and deserialize. + +Each API includes overloads related to emit CloudEvent events, send CloudEvent commands and make CloudEvent async queries. + +In order to instantiate a CloudEvent you may need to include the dependencies: + +```groovy +implementation 'io.cloudevents:cloudevents-core:' +// or +implementation 'io.cloudevents:cloudevents-json-jackson:' +``` + +## Creating a CloudEvent instance with our Data wrapper + +add this classes: + +```java +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.CloudEvent; +import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; +``` + +```java +CloudEvent commandCloudEvent = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://reactivecommons.org/foos")) + .withType("some.command.name") + .withTime(OffsetDateTime.now()) + .withData("application/json", CloudEventBuilderExt.asCloudEventData(commandData)) // commandData is your own object + .build(); + +CloudEvent queryCloudEvent = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://reactivecommons.org/foos")) + .withType("some.query.name") + .withTime(OffsetDateTime.now()) + .withData("application/json", CloudEventBuilderExt.asCloudEventData(queryData)) // queryData is your own object + .build(); + +CloudEvent eventCloudEvent = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://reactivecommons.org/foos")) + .withType("some.event.name") + .withDataContentType("application/json") + .withTime(OffsetDateTime.now()) + .withData("application/json", CloudEventBuilderExt.asCloudEventData(eventData)) // eventData is your own object + .build(); +``` + +## Creating a CloudEvent instance with jackson wrapper Data wrapper + +add this classes: + +```java +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.CloudEvent; +import io.cloudevents.jackson.JsonCloudEventData; +import com.fasterxml.jackson.databind.ObjectMapper; +``` + +```java +ObjectMapper mapper = new ObjectMapper(); // You should convert your object to a JsonNode + +CloudEvent commandCloudEvent = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://reactivecommons.org/foos")) + .withType("some.command.name") + .withTime(OffsetDateTime.now()) + .withData("application/json", JsonCloudEventData.wrap(mapper.valueToTree(commandData))) // commandData is your own object + .build(); + +CloudEvent queryCloudEvent = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://reactivecommons.org/foos")) + .withType("some.query.name") + .withTime(OffsetDateTime.now()) + .withData("application/json", JsonCloudEventData.wrap(mapper.valueToTree(queryData))) // queryData is your own object + .build(); + +CloudEvent eventCloudEvent = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://reactivecommons.org/foos")) + .withType("some.event.name") + .withDataContentType("application/json") + .withTime(OffsetDateTime.now()) + .withData("application/json", JsonCloudEventData.wrap(mapper.valueToTree(eventData))) // eventData is your own object + .build(); +``` \ No newline at end of file diff --git a/docs/docs/reactive-commons/2-sending-a-domain-event.md b/docs/docs/reactive-commons/2-sending-a-domain-event.md index b48fc7ac..08cb8703 100644 --- a/docs/docs/reactive-commons/2-sending-a-domain-event.md +++ b/docs/docs/reactive-commons/2-sending-a-domain-event.md @@ -26,7 +26,7 @@ Where name is the event name, eventId is an unique event identifier and data is public interface DomainEventBus { Publisher emit(DomainEvent event); - //... other definitions for eda variant + Publisher emit(CloudEvent event); } ``` @@ -40,7 +40,7 @@ For example: @EnableDomainEventBus public class ReactiveEventsGateway { public static final String SOME_EVENT_NAME = "some.event.name"; - private final DomainEventBus domainEventBus; // Auto injectec bean created by the @EnableDomainEventBus annotation + private final DomainEventBus domainEventBus; // Auto injected bean created by the @EnableDomainEventBus annotation public Mono emit(Object event) { return Mono.from(domainEventBus.emit(new DomainEvent<>(SOME_EVENT_NAME, UUID.randomUUID().toString(), event))); diff --git a/docs/docs/reactive-commons/3-sending-a-command.md b/docs/docs/reactive-commons/3-sending-a-command.md index 692bd9cb..62142820 100644 --- a/docs/docs/reactive-commons/3-sending-a-command.md +++ b/docs/docs/reactive-commons/3-sending-a-command.md @@ -29,10 +29,19 @@ public interface DirectAsyncGateway { Mono sendCommand(Command command, String targetName, long delayMillis); - //... other definitions for queries and eda variant + Mono sendCommand(Command command, String targetName, String domain); // Send to specific domain + + Mono sendCommand(Command command, String targetName, long delayMillis, String domain); // Send to specific domain with delay + + Mono sendCommand(CloudEvent command, String targetName); // Send with CloudEvent format + + Mono sendCommand(CloudEvent command, String targetName, String domain); // Send with CloudEvent format to an specific domain } ``` +You can send a CloudEvent or a Command\ to a target application. You also can send a command to a specific domain +(remote broker out of you application context). + ## Enabling autoconfiguration To send Commands you should enable the respecting spring boot autoconfiguration using the `@EnableDomainEventBus` annotation @@ -44,7 +53,7 @@ For example: public class ReactiveDirectAsyncGateway { public static final String TARGET_NAME = "other-app";// refers to remote spring.application.name property public static final String SOME_COMMAND_NAME = "some.command.name"; - private final DirectAsyncGateway gateway; // Auto injectec bean created by the @EnableDirectAsyncGateway annotation + private final DirectAsyncGateway gateway; // Auto injected bean created by the @EnableDirectAsyncGateway annotation public Mono runRemoteJob(Object command/*change for proper model*/) { return gateway.sendCommand(new Command<>(SOME_COMMAND_NAME, UUID.randomUUID().toString(), command), TARGET_NAME); diff --git a/docs/docs/reactive-commons/4-making-an-async-query.md b/docs/docs/reactive-commons/4-making-an-async-query.md index 00f7c6e0..a8f13541 100644 --- a/docs/docs/reactive-commons/4-making-an-async-query.md +++ b/docs/docs/reactive-commons/4-making-an-async-query.md @@ -28,12 +28,19 @@ public interface DirectAsyncGateway { Mono requestReply(AsyncQuery query, String targetName, Class type); - //... other definitions for commands and eda variant + Mono requestReply(AsyncQuery query, String targetName, Class type, String domain); // Query to specific domain + + Mono requestReply(CloudEvent query, String targetName, Class type); // Query with CloudEvent format + + Mono requestReply(CloudEvent query, String targetName, Class type, String domain); // Query with CloudEvent format to specific domain } ``` In this method the Class\ called type is the return type of the query, represented by a JSON Serializable object +You can send a CloudEvent or an AsyncQuery\ to a target application. You also can send a query to a specific domain +(remote broker out of you application context). + ## Enabling autoconfiguration To do an Async Query you should enable the respecting spring boot autoconfiguration using the `@EnableDirectAsyncGateway` annotation @@ -45,7 +52,7 @@ For example: public class ReactiveDirectAsyncGateway { public static final String TARGET_NAME = "other-app";// refers to remote spring.application.name property public static final String SOME_QUERY_NAME = "some.query.name"; - private final DirectAsyncGateway gateway; // Auto injectec bean created by the @EnableDirectAsyncGateway annotation + private final DirectAsyncGateway gateway; // Auto injected bean created by the @EnableDirectAsyncGateway annotation public Mono requestForRemoteData(Object query/*change for proper model*/) { return gateway.requestReply(new AsyncQuery<>(SOME_QUERY_NAME, query), TARGET_NAME, Object.class/*change for proper model*/); diff --git a/docs/docs/reactive-commons/5-handler-registry.md b/docs/docs/reactive-commons/5-handler-registry.md index 207a3997..79818a90 100644 --- a/docs/docs/reactive-commons/5-handler-registry.md +++ b/docs/docs/reactive-commons/5-handler-registry.md @@ -16,12 +16,24 @@ The next methods are the main methods that you can use to register a handler. ```java public class HandlerRegistry { - public HandlerRegistry listenEvent(String eventName, EventHandler handler, Class eventClass){...} - public HandlerRegistry handleDynamicEvents(String eventNamePattern, EventHandler handler, Class eventClass){...} - public HandlerRegistry listenNotificationEvent(String eventName, EventHandler handler, Class eventClass){...} - public HandlerRegistry handleCommand(String commandName, CommandHandler fn, Class commandClass){...} - public HandlerRegistry serveQuery(String resource, QueryHandler handler, Class queryClass){...} - public HandlerRegistry serveQuery(String resource, QueryHandlerDelegate handler, Class queryClass) {...} - // ... other methods for eda variant and overloads + public HandlerRegistry listenDomainEvent(String domain, String eventName, DomainEventHandler handler, Class eventClass) + public HandlerRegistry listenDomainCloudEvent(String domain, String eventName, CloudEventHandler handler) + public HandlerRegistry listenEvent(String eventName, DomainEventHandler handler, Class eventClass) + public HandlerRegistry listenCloudEvent(String eventName, CloudEventHandler handler) + public HandlerRegistry listenNotificationEvent(String eventName, DomainEventHandler handler, Class eventClass) + public HandlerRegistry listenNotificationCloudEvent(String eventName, CloudEventHandler handler) + public HandlerRegistry handleDynamicEvents(String eventNamePattern, DomainEventHandler handler, Class eventClass) + public HandlerRegistry handleDynamicCloudEvents(String eventNamePattern, CloudEventHandler handler) + public HandlerRegistry handleCommand(String commandName, DomainCommandHandler fn, Class commandClass) + public HandlerRegistry handleCloudEventCommand(String commandName, CloudCommandHandler handler) + public HandlerRegistry serveQuery(String resource, QueryHandler handler, Class queryClass) + public HandlerRegistry serveQuery(String resource, QueryHandlerDelegate handler, Class queryClass) + public HandlerRegistry serveCloudEventQuery(String resource, QueryHandler handler) + public HandlerRegistry serveCloudEventQuery(String resource, QueryHandlerDelegate handler) } -``` \ No newline at end of file +``` + +Methods that Has `CloudEvent` in the name are related to the CloudEvent specification. + +Methods that has `domain` String argument are related to the multi-broker support, this support is limited to listen events +from different domains (brokers) independent of the technology. \ No newline at end of file diff --git a/docs/docs/reactive-commons/8-serving-async-queries.md b/docs/docs/reactive-commons/8-serving-async-queries.md index fc0fff41..f6b94b89 100644 --- a/docs/docs/reactive-commons/8-serving-async-queries.md +++ b/docs/docs/reactive-commons/8-serving-async-queries.md @@ -38,7 +38,7 @@ As the model of queries is direct, a consumer always can send queries to the ser ### Wildcards -You may need to handle variable querie names that have the same structure, in that case you can specfy a pattern with '*' wildcard, for example: +You may need to handle variable queries names that have the same structure, in that case you can specfy a pattern with '*' wildcard, for example: ```java @Configuration @@ -60,7 +60,7 @@ There is a concept introduced in queries that cannot be resolved locally and may - A consumer application called APP1 make an async query to an application called APP2 (with horizontal scaling). - an instance A of the APP2 receives the query and stores a reference to respond later. -- When response is fullfilled, an external source makes an HTTPS Call to an endpoint of an instance of APP2 but it can be diferent to the instance A, for example can be the instance B. +- When response is fulfilled, an external source makes an HTTPS Call to an endpoint of an instance of APP2 but it can be diferent to the instance A, for example can be the instance B. - The instance B of APP2 queries for the saved reference and uses DirectAsyncGateway to answer the pending query to the application APP1. This scenario can be resolved with ReactiveCommons by using the next resources: @@ -106,7 +106,7 @@ When some external source notifies our APP2 instance with the answer we should f @RequiredArgsConstructor @EnableDirectAsyncGateway public class ReactiveDirectAsyncGateway { - private final DirectAsyncGateway gateway; // Auto injectec bean created by the @EnableDirectAsyncGateway annotation + private final DirectAsyncGateway gateway; // Auto injected bean created by the @EnableDirectAsyncGateway annotation public Mono replyDelegate(String correlationId, Object response/*change for proper model*/) { return getReference(correlationId) diff --git a/docs/docs/reactive-commons/9-configuration-properties.md b/docs/docs/reactive-commons/9-configuration-properties.md index 1540b385..8baefe62 100644 --- a/docs/docs/reactive-commons/9-configuration-properties.md +++ b/docs/docs/reactive-commons/9-configuration-properties.md @@ -4,33 +4,269 @@ sidebar_position: 8 # Configuration Properties +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + You can customize some predefined variables of Reactive Commons -This can be done by Spring Boot `application.yaml` or by overriding the [AsyncProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java) bean. +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java) +bean. ```yaml app: async: - withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. - maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. - retryDelay: 1000 # interval for message retries, with and without DLQRetry - listenReplies: true # if you will not use ReqReply patter you can set it to false - createTopology: true # if your organization have restricctions with automatic topology creation you can set it to false and create it manually or by your organization process. - delayedCommands: false # Enable to send a delayed command to an external target - prefetchCount: 250 # is the maximum number of in flight messages you can reduce it to process less concurrent messages, this settings acts per instance of your service - flux: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + retryDelay: 1000 # interval for message retries, with and without DLQRetry + listenReplies: true # if you will not use ReqReply patter you can set it to false + createTopology: true # if your organization have restrictions with automatic topology creation you can set it to false and create it manually or by your organization process. + delayedCommands: false # Enable to send a delayed command to an external target + prefetchCount: 250 # is the maximum number of in flight messages you can reduce it to process less concurrent messages, this settings acts per instance of your service + useDiscardNotifierPerDomain: false # if true it uses a discard notifier for each domain,when false it uses a single discard notifier for all domains with default 'app' domain + enabled: true # if you want to disable this domain you can set it to false + brokerType: "rabbitmq" # please don't change this value + flux: maxConcurrency: 250 # max concurrency of listener flow - domain: + domain: + ignoreThisListener: false # Allows you to disable event listener for this specific domain events: - exchange: domainEvents # you can change the exchange, but you should do it in all applications consistently - eventsSuffix: subsEvents # events queue name suffix, name will be like ${spring.application.name}.${app.async.domain.events.eventsSuffix} - notificationSuffix: notification # notification events queue name suffix - direct: + exchange: domainEvents # you can change the exchange, but you should do it in all applications consistently + eventsSuffix: subsEvents # events queue name suffix, name will be like ${spring.application.name}.${app.async.domain.events.eventsSuffix} + notificationSuffix: notification # notification events queue name suffix + direct: exchange: directMessages # you can change the exchange, but you should do it in all applications querySuffix: query # queries queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} commandSuffix: '' # commands queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} or ${spring.application.name} if empty by default discardTimeoutQueries: false # enable to discard this condition - global: + global: exchange: globalReply # you can change the exchange, but you should do it in all applications repliesSuffix: replies # async query replies events queue name suffix -``` \ No newline at end of file + connectionProperties: # you can override the connection properties of each domain + host: localhost + port: 5672 + username: guest + password: guest + virtual-host: / + # Another domain can be configured with same properties structure that app + accounts: # this is a second domain name and can have another independent setup + connectionProperties: # you can override the connection properties of each domain + host: localhost + port: 5672 + username: guest + password: guest + virtual-host: /accounts +``` + +You can override this settings programmatically through a `AsyncRabbitPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncRabbitPropsDomainProperties customDomainProperties() { + RabbitProperties propertiesApp = new RabbitProperties(); + propertiesApp.setHost("localhost"); + propertiesApp.setPort(5672); + propertiesApp.setVirtualHost("/"); + propertiesApp.setUsername("guest"); + propertiesApp.setPassword("guest"); + + RabbitProperties propertiesAccounts = new RabbitProperties(); + propertiesAccounts.setHost("localhost"); + propertiesAccounts.setPort(5672); + propertiesAccounts.setVirtualHost("/accounts"); + propertiesAccounts.setUsername("guest"); + propertiesAccounts.setPassword("guest"); + + return AsyncRabbitPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .withDomain("accounts", AsyncProps.builder() + .connectionProperties(propertiesAccounts) + .build()) + .build(); + } +} +``` + +## Loading properties from a secret + +Additionally, if you want to set only connection properties you can use the `AsyncPropsDomain.RabbitSecretFiller` class. + +```java + +@Bean +public AsyncPropsDomain.RabbitSecretFiller customFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + +For example if you use the [Secrets Manager](https://github.com/bancolombia/secrets-manager) project, you may use +the next code to load the properties from a secret: + +1. Create a class with the properties that you will load from the secret: +```java +import lombok.Builder; +import lombok.Data; +import org.reactivecommons.async.rabbit.config.RabbitProperties; + +@Data +@Builder +public class RabbitConnectionProperties { + private String hostname; + private String password; + private String username; + private Integer port; + private String virtualhost; + private boolean ssl; + + public RabbitProperties toRabbitProperties() { + var rabbitProperties = new RabbitProperties(); + rabbitProperties.setHost(this.hostname); + rabbitProperties.setUsername(this.username); + rabbitProperties.setPassword(this.password); + rabbitProperties.setPort(this.port); + rabbitProperties.setVirtualHost(this.virtualhost); + rabbitProperties.getSsl().setEnabled(this.ssl); // To enable SSL + return rabbitProperties; + } +} +``` +2. Use the `SecretsManager` to load the properties from the secret: + +```java +import co.com.bancolombia.secretsmanager.api.GenericManager; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.lang.reflect.GenericArrayType; + +@Log4j2 +@Configuration +public class AsyncEventBusConfig { + + // TODO: You should create the GenericManager bean as indicated in Secrets Manager library + @Bean + public AsyncPropsDomain.RabbitSecretFiller rabbitSecretFiller(GenericManager genericManager) { + return (domain, props) -> { + if (props.getSecret() != null) { + log.info("Loading RabbitMQ connection properties from secret: {}", props.getSecret()); + props.setConnectionProperties(getFromSecret(genericManager, props.getSecret())); + log.info("RabbitMQ connection properties loaded successfully with host: '{}'", + props.getConnectionProperties().getHost()); + } + }; + } + + @SneakyThrows + private RabbitProperties getFromSecret(GenericManager genericManager, String secretName) { + return genericManager.getSecret(secretName, RabbitConnectionProperties.class).toRabbitProperties(); + } +} + +``` + + + + You can customize some predefined variables of Reactive Commons + +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncKafkaProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java) +bean. + +```yaml +reactive: + commons: + kafka: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + retryDelay: 1000 # interval for message retries, with and without DLQRetry + checkExistingTopics: true # if you don't want to verify topic existence before send a record you can set it to false + createTopology: true # if your organization have restrictions with automatic topology creation you can set it to false and create it manually or by your organization process. + useDiscardNotifierPerDomain: false # if true it uses a discard notifier for each domain,when false it uses a single discard notifier for all domains with default 'app' domain + enabled: true # if you want to disable this domain you can set it to false + brokerType: "kafka" # please don't change this value + domain: + ignoreThisListener: false # Allows you to disable event listener for this specific domain + connectionProperties: # you can override the connection properties of each domain + bootstrap-servers: localhost:9092 + # Another domain can be configured with same properties structure that app + accounts: # this is a second domain name and can have another independent setup + connectionProperties: # you can override the connection properties of each domain + bootstrap-servers: localhost:9093 +``` + +You can override this settings programmatically through a `AsyncKafkaPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.props.AsyncProps; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncKafkaPropsDomainProperties customKafkaDomainProperties() { + KafkaProperties propertiesApp = new KafkaProperties(); + propertiesApp.setBootstrapServers(List.of("localhost:9092")); + + KafkaProperties propertiesAccounts = new KafkaProperties(); + propertiesAccounts.setBootstrapServers(List.of("localhost:9093")); + + return AsyncKafkaPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .withDomain("accounts", AsyncProps.builder() + .connectionProperties(propertiesAccounts) + .build()) + .build(); + } +} +``` + +Additionally, if you want to set only connection properties you can use the `AsyncKafkaPropsDomain.KafkaSecretFiller` +class. + +```java + +@Bean +@Primary +public AsyncKafkaPropsDomain.KafkaSecretFiller customKafkaFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + + + \ No newline at end of file diff --git a/docs/docs/scenarios/1-single-broker.md b/docs/docs/scenarios/1-single-broker.md new file mode 100644 index 00000000..ea4d7380 --- /dev/null +++ b/docs/docs/scenarios/1-single-broker.md @@ -0,0 +1,180 @@ +--- +sidebar_position: 1 +--- + +# Single Broker + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import ThemeImage from '../../src/components/ThemeImage'; + + + +Both apps the `App 1` and the `App 2` are connected to the same `Broker`, so both has the same connection configuration, +and `Broker` is considered the `app` domain for both apps. + + + + +You can customize some predefined variables of Reactive Commons + +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java) +bean. + +```yaml +app: + async: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + retryDelay: 1000 # interval for message retries, with and without DLQRetry + listenReplies: true # if you will not use ReqReply patter you can set it to false + createTopology: true # if your organization have restrictions with automatic topology creation you can set it to false and create it manually or by your organization process. + delayedCommands: false # Enable to send a delayed command to an external target + prefetchCount: 250 # is the maximum number of in flight messages you can reduce it to process less concurrent messages, this settings acts per instance of your service + useDiscardNotifierPerDomain: false # if true it uses a discard notifier for each domain,when false it uses a single discard notifier for all domains with default 'app' domain + enabled: true # if you want to disable this domain you can set it to false + brokerType: "rabbitmq" # please don't change this value + flux: + maxConcurrency: 250 # max concurrency of listener flow + domain: + ignoreThisListener: false # Allows you to disable event listener for this specific domain + events: + exchange: domainEvents # you can change the exchange, but you should do it in all applications consistently + eventsSuffix: subsEvents # events queue name suffix, name will be like ${spring.application.name}.${app.async.domain.events.eventsSuffix} + notificationSuffix: notification # notification events queue name suffix + direct: + exchange: directMessages # you can change the exchange, but you should do it in all applications + querySuffix: query # queries queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} + commandSuffix: '' # commands queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} or ${spring.application.name} if empty by default + discardTimeoutQueries: false # enable to discard this condition + global: + exchange: globalReply # you can change the exchange, but you should do it in all applications + repliesSuffix: replies # async query replies events queue name suffix + connectionProperties: # you can override the connection properties of each domain + host: localhost + port: 5672 + username: guest + password: guest + virtual-host: / +``` + +You can override this settings programmatically through a `AsyncRabbitPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncRabbitPropsDomainProperties customDomainProperties() { + RabbitProperties propertiesApp = new RabbitProperties(); + propertiesApp.setHost("localhost"); + propertiesApp.setPort(5672); + propertiesApp.setVirtualHost("/"); + propertiesApp.setUsername("guest"); + propertiesApp.setPassword("guest"); + + return AsyncRabbitPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .build(); + } +} +``` + +Additionally, if you want to set only connection properties you can use the `AsyncPropsDomain.RabbitSecretFiller` class. + +```java + +@Bean +@Primary +public AsyncPropsDomain.RabbitSecretFiller customFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + + + + You can customize some predefined variables of Reactive Commons + +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncKafkaProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java) +bean. + +```yaml +reactive: + commons: + kafka: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + retryDelay: 1000 # interval for message retries, with and without DLQRetry + checkExistingTopics: true # if you don't want to verify topic existence before send a record you can set it to false + createTopology: true # if your organization have restrictions with automatic topology creation you can set it to false and create it manually or by your organization process. + useDiscardNotifierPerDomain: false # if true it uses a discard notifier for each domain,when false it uses a single discard notifier for all domains with default 'app' domain + enabled: true # if you want to disable this domain you can set it to false + brokerType: "kafka" # please don't change this value + domain: + ignoreThisListener: false # Allows you to disable event listener for this specific domain + connectionProperties: # you can override the connection properties of each domain + bootstrap-servers: localhost:9092 +``` + +You can override this settings programmatically through a `AsyncKafkaPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.props.AsyncProps; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncKafkaPropsDomainProperties customKafkaDomainProperties() { + KafkaProperties propertiesApp = new KafkaProperties(); + propertiesApp.setBootstrapServers(List.of("localhost:9092")); + + return AsyncKafkaPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .build(); + } +} +``` + +Additionally, if you want to set only connection properties you can use the `AsyncKafkaPropsDomain.KafkaSecretFiller` +class. + +```java + +@Bean +@Primary +public AsyncKafkaPropsDomain.KafkaSecretFiller customKafkaFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + + + \ No newline at end of file diff --git a/docs/docs/scenarios/2-two-brokers-same-type.md b/docs/docs/scenarios/2-two-brokers-same-type.md new file mode 100644 index 00000000..4ff5045c --- /dev/null +++ b/docs/docs/scenarios/2-two-brokers-same-type.md @@ -0,0 +1,266 @@ +--- +sidebar_position: 2 +--- + +# Two Brokers same Broker Type - Emit to external Broker + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import ThemeImage from '../../src/components/ThemeImage'; + + + +`App 1` has only connection to `Broker 1`, which is considered the `app` domain for this app. This is the same as +the [Single Broker](./1-single-broker.md) scenario. + +`App 3` has only connection to `Broker 2`, which is considered the `app domain` for this app. This is the same as +the [Single Broker](./1-single-broker.md) scenario. + +`App 2` has two brokers, `Broker 1` and `Broker 2`. `Broker 1` is considered the `app` domain for this app, and `Broker 2` +is considered an external broker which will be called `accounts` domain for this scenario. + +So you need to configure the connection properties for each broker. + +In this scenario, `Broker 1` is considered the `app` domain by default, so `App 2` can listen and send all operations +from/to this broker, but it can only publish commands and queries to `Broker 2` and listen for events from the +`Broker 2` (`accounts` domain). + +Note: `App 2` cannot listen notification events, cannot listen for queries, and commands from `Broker 2`. This is for +responsibility segregation. + +To send commands and queries to `Broker 2` you need to use the `DirectAsyncGateway` interface with the `accounts` domain +, and the same when listen, you should pass the domain name. + +### Listen from external domain + +```java +@Bean +@Primary +public HandlerRegistry handlerRegistrySubs(UseCase useCase) { + return HandlerRegistry.register() + //.serveQuery(...) + //.handleCommand(...) + //.listenEvent(...) + //.listenNotificationEvent(...) + .listenDomainEvent("accounts", "event-name", handler::process, MyEventData.class) + .listenDomainCloudEvent("accounts", "event-name", handler::processCloudEvent); +} +``` + +### Send to external domain + +```java +@Service +@RequiredArgsConstructor +public class SampleRestController { + private final DirectAsyncGateway directAsyncGateway; + + public Mono getTeams() { + AsyncQuery query = .... + return directAsyncGateway.requestReply(query, "external-service", Teams.class, "accounts"); + } + + public Mono getTeamsCloudEvent() { + CloudEvent query = .... + return directAsyncGateway.requestReply(query, "external-service", CloudEvent.class, "accounts") + .map(...); + } +} +``` + + +Next are configurations needed to set up this scenario for `App 2`. + + + + +You can customize some predefined variables of Reactive Commons + +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java) +bean. + +```yaml +app: + async: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + retryDelay: 1000 # interval for message retries, with and without DLQRetry + listenReplies: true # if you will not use ReqReply patter you can set it to false + createTopology: true # if your organization have restrictions with automatic topology creation you can set it to false and create it manually or by your organization process. + delayedCommands: false # Enable to send a delayed command to an external target + prefetchCount: 250 # is the maximum number of in flight messages you can reduce it to process less concurrent messages, this settings acts per instance of your service + useDiscardNotifierPerDomain: false # if true it uses a discard notifier for each domain,when false it uses a single discard notifier for all domains with default 'app' domain + enabled: true # if you want to disable this domain you can set it to false + brokerType: "rabbitmq" # please don't change this value + flux: + maxConcurrency: 250 # max concurrency of listener flow + domain: + ignoreThisListener: false # Allows you to disable event listener for this specific domain + events: + exchange: domainEvents # you can change the exchange, but you should do it in all applications consistently + eventsSuffix: subsEvents # events queue name suffix, name will be like ${spring.application.name}.${app.async.domain.events.eventsSuffix} + notificationSuffix: notification # notification events queue name suffix + direct: + exchange: directMessages # you can change the exchange, but you should do it in all applications + querySuffix: query # queries queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} + commandSuffix: '' # commands queue name suffix, name will be like ${spring.application.name}.${app.async.direct.querySuffix} or ${spring.application.name} if empty by default + discardTimeoutQueries: false # enable to discard this condition + global: + exchange: globalReply # you can change the exchange, but you should do it in all applications + repliesSuffix: replies # async query replies events queue name suffix + connectionProperties: # you can override the connection properties of each domain + host: localhost + port: 5672 + username: guest + password: guest + virtual-host: / + # Another domain can be configured with same properties structure that app + accounts: # this is a second domain name and can have another independent setup + connectionProperties: # you can override the connection properties of each domain + host: localhost + port: 5673 + username: guest + password: guest + virtual-host: /accounts +``` + +You can override this settings programmatically through a `AsyncRabbitPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncRabbitPropsDomainProperties customDomainProperties() { + RabbitProperties propertiesApp = new RabbitProperties(); // this may be loaded from secrets + propertiesApp.setHost("localhost"); + propertiesApp.setPort(5672); + propertiesApp.setVirtualHost("/"); + propertiesApp.setUsername("guest"); + propertiesApp.setPassword("guest"); + + RabbitProperties propertiesAccounts = new RabbitProperties(); // this may be loaded from secrets + propertiesAccounts.setHost("localhost"); + propertiesAccounts.setPort(5673); + propertiesAccounts.setVirtualHost("/accounts"); + propertiesAccounts.setUsername("guest"); + propertiesAccounts.setPassword("guest"); + + return AsyncRabbitPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .withDomain("accounts", AsyncProps.builder() + .connectionProperties(propertiesAccounts) + .build()) + .build(); + } +} +``` + +Additionally, if you want to set only connection properties you can use the `AsyncPropsDomain.SecretFiller` class. + +```java + +@Bean +@Primary +public AsyncPropsDomain.SecretFiller customFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + + + + You can customize some predefined variables of Reactive Commons + +This can be done by Spring Boot `application.yaml` or by overriding +the [AsyncKafkaProps](https://github.com/reactive-commons/reactive-commons-java/blob/master/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java) +bean. + +```yaml +reactive: + commons: + kafka: + app: # this is the name of the default domain + withDLQRetry: false # if you want to have dlq queues with retries you can set it to true, you cannot change it after queues are created, because you will get an error, so you should delete topology before the change. + maxRetries: -1 # -1 will be considered default value. When withDLQRetry is true, it will be retried 10 times. When withDLQRetry is false, it will be retried indefinitely. + retryDelay: 1000 # interval for message retries, with and without DLQRetry + checkExistingTopics: true # if you don't want to verify topic existence before send a record you can set it to false + createTopology: true # if your organization have restrictions with automatic topology creation you can set it to false and create it manually or by your organization process. + useDiscardNotifierPerDomain: false # if true it uses a discard notifier for each domain,when false it uses a single discard notifier for all domains with default 'app' domain + enabled: true # if you want to disable this domain you can set it to false + brokerType: "kafka" # please don't change this value + domain: + ignoreThisListener: false # Allows you to disable event listener for this specific domain + connectionProperties: # you can override the connection properties of each domain + bootstrap-servers: localhost:9092 + # Another domain can be configured with same properties structure that app + accounts: # this is a second domain name and can have another independent setup + connectionProperties: # you can override the connection properties of each domain + bootstrap-servers: localhost:9093 +``` + +You can override this settings programmatically through a `AsyncKafkaPropsDomainProperties` bean. + +```java +package sample; + +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.props.AsyncProps; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomainProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; + +@Configuration +public class MyDomainConfig { + + @Bean + @Primary + public AsyncKafkaPropsDomainProperties customKafkaDomainProperties() { + KafkaProperties propertiesApp = new KafkaProperties(); // this may be loaded from secrets + propertiesApp.setBootstrapServers(List.of("localhost:9092")); + + KafkaProperties propertiesAccounts = new KafkaProperties(); // this may be loaded from secrets + propertiesAccounts.setBootstrapServers(List.of("localhost:9093")); + + return AsyncKafkaPropsDomainProperties.builder() + .withDomain("app", AsyncProps.builder() + .connectionProperties(propertiesApp) + .build()) + .withDomain("accounts", AsyncProps.builder() + .connectionProperties(propertiesAccounts) + .build()) + .build(); + } +} +``` + +Additionally, if you want to set only connection properties you can use the `AsyncKafkaPropsDomain.KafkaSecretFiller` +class. + +```java + +@Bean +@Primary +public AsyncKafkaPropsDomain.KafkaSecretFiller customKafkaFiller() { + return (domain, asyncProps) -> { + // customize asyncProps here by domain + }; +} +``` + + + \ No newline at end of file diff --git a/docs/docs/scenarios/_category_.json b/docs/docs/scenarios/_category_.json new file mode 100644 index 00000000..ee603a10 --- /dev/null +++ b/docs/docs/scenarios/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Communication scenarios", + "position": 3, + "link": { + "type": "generated-index", + "description": "Review configuration examples for common scenarios." + } +} diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 3f5a41a3..76ce0a31 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -97,10 +97,6 @@ const config = { label: 'Reactive Commons', to: '/docs/reactive-commons/getting-started', }, - { - label: 'Reactive Commons EDA', - to: '/docs/reactive-commons-eda/getting-started', - }, ], }, { diff --git a/docs/package-lock.json b/docs/package-lock.json index 86fe140b..5e6f9577 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -8,48 +8,49 @@ "name": "docs", "version": "0.0.0", "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/preset-classic": "3.2.1", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "prism-react-renderer": "^2.3.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "@docusaurus/core": "^3.6.3", + "@docusaurus/preset-classic": "^3.6.3", + "@docusaurus/theme-common": "^3.6.3", + "@mdx-js/react": "^3.1.0", + "clsx": "^2.1.1", + "prism-react-renderer": "^2.4.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.2.1", - "@docusaurus/types": "3.2.1" + "@docusaurus/module-type-aliases": "^3.6.3", + "@docusaurus/types": "^3.6.3" }, "engines": { "node": ">=18.0" } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", - "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" } }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", - "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", - "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -57,83 +58,175 @@ } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", - "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.2.tgz", - "integrity": "sha512-PvRQdCmtiU22dw9ZcTJkrVKgNBVAxKgD0/cfiqyxhA5+PHzA2WDt6jOmZ9QASkeM2BpyzClJb/Wr1yt2/t78Kw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", + "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", "dependencies": { - "@algolia/cache-common": "4.23.2" + "@algolia/cache-common": "4.24.0" } }, "node_modules/@algolia/cache-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.2.tgz", - "integrity": "sha512-OUK/6mqr6CQWxzl/QY0/mwhlGvS6fMtvEPyn/7AHUx96NjqDA4X4+Ju7aXFQKh+m3jW9VPB0B9xvEQgyAnRPNw==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", + "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==" }, "node_modules/@algolia/cache-in-memory": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.2.tgz", - "integrity": "sha512-rfbi/SnhEa3MmlqQvgYz/9NNJ156NkU6xFxjbxBtLWnHbpj+qnlMoKd+amoiacHRITpajg6zYbLM9dnaD3Bczw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", + "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", + "dependencies": { + "@algolia/cache-common": "4.24.0" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.15.0.tgz", + "integrity": "sha512-FaEM40iuiv1mAipYyiptP4EyxkJ8qHfowCpEeusdHUC4C7spATJYArD2rX3AxkVeREkDIgYEOuXcwKUbDCr7Nw==", "dependencies": { - "@algolia/cache-common": "4.23.2" + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-account": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.2.tgz", - "integrity": "sha512-VbrOCLIN/5I7iIdskSoSw3uOUPF516k4SjDD4Qz3BFwa3of7D9A0lzBMAvQEJJEPHWdVraBJlGgdJq/ttmquJQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", + "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/@algolia/client-analytics": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.2.tgz", - "integrity": "sha512-lLj7irsAztGhMoEx/SwKd1cwLY6Daf1Q5f2AOsZacpppSvuFvuBrmkzT7pap1OD/OePjLKxicJS8wNA0+zKtuw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", + "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/@algolia/client-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.2.tgz", - "integrity": "sha512-Q2K1FRJBern8kIfZ0EqPvUr3V29ICxCm/q42zInV+VJRjldAD9oTsMGwqUQ26GFMdFYmqkEfCbY4VGAiQhh22g==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.15.0.tgz", + "integrity": "sha512-IofrVh213VLsDkPoSKMeM9Dshrv28jhDlBDLRcVJQvlL8pzue7PEB1EZ4UoJFYS3NSn7JOcJ/V+olRQzXlJj1w==", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.15.0.tgz", + "integrity": "sha512-bDDEQGfFidDi0UQUCbxXOCdphbVAgbVmxvaV75cypBTQkJ+ABx/Npw7LkFGw1FsoVrttlrrQbwjvUB6mLVKs/w==", "dependencies": { - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.2.tgz", - "integrity": "sha512-vwPsgnCGhUcHhhQG5IM27z8q7dWrN9itjdvgA6uKf2e9r7vB+WXt4OocK0CeoYQt3OGEAExryzsB8DWqdMK5wg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", + "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.15.0.tgz", + "integrity": "sha512-wu8GVluiZ5+il8WIRsGKu8VxMK9dAlr225h878GGtpTL6VBvwyJvAyLdZsfFIpY0iN++jiNb31q2C1PlPL+n/A==", "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.2.tgz", - "integrity": "sha512-CxSB29OVGSE7l/iyoHvamMonzq7Ev8lnk/OkzleODZ1iBcCs3JC/XgTIKzN/4RSTrJ9QybsnlrN/bYCGufo7qw==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.15.0.tgz", + "integrity": "sha512-Z32gEMrRRpEta5UqVQA612sLdoqY3AovvUPClDfMxYrbdDAebmGDVPtSogUba1FZ4pP5dx20D3OV3reogLKsRA==", "dependencies": { - "@algolia/client-common": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/transporter": "4.23.2" + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/events": { @@ -141,66 +234,146 @@ "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" }, + "node_modules/@algolia/ingestion": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.15.0.tgz", + "integrity": "sha512-MkqkAxBQxtQ5if/EX2IPqFA7LothghVyvPoRNA/meS2AW2qkHwcxjuiBxv4H6mnAVEPfJlhu9rkdVz9LgCBgJg==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@algolia/logger-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.2.tgz", - "integrity": "sha512-jGM49Q7626cXZ7qRAWXn0jDlzvoA1FvN4rKTi1g0hxKsTTSReyYk0i1ADWjChDPl3Q+nSDhJuosM2bBUAay7xw==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", + "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==" }, "node_modules/@algolia/logger-console": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.2.tgz", - "integrity": "sha512-oo+lnxxEmlhTBTFZ3fGz1O8PJ+G+8FiAoMY2Qo3Q4w23xocQev6KqDTA1JQAGPDxAewNA2VBwWOsVXeXFjrI/Q==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", + "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", + "dependencies": { + "@algolia/logger-common": "4.24.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.15.0.tgz", + "integrity": "sha512-QPrFnnGLMMdRa8t/4bs7XilPYnoUXDY8PMQJ1sf9ZFwhUysYYhQNX34/enoO0LBjpoOY6rLpha39YQEFbzgKyQ==", "dependencies": { - "@algolia/logger-common": "4.23.2" + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.2.tgz", - "integrity": "sha512-Q75CjnzRCDzgIlgWfPnkLtrfF4t82JCirhalXkSSwe/c1GH5pWh4xUyDOR3KTMo+YxxX3zTlrL/FjHmUJEWEcg==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.2", - "@algolia/cache-common": "4.23.2", - "@algolia/cache-in-memory": "4.23.2", - "@algolia/client-common": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/logger-common": "4.23.2", - "@algolia/logger-console": "4.23.2", - "@algolia/requester-browser-xhr": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/requester-node-http": "4.23.2", - "@algolia/transporter": "4.23.2" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", + "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "dependencies": { + "@algolia/requester-common": "4.24.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.2.tgz", - "integrity": "sha512-TO9wLlp8+rvW9LnIfyHsu8mNAMYrqNdQ0oLF6eTWFxXfxG3k8F/Bh7nFYGk2rFAYty4Fw4XUtrv/YjeNDtM5og==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.15.0.tgz", + "integrity": "sha512-Po/GNib6QKruC3XE+WKP1HwVSfCDaZcXu48kD+gwmtDlqHWKc7Bq9lrS0sNZ456rfCKhXksOmMfUs4wRM/Y96w==", "dependencies": { - "@algolia/requester-common": "4.23.2" + "@algolia/client-common": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-common": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.2.tgz", - "integrity": "sha512-3EfpBS0Hri0lGDB5H/BocLt7Vkop0bTTLVUBB844HH6tVycwShmsV6bDR7yXbQvFP1uNpgePRD3cdBCjeHmk6Q==" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", + "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==" + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.15.0.tgz", + "integrity": "sha512-rOZ+c0P7ajmccAvpeeNrUmEKoliYFL8aOR5qGW5pFq3oj3Iept7Y5mEtEsOBYsRt6qLnaXn4zUKf+N8nvJpcIw==", + "dependencies": { + "@algolia/client-common": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } }, "node_modules/@algolia/requester-node-http": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.2.tgz", - "integrity": "sha512-SVzgkZM/malo+2SB0NWDXpnT7nO5IZwuDTaaH6SjLeOHcya1o56LSWXk+3F3rNLz2GVH+I/rpYKiqmHhSOjerw==", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.15.0.tgz", + "integrity": "sha512-b1jTpbFf9LnQHEJP5ddDJKE2sAlhYd7EVSOWgzo/27n/SfCoHfqD0VWntnWYD83PnOKvfe8auZ2+xCb0TXotrQ==", "dependencies": { - "@algolia/requester-common": "4.23.2" + "@algolia/client-common": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/transporter": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.2.tgz", - "integrity": "sha512-GY3aGKBy+8AK4vZh8sfkatDciDVKad5rTY2S10Aefyjh7e7UGBP4zigf42qVXwU8VOPwi7l/L7OACGMOFcjB0Q==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", + "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", "dependencies": { - "@algolia/cache-common": "4.23.2", - "@algolia/logger-common": "4.23.2", - "@algolia/requester-common": "4.23.2" + "@algolia/cache-common": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/requester-common": "4.24.0" } }, "node_modules/@ampproject/remapping": { @@ -216,11 +389,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -228,28 +402,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", - "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.4", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.4", - "@babel/parser": "^7.24.4", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -273,49 +447,39 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", - "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "dependencies": { - "@babel/types": "^7.24.0", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", - "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -332,18 +496,16 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz", - "integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", + "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.25.9", "semver": "^6.3.1" }, "engines": { @@ -362,12 +524,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -386,9 +548,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz", - "integrity": "sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", @@ -400,69 +562,38 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", "dependencies": { - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -472,32 +603,32 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -507,13 +638,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", - "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", + "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -522,185 +653,102 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", - "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.15", - "@babel/types": "^7.22.19" + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", - "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", - "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", - "@babel/types": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@babel/parser": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "dependencies": { - "color-convert": "^1.9.0" + "@babel/types": "^7.26.3" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", "dependencies": { - "has-flag": "^3.0.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", - "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", - "bin": { - "parser": "bin/babel-parser.js" + "node": ">=6.9.0" }, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz", - "integrity": "sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA==", + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -710,11 +758,11 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", - "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -724,13 +772,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", - "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -740,12 +788,12 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", - "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -765,10 +813,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -776,23 +824,26 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -801,34 +852,26 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.25.9" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", - "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -837,48 +880,59 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", - "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", + "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -887,78 +941,104 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", + "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -967,12 +1047,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -981,12 +1062,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", - "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -995,13 +1076,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1010,12 +1091,12 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", - "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1024,15 +1105,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", - "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20", - "@babel/plugin-syntax-async-generators": "^7.8.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1041,197 +1119,12 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", - "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", "dependencies": { - "@babel/helper-module-imports": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", - "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz", - "integrity": "sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", - "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", - "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.4", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz", - "integrity": "sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-split-export-declaration": "^7.22.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", - "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/template": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz", - "integrity": "sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", - "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", - "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", - "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", - "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", - "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1241,12 +1134,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", - "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1256,13 +1149,13 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", - "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1272,12 +1165,11 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", - "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1287,11 +1179,11 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", - "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1301,12 +1193,11 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", - "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1316,11 +1207,11 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", - "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1330,12 +1221,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", - "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1345,13 +1236,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1361,14 +1251,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", - "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1378,12 +1268,12 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", - "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1393,12 +1283,12 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1408,11 +1298,11 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", - "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1422,12 +1312,11 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", - "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", + "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1437,12 +1326,11 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", - "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1452,14 +1340,13 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz", - "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.1" + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1469,12 +1356,12 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", - "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1484,12 +1371,11 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", - "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1499,13 +1385,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz", - "integrity": "sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1515,11 +1400,11 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz", - "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1529,12 +1414,12 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", - "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1544,14 +1429,13 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz", - "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1561,11 +1445,11 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", - "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1575,11 +1459,11 @@ } }, "node_modules/@babel/plugin-transform-react-constant-elements": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.1.tgz", - "integrity": "sha512-QXp1U9x0R7tkiGB0FOk8o74jhnap0FlZ5gNkRIWdG3eP+SvMFg118e1zaWewDzgABb106QSKpVsD3Wgd8t6ifA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz", + "integrity": "sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1589,11 +1473,11 @@ } }, "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.1.tgz", - "integrity": "sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1603,15 +1487,15 @@ } }, "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", - "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.23.3", - "@babel/types": "^7.23.4" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1621,11 +1505,11 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", - "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", + "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.22.5" + "@babel/plugin-transform-react-jsx": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1635,12 +1519,12 @@ } }, "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.1.tgz", - "integrity": "sha512-+pWEAaDJvSm9aFvJNpLiM2+ktl2Sn2U5DdyiWdZBxmLc6+xGt88dvFqsHiAiDS+8WqUwbDfkKz9jRxK3M0k+kA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", + "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1650,11 +1534,11 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", - "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.25.9", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1664,12 +1548,27 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", - "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1679,14 +1578,14 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz", - "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz", + "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==", "dependencies": { - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, @@ -1706,11 +1605,11 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", - "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1720,12 +1619,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", - "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1735,11 +1634,11 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", - "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1749,11 +1648,11 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", - "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1763,11 +1662,11 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz", - "integrity": "sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1777,14 +1676,15 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.4.tgz", - "integrity": "sha512-79t3CQ8+oBGk/80SQ8MN3Bs3obf83zJ0YZjDmDaEZN8MqhMI760apl5z6a20kFeMXBwJX99VpKT8CKxEBp5H1g==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.3.tgz", + "integrity": "sha512-6+5hpdr6mETwSKjmJUdYw0EIkATiQhnELWlE3kJFBwSg/BGIVwVaVbX+gOXBCdc7Ln1RXZxyWGecIXhUfnl7oA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.4", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/plugin-syntax-typescript": "^7.24.1" + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1794,11 +1694,11 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", - "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1808,12 +1708,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", - "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1823,12 +1723,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", - "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1838,12 +1738,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", - "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1853,90 +1753,78 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.4.tgz", - "integrity": "sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A==", - "dependencies": { - "@babel/compat-data": "^7.24.4", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.4", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", + "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", + "dependencies": { + "@babel/compat-data": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.1", - "@babel/plugin-syntax-import-attributes": "^7.24.1", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.1", - "@babel/plugin-transform-async-generator-functions": "^7.24.3", - "@babel/plugin-transform-async-to-generator": "^7.24.1", - "@babel/plugin-transform-block-scoped-functions": "^7.24.1", - "@babel/plugin-transform-block-scoping": "^7.24.4", - "@babel/plugin-transform-class-properties": "^7.24.1", - "@babel/plugin-transform-class-static-block": "^7.24.4", - "@babel/plugin-transform-classes": "^7.24.1", - "@babel/plugin-transform-computed-properties": "^7.24.1", - "@babel/plugin-transform-destructuring": "^7.24.1", - "@babel/plugin-transform-dotall-regex": "^7.24.1", - "@babel/plugin-transform-duplicate-keys": "^7.24.1", - "@babel/plugin-transform-dynamic-import": "^7.24.1", - "@babel/plugin-transform-exponentiation-operator": "^7.24.1", - "@babel/plugin-transform-export-namespace-from": "^7.24.1", - "@babel/plugin-transform-for-of": "^7.24.1", - "@babel/plugin-transform-function-name": "^7.24.1", - "@babel/plugin-transform-json-strings": "^7.24.1", - "@babel/plugin-transform-literals": "^7.24.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", - "@babel/plugin-transform-member-expression-literals": "^7.24.1", - "@babel/plugin-transform-modules-amd": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.24.1", - "@babel/plugin-transform-modules-systemjs": "^7.24.1", - "@babel/plugin-transform-modules-umd": "^7.24.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.24.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", - "@babel/plugin-transform-numeric-separator": "^7.24.1", - "@babel/plugin-transform-object-rest-spread": "^7.24.1", - "@babel/plugin-transform-object-super": "^7.24.1", - "@babel/plugin-transform-optional-catch-binding": "^7.24.1", - "@babel/plugin-transform-optional-chaining": "^7.24.1", - "@babel/plugin-transform-parameters": "^7.24.1", - "@babel/plugin-transform-private-methods": "^7.24.1", - "@babel/plugin-transform-private-property-in-object": "^7.24.1", - "@babel/plugin-transform-property-literals": "^7.24.1", - "@babel/plugin-transform-regenerator": "^7.24.1", - "@babel/plugin-transform-reserved-words": "^7.24.1", - "@babel/plugin-transform-shorthand-properties": "^7.24.1", - "@babel/plugin-transform-spread": "^7.24.1", - "@babel/plugin-transform-sticky-regex": "^7.24.1", - "@babel/plugin-transform-template-literals": "^7.24.1", - "@babel/plugin-transform-typeof-symbol": "^7.24.1", - "@babel/plugin-transform-unicode-escapes": "^7.24.1", - "@babel/plugin-transform-unicode-property-regex": "^7.24.1", - "@babel/plugin-transform-unicode-regex": "^7.24.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.31.0", + "core-js-compat": "^3.38.1", "semver": "^6.3.1" }, "engines": { @@ -1968,16 +1856,16 @@ } }, "node_modules/@babel/preset-react": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.1.tgz", - "integrity": "sha512-eFa8up2/8cZXLIpkafhaADTXSnl7IsUFCYenRWrARBz0/qZwcT0RBXpys0LJU4+WfPoF2ZG6ew6s2V6izMCwRA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", + "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-transform-react-display-name": "^7.24.1", - "@babel/plugin-transform-react-jsx": "^7.23.4", - "@babel/plugin-transform-react-jsx-development": "^7.22.5", - "@babel/plugin-transform-react-pure-annotations": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-react-display-name": "^7.25.9", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/plugin-transform-react-jsx-development": "^7.25.9", + "@babel/plugin-transform-react-pure-annotations": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1987,15 +1875,15 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", - "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz", + "integrity": "sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-syntax-jsx": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.24.1", - "@babel/plugin-transform-typescript": "^7.24.1" + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-typescript": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2004,15 +1892,10 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" - }, "node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2021,9 +1904,9 @@ } }, "node_modules/@babel/runtime-corejs3": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.4.tgz", - "integrity": "sha512-VOQOexSilscN24VEY810G/PqtpFvx/z6UqDIjIWbDe2368HhDLkYN5TYwaEz/+eRCUkhJ2WaNLLmQAlxzfWj4w==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz", + "integrity": "sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==", "dependencies": { "core-js-pure": "^3.30.2", "regenerator-runtime": "^0.14.0" @@ -2033,31 +1916,28 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", - "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", - "dependencies": { - "@babel/code-frame": "^7.24.1", - "@babel/generator": "^7.24.1", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.1", - "@babel/types": "^7.24.0", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2066,13 +1946,12 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -2087,155 +1966,1318 @@ "node": ">=0.1.90" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.4.tgz", + "integrity": "sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", - "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==" - }, - "node_modules/@docsearch/react": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", - "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", - "dependencies": { - "@algolia/autocomplete-core": "1.9.3", - "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.0", - "algoliasearch": "^4.19.1" + "node": ">=18" }, "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz", + "integrity": "sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" }, - "search-insights": { - "optional": true + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" } + ], + "engines": { + "node": ">=18" } }, - "node_modules/@docusaurus/core": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.2.1.tgz", - "integrity": "sha512-ZeMAqNvy0eBv2dThEeMuNzzuu+4thqMQakhxsgT5s02A8LqRcdkg+rbcnuNqUIpekQ4GRx3+M5nj0ODJhBXo9w==", - "dependencies": { - "@babel/core": "^7.23.3", - "@babel/generator": "^7.23.3", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.22.9", - "@babel/preset-env": "^7.22.9", - "@babel/preset-react": "^7.22.5", - "@babel/preset-typescript": "^7.22.5", - "@babel/runtime": "^7.22.6", - "@babel/runtime-corejs3": "^7.22.6", - "@babel/traverse": "^7.22.8", - "@docusaurus/cssnano-preset": "3.2.1", - "@docusaurus/logger": "3.2.1", - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", - "@svgr/webpack": "^6.5.1", - "autoprefixer": "^10.4.14", - "babel-loader": "^9.1.3", - "babel-plugin-dynamic-import-node": "^2.3.3", - "boxen": "^6.2.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "clean-css": "^5.3.2", - "cli-table3": "^0.6.3", - "combine-promises": "^1.1.0", - "commander": "^5.1.0", - "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.31.1", - "css-loader": "^6.8.1", - "css-minimizer-webpack-plugin": "^4.2.2", - "cssnano": "^5.1.15", - "del": "^6.1.1", - "detect-port": "^1.5.1", - "escape-html": "^1.0.3", - "eta": "^2.2.0", - "eval": "^0.1.8", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "html-minifier-terser": "^7.2.0", - "html-tags": "^3.3.1", - "html-webpack-plugin": "^5.5.3", - "leven": "^3.1.0", - "lodash": "^4.17.21", - "mini-css-extract-plugin": "^2.7.6", - "p-map": "^4.0.0", - "postcss": "^8.4.26", - "postcss-loader": "^7.3.3", - "prompts": "^2.4.2", - "react-dev-utils": "^12.0.1", - "react-helmet-async": "^1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@5.5.2", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", - "react-router": "^5.3.4", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.4", - "rtl-detect": "^1.0.4", - "semver": "^7.5.4", - "serve-handler": "^6.1.5", - "shelljs": "^0.8.5", - "terser-webpack-plugin": "^5.3.9", - "tslib": "^2.6.0", - "update-notifier": "^6.0.2", - "url-loader": "^4.1.1", - "webpack": "^5.88.1", - "webpack-bundle-analyzer": "^4.9.0", - "webpack-dev-server": "^4.15.1", - "webpack-merge": "^5.9.0", - "webpackbar": "^5.0.2" - }, - "bin": { - "docusaurus": "bin/docusaurus.mjs" - }, + "node_modules/@csstools/css-calc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.0.tgz", + "integrity": "sha512-X69PmFOrjTZfN5ijxtI8hZ9kRADFSLrmmQ6hgDJ272Il049WGKpDY64KhrFm/7rbWve0z81QepawzjkKlqkNGw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=18.0" + "node": ">=18" }, "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" } }, - "node_modules/@docusaurus/cssnano-preset": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.2.1.tgz", - "integrity": "sha512-wTL9KuSSbMJjKrfu385HZEzAoamUsbKqwscAQByZw4k6Ja/RWpqgVvt/CbAC+aYEH6inLzOt+MjuRwMOrD3VBA==", + "node_modules/@csstools/css-color-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.6.tgz", + "integrity": "sha512-S/IjXqTHdpI4EtzGoNCHfqraXF37x12ZZHA1Lk7zoT5pm2lMjFuqhX/89L7dqX4CcMacKK+6ZCs5TmEGb/+wKw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "cssnano-preset-advanced": "^5.3.10", - "postcss": "^8.4.26", - "postcss-sort-media-queries": "^4.4.1", - "tslib": "^2.6.0" + "@csstools/color-helpers": "^5.0.1", + "@csstools/css-calc": "^2.1.0" }, "engines": { - "node": ">=18.0" + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz", + "integrity": "sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.6.tgz", + "integrity": "sha512-EcvXfC60cTIumzpsxWuvVjb7rsJEHPvqn3jeMEBUaE3JSc4FRuP7mEQ+1eicxWmIrs3FtzMH9gR3sgA5TH+ebQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-function": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.6.tgz", + "integrity": "sha512-jVKdJn4+JkASYGhyPO+Wa5WXSx1+oUgaXb3JsjJn/BlrtFh5zjocCY7pwWi0nuP24V1fY7glQsxEYcYNy0dMFg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-content-alt-text": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.4.tgz", + "integrity": "sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-exponential-functions": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.5.tgz", + "integrity": "sha512-mi8R6dVfA2nDoKM3wcEi64I8vOYEgQVtVKCfmLHXupeLpACfGAided5ddMt5f+CnEodNu4DifuVwb0I6fQDGGQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", + "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.6.tgz", + "integrity": "sha512-0ke7fmXfc8H+kysZz246yjirAH6JFhyX9GTlyRnM0exHO80XcA9zeJpy5pOp5zo/AZiC/q5Pf+Hw7Pd6/uAoYA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.6.tgz", + "integrity": "sha512-Itrbx6SLUzsZ6Mz3VuOlxhbfuyLTogG5DwEF1V8dAi24iMuvQPIHd7Ti+pNDp7j6WixndJGZaoNR0f9VSzwuTg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.6.tgz", + "integrity": "sha512-927Pqy3a1uBP7U8sTfaNdZVB0mNXzIrJO/GZ8us9219q9n06gOqCdfZ0E6d1P66Fm0fYHvxfDbfcUuwAn5UwhQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.0.tgz", + "integrity": "sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-initial": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.0.tgz", + "integrity": "sha512-dv2lNUKR+JV+OOhZm9paWzYBXOCi+rJPqJ2cJuhh9xd8USVrd0cBEPczla81HNOyThMQWeCcdln3gZkQV2kYxA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz", + "integrity": "sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.7.tgz", + "integrity": "sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", + "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overflow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", + "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", + "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-resize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", + "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.3.tgz", + "integrity": "sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-minmax": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.5.tgz", + "integrity": "sha512-sdh5i5GToZOIAiwhdntRWv77QDtsxP2r2gXW/WbLSCoLr00KTq/yiF1qlQ5XX2+lmiFa8rATKMcbwl3oXDMNew==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.4.tgz", + "integrity": "sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", + "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", + "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.6.tgz", + "integrity": "sha512-Hptoa0uX+XsNacFBCIQKTUBrFKDiplHan42X73EklG6XmQLG7/aIvxoNhvZ7PvOWMt67Pw3bIlUY2nD6p5vL8A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.0.tgz", + "integrity": "sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-random-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-1.0.1.tgz", + "integrity": "sha512-Ab/tF8/RXktQlFwVhiC70UNfpFQRhtE5fQQoP2pO+KCPGLsLdWFiOuHgSRtBOqEshCVAzR4H6o38nhvRZq8deA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.6.tgz", + "integrity": "sha512-yxP618Xb+ji1I624jILaYM62uEmZcmbdmFoZHoaThw896sq0vU39kqTTF+ZNic9XyPtPMvq0vyvbgmHaszq8xg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", + "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-sign-functions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.0.tgz", + "integrity": "sha512-SLcc20Nujx/kqbSwDmj6oaXgpy3UjFhBy1sfcqPgDkHfOIfUtUVH7OXO+j7BU4v/At5s61N5ZX6shvgPwluhsA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.5.tgz", + "integrity": "sha512-G6SJ6hZJkhxo6UZojVlLo14MohH4J5J7z8CRBrxxUYy9JuZiIqUo5TBYyDGcE0PLdzpg63a7mHSJz3VD+gMwqw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.1.tgz", + "integrity": "sha512-xPZIikbx6jyzWvhms27uugIc0I4ykH4keRvoa3rxX5K7lEhkbd54rjj/dv60qOCTisoS+3bmwJTeyV1VNBrXaw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/color-helpers": "^5.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.5.tgz", + "integrity": "sha512-/YQThYkt5MLvAmVu7zxjhceCYlKrYddK6LEmK5I4ojlS6BmO9u2yO4+xjXzu2+NPYmHSTtP4NFSamBCMmJ1NJA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-calc": "^2.1.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", + "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/utilities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", + "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.0.tgz", + "integrity": "sha512-pieeipSOW4sQ0+bE5UFC51AOZp9NGxg89wAlZ1BAQFaiRAGK1IKUaPQ0UGZeNctJXyqZ1UvBtOQh2HH+U5GtmA==" + }, + "node_modules/@docsearch/react": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.0.tgz", + "integrity": "sha512-WnFK720+iwTVt94CxY3u+FgX6exb3BfN5kE9xUY6uuAH/9W/UFboBZFLlrw/zxFRHoHZCOXRtOylsXF+6LHI+Q==", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.0", + "algoliasearch": "^5.12.0" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/client-analytics": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.15.0.tgz", + "integrity": "sha512-lho0gTFsQDIdCwyUKTtMuf9nCLwq9jOGlLGIeQGKDxXF7HbiAysFIu5QW/iQr1LzMgDyM9NH7K98KY+BiIFriQ==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/client-personalization": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.15.0.tgz", + "integrity": "sha512-LfaZqLUWxdYFq44QrasCDED5bSYOswpQjSiIL7Q5fYlefAAUO95PzBPKCfUhSwhb4rKxigHfDkd81AvEicIEoA==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/recommend": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.15.0.tgz", + "integrity": "sha512-5eupMwSqMLDObgSMF0XG958zR6GJP3f7jHDQ3/WlzCM9/YIJiWIUoJFGsko9GYsA5xbLDHE/PhWtq4chcCdaGQ==", + "dependencies": { + "@algolia/client-common": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@docsearch/react/node_modules/algoliasearch": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.15.0.tgz", + "integrity": "sha512-Yf3Swz1s63hjvBVZ/9f2P1Uu48GjmjCN+Esxb6MAONMGtZB1fRX8/S1AhUTtsuTlcGovbYLxpHgc7wEzstDZBw==", + "dependencies": { + "@algolia/client-abtesting": "5.15.0", + "@algolia/client-analytics": "5.15.0", + "@algolia/client-common": "5.15.0", + "@algolia/client-insights": "5.15.0", + "@algolia/client-personalization": "5.15.0", + "@algolia/client-query-suggestions": "5.15.0", + "@algolia/client-search": "5.15.0", + "@algolia/ingestion": "1.15.0", + "@algolia/monitoring": "1.15.0", + "@algolia/recommend": "5.15.0", + "@algolia/requester-browser-xhr": "5.15.0", + "@algolia/requester-fetch": "5.15.0", + "@algolia/requester-node-http": "5.15.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@docusaurus/babel": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.6.3.tgz", + "integrity": "sha512-7dW9Hat9EHYCVicFXYA4hjxBY38+hPuCURL8oRF9fySRm7vzNWuEOghA1TXcykuXZp0HLG2td4RhDxCvGG7tNw==", + "dependencies": { + "@babel/core": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.25.9", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.25.9", + "@babel/runtime": "^7.25.9", + "@babel/runtime-corejs3": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@docusaurus/logger": "3.6.3", + "@docusaurus/utils": "3.6.3", + "babel-plugin-dynamic-import-node": "^2.3.3", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/bundler": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.6.3.tgz", + "integrity": "sha512-47JLuc8D4wA+6VOvmMd5fUC9rFppBQpQOnxDYiVXffm/DeV/wmm3sbpNd5Y+O+G2+nevLTRnvCm/qyancv0Y3A==", + "dependencies": { + "@babel/core": "^7.25.9", + "@docusaurus/babel": "3.6.3", + "@docusaurus/cssnano-preset": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "babel-loader": "^9.2.1", + "clean-css": "^5.3.2", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.8.1", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "file-loader": "^6.2.0", + "html-minifier-terser": "^7.2.0", + "mini-css-extract-plugin": "^2.9.1", + "null-loader": "^4.0.1", + "postcss": "^8.4.26", + "postcss-loader": "^7.3.3", + "postcss-preset-env": "^10.1.0", + "react-dev-utils": "^12.0.1", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "webpack": "^5.95.0", + "webpackbar": "^6.0.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.6.3.tgz", + "integrity": "sha512-xL7FRY9Jr5DWqB6pEnqgKqcMPJOX5V0pgWXi5lCiih11sUBmcFKM7c3+GyxcVeeWFxyYSDP3grLTWqJoP4P9Vw==", + "dependencies": { + "@docusaurus/babel": "3.6.3", + "@docusaurus/bundler": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cli-table3": "^0.6.3", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "core-js": "^3.31.1", + "del": "^6.1.1", + "detect-port": "^1.5.1", + "escape-html": "^1.0.3", + "eta": "^2.2.0", + "eval": "^0.1.8", + "fs-extra": "^11.1.1", + "html-tags": "^3.3.1", + "html-webpack-plugin": "^5.6.0", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "p-map": "^4.0.0", + "prompts": "^2.4.2", + "react-dev-utils": "^12.0.1", + "react-helmet-async": "^1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", + "react-loadable-ssr-addon-v5-slorber": "^1.0.1", + "react-router": "^5.3.4", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.4", + "rtl-detect": "^1.0.4", + "semver": "^7.5.4", + "serve-handler": "^6.1.6", + "shelljs": "^0.8.5", + "tslib": "^2.6.0", + "update-notifier": "^6.0.2", + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "^4.15.2", + "webpack-merge": "^6.0.1" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@mdx-js/react": "^3.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/cssnano-preset": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.6.3.tgz", + "integrity": "sha512-qP7SXrwZ+23GFJdPN4aIHQrZW+oH/7tzwEuc/RNL0+BdZdmIjYQqUxdXsjE4lFxLNZjj0eUrSNYIS6xwfij+5Q==", + "dependencies": { + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.4.38", + "postcss-sort-media-queries": "^5.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" } }, "node_modules/@docusaurus/logger": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.2.1.tgz", - "integrity": "sha512-0voOKJCn9RaM3np6soqEfo7SsVvf2C+CDTWhW+H/1AyBhybASpExtDEz+7ECck9TwPzFQ5tt+I3zVugUJbJWDg==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.6.3.tgz", + "integrity": "sha512-xSubJixcNyMV9wMV4q0s47CBz3Rlc5jbcCCuij8pfQP8qn/DIpt0ks8W6hQWzHAedg/J/EwxxUOUrnEoKzJo8g==", "dependencies": { "chalk": "^4.1.2", "tslib": "^2.6.0" @@ -2245,13 +3287,13 @@ } }, "node_modules/@docusaurus/mdx-loader": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.2.1.tgz", - "integrity": "sha512-Fs8tXhXKZjNkdGaOy1xSLXSwfjCMT73J3Zfrju2U16uGedRFRjgK0ojpK5tiC7TnunsL3tOFgp1BSMBRflX9gw==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.6.3.tgz", + "integrity": "sha512-3iJdiDz9540ppBseeI93tWTDtUGVkxzh59nMq4ignylxMuXBLK8dFqVeaEor23v1vx6TrGKZ2FuLaTB+U7C0QQ==", "dependencies": { - "@docusaurus/logger": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "@docusaurus/logger": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "@mdx-js/mdx": "^3.0.0", "@slorber/remark-comment": "^1.0.0", "escape-html": "^1.0.3", @@ -2283,18 +3325,17 @@ } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.2.1.tgz", - "integrity": "sha512-FyViV5TqhL1vsM7eh29nJ5NtbRE6Ra6LP1PDcPvhwPSlA7eiWGRKAn3jWwMUcmjkos5SYY+sr0/feCdbM3eQHQ==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.6.3.tgz", + "integrity": "sha512-MjaXX9PN/k5ugNvfRZdWyKWq4FsrhN4LEXaj0pEmMebJuBNlFeGyKQUa9DRhJHpadNaiMLrbo9m3U7Ig5YlsZg==", "dependencies": { - "@docusaurus/react-loadable": "5.5.2", - "@docusaurus/types": "3.2.1", + "@docusaurus/types": "3.6.3", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", "@types/react-router-dom": "*", "react-helmet-async": "*", - "react-loadable": "npm:@docusaurus/react-loadable@5.5.2" + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" }, "peerDependencies": { "react": "*", @@ -2302,18 +3343,19 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.2.1.tgz", - "integrity": "sha512-lOx0JfhlGZoZu6pEJfeEpSISZR5dQbJGGvb42IP13G5YThNHhG9R9uoWuo4IOimPqBC7sHThdLA3VLevk61Fsw==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/logger": "3.2.1", - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", - "cheerio": "^1.0.0-rc.12", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.6.3.tgz", + "integrity": "sha512-k0ogWwwJU3pFRFfvW1kRVHxzf2DutLGaaLjAnHVEU6ju+aRP0Z5ap/13DHyPOfHeE4WKpn/M0TqjdwZAcY3kAw==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/theme-common": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", + "cheerio": "1.0.0-rc.12", "feed": "^4.2.2", "fs-extra": "^11.1.1", "lodash": "^4.17.21", @@ -2328,23 +3370,25 @@ "node": ">=18.0" }, "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.2.1.tgz", - "integrity": "sha512-GHe5b/lCskAR8QVbfWAfPAApvRZgqk7FN3sOHgjCtjzQACZxkHmq6QqyqZ8Jp45V7lVck4wt2Xw2IzBJ7Cz3bA==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/logger": "3.2.1", - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/module-type-aliases": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.6.3.tgz", + "integrity": "sha512-r2wS8y/fsaDcxkm20W5bbYJFPzdWdEaTWVYjNxlHlcmX086eqQR1Fomlg9BHTJ0dLXPzAlbC8EN4XqMr3QzNCQ==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/module-type-aliases": "3.6.3", + "@docusaurus/theme-common": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", @@ -2363,15 +3407,15 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.2.1.tgz", - "integrity": "sha512-TOqVfMVTAHqWNEGM94Drz+PUpHDbwFy6ucHFgyTx9zJY7wPNSG5EN+rd/mU7OvAi26qpOn2o9xTdUmb28QLjEQ==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.6.3.tgz", + "integrity": "sha512-eHrmTgjgLZsuqfsYr5X2xEwyIcck0wseSofWrjTwT9FLOWp+KDmMAuVK+wRo7sFImWXZk3oV/xX/g9aZrhD7OA==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" @@ -2385,13 +3429,13 @@ } }, "node_modules/@docusaurus/plugin-debug": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.2.1.tgz", - "integrity": "sha512-AMKq8NuUKf2sRpN1m/sIbqbRbnmk+rSA+8mNU1LNxEl9BW9F/Gng8m9HKlzeyMPrf5XidzS1jqkuTLDJ6KIrFw==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.6.3.tgz", + "integrity": "sha512-zB9GXfIZNPRfzKnNjU6xGVrqn9bPXuGhpjgsuc/YtcTDjnjhasg38NdYd5LEqXex5G/zIorQgWB3n6x/Ut62vQ==", "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", + "@docusaurus/core": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", "fs-extra": "^11.1.1", "react-json-view-lite": "^1.2.0", "tslib": "^2.6.0" @@ -2405,13 +3449,13 @@ } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.2.1.tgz", - "integrity": "sha512-/rJ+9u+Px0eTCiF4TNcNtj3kHf8cp6K1HCwOTdbsSlz6Xn21syZYcy+f1VM9wF6HrvUkXUcbM5TDCvg2IRL6bQ==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.6.3.tgz", + "integrity": "sha512-rCDNy1QW8Dag7nZq67pcum0bpFLrwvxJhYuVprhFh8BMBDxV0bY+bAkGHbSf68P3Bk9C3hNOAXX1srGLIDvcTA==", "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "@docusaurus/core": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "tslib": "^2.6.0" }, "engines": { @@ -2423,13 +3467,13 @@ } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.2.1.tgz", - "integrity": "sha512-XtuJnlMvYfppeVdUyKiDIJAa/gTJKCQU92z8CLZZ9ibJdgVjFOLS10s0hIC0eL5z0U2u2loJz2rZ63HOkNHbBA==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.6.3.tgz", + "integrity": "sha512-+OyDvhM6rqVkQOmLVkQWVJAizEEfkPzVWtIHXlWPOCFGK9X4/AWeBSrU0WG4iMg9Z4zD4YDRrU+lvI4s6DSC+w==", "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "@docusaurus/core": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "@types/gtag.js": "^0.0.12", "tslib": "^2.6.0" }, @@ -2442,13 +3486,13 @@ } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.2.1.tgz", - "integrity": "sha512-wiS/kE0Ny5pnjTxVCs8ljRnkL1RVMj59t6jmSsgEX7piDOoaXSMIUaoIt9ogS/v132uO0xEsxHstkRUZHQyPcQ==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.6.3.tgz", + "integrity": "sha512-1M6UPB13gWUtN2UHX083/beTn85PlRI9ABItTl/JL1FJ5dJTWWFXXsHf9WW/6hrVwthwTeV/AGbGKvLKV+IlCA==", "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "@docusaurus/core": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "tslib": "^2.6.0" }, "engines": { @@ -2460,16 +3504,16 @@ } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.2.1.tgz", - "integrity": "sha512-uWZ7AxzdeaQSTCwD2yZtOiEm9zyKU+wqCmi/Sf25kQQqqFSBZUStXfaQ8OHP9cecnw893ZpZ811rPhB/wfujJw==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/logger": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.6.3.tgz", + "integrity": "sha512-94qOO4M9Fwv9KfVQJsgbe91k+fPJ4byf1L3Ez8TUa6TAFPo/BrLwQ80zclHkENlL1824TuxkcMKv33u6eydQCg==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "fs-extra": "^11.1.1", "sitemap": "^7.1.1", "tslib": "^2.6.0" @@ -2483,23 +3527,23 @@ } }, "node_modules/@docusaurus/preset-classic": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.2.1.tgz", - "integrity": "sha512-E3OHSmttpEBcSMhfPBq3EJMBxZBM01W1rnaCUTXy9EHvkmB5AwgTfW1PwGAybPAX579ntE03R+2zmXdizWfKnQ==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/plugin-content-blog": "3.2.1", - "@docusaurus/plugin-content-docs": "3.2.1", - "@docusaurus/plugin-content-pages": "3.2.1", - "@docusaurus/plugin-debug": "3.2.1", - "@docusaurus/plugin-google-analytics": "3.2.1", - "@docusaurus/plugin-google-gtag": "3.2.1", - "@docusaurus/plugin-google-tag-manager": "3.2.1", - "@docusaurus/plugin-sitemap": "3.2.1", - "@docusaurus/theme-classic": "3.2.1", - "@docusaurus/theme-common": "3.2.1", - "@docusaurus/theme-search-algolia": "3.2.1", - "@docusaurus/types": "3.2.1" + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.6.3.tgz", + "integrity": "sha512-VHSYWROT3flvNNI1SrnMOtW1EsjeHNK9dhU6s9eY5hryZe79lUqnZJyze/ymDe2LXAqzyj6y5oYvyBoZZk6ErA==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/plugin-content-blog": "3.6.3", + "@docusaurus/plugin-content-docs": "3.6.3", + "@docusaurus/plugin-content-pages": "3.6.3", + "@docusaurus/plugin-debug": "3.6.3", + "@docusaurus/plugin-google-analytics": "3.6.3", + "@docusaurus/plugin-google-gtag": "3.6.3", + "@docusaurus/plugin-google-tag-manager": "3.6.3", + "@docusaurus/plugin-sitemap": "3.6.3", + "@docusaurus/theme-classic": "3.6.3", + "@docusaurus/theme-common": "3.6.3", + "@docusaurus/theme-search-algolia": "3.6.3", + "@docusaurus/types": "3.6.3" }, "engines": { "node": ">=18.0" @@ -2509,39 +3553,28 @@ "react-dom": "^18.0.0" } }, - "node_modules/@docusaurus/react-loadable": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz", - "integrity": "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==", - "dependencies": { - "@types/react": "*", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": "*" - } - }, "node_modules/@docusaurus/theme-classic": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.2.1.tgz", - "integrity": "sha512-+vSbnQyoWjc6vRZi4vJO2dBU02wqzynsai15KK+FANZudrYaBHtkbLZAQhgmxzBGVpxzi87gRohlMm+5D8f4tA==", - "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/module-type-aliases": "3.2.1", - "@docusaurus/plugin-content-blog": "3.2.1", - "@docusaurus/plugin-content-docs": "3.2.1", - "@docusaurus/plugin-content-pages": "3.2.1", - "@docusaurus/theme-common": "3.2.1", - "@docusaurus/theme-translations": "3.2.1", - "@docusaurus/types": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.6.3.tgz", + "integrity": "sha512-1RRLK1tSArI2c00qugWYO3jRocjOZwGF1mBzPPylDVRwWCS/rnWWR91ChdbbaxIupRJ+hX8ZBYrwr5bbU0oztQ==", + "dependencies": { + "@docusaurus/core": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/module-type-aliases": "3.6.3", + "@docusaurus/plugin-content-blog": "3.6.3", + "@docusaurus/plugin-content-docs": "3.6.3", + "@docusaurus/plugin-content-pages": "3.6.3", + "@docusaurus/theme-common": "3.6.3", + "@docusaurus/theme-translations": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.43", + "infima": "0.2.0-alpha.45", "lodash": "^4.17.21", "nprogress": "^0.2.0", "postcss": "^8.4.26", @@ -2561,17 +3594,14 @@ } }, "node_modules/@docusaurus/theme-common": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.2.1.tgz", - "integrity": "sha512-d+adiD7L9xv6EvfaAwUqdKf4orsM3jqgeqAM+HAjgL/Ux0GkVVnfKr+tsoe+4ow4rHe6NUt+nkkW8/K8dKdilA==", - "dependencies": { - "@docusaurus/mdx-loader": "3.2.1", - "@docusaurus/module-type-aliases": "3.2.1", - "@docusaurus/plugin-content-blog": "3.2.1", - "@docusaurus/plugin-content-docs": "3.2.1", - "@docusaurus/plugin-content-pages": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.6.3.tgz", + "integrity": "sha512-b8ZkhczXHDxWWyvz+YJy4t/PlPbEogTTbgnHoflYnH7rmRtyoodTsu8WVM12la5LmlMJBclBXFl29OH8kPE7gg==", + "dependencies": { + "@docusaurus/mdx-loader": "3.6.3", + "@docusaurus/module-type-aliases": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -2585,23 +3615,24 @@ "node": ">=18.0" }, "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", "react": "^18.0.0", "react-dom": "^18.0.0" } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.2.1.tgz", - "integrity": "sha512-bzhCrpyXBXzeydNUH83II2akvFEGfhsNTPPWsk5N7e+odgQCQwoHhcF+2qILbQXjaoZ6B3c48hrvkyCpeyqGHw==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.6.3.tgz", + "integrity": "sha512-rt+MGCCpYgPyWCGXtbxlwFbTSobu15jWBTPI2LHsHNa5B0zSmOISX6FWYAPt5X1rNDOqMGM0FATnh7TBHRohVA==", "dependencies": { "@docsearch/react": "^3.5.2", - "@docusaurus/core": "3.2.1", - "@docusaurus/logger": "3.2.1", - "@docusaurus/plugin-content-docs": "3.2.1", - "@docusaurus/theme-common": "3.2.1", - "@docusaurus/theme-translations": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-validation": "3.2.1", + "@docusaurus/core": "3.6.3", + "@docusaurus/logger": "3.6.3", + "@docusaurus/plugin-content-docs": "3.6.3", + "@docusaurus/theme-common": "3.6.3", + "@docusaurus/theme-translations": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-validation": "3.6.3", "algoliasearch": "^4.18.0", "algoliasearch-helper": "^3.13.3", "clsx": "^2.0.0", @@ -2620,9 +3651,9 @@ } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.2.1.tgz", - "integrity": "sha512-jAUMkIkFfY+OAhJhv6mV8zlwY6J4AQxJPTgLdR2l+Otof9+QdJjHNh/ifVEu9q0lp3oSPlJj9l05AaP7Ref+cg==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.6.3.tgz", + "integrity": "sha512-Gb0regclToVlngSIIwUCtBMQBq48qVUaN1XQNKW4XwlsgUyk0vP01LULdqbem7czSwIeBAFXFoORJ0RPX7ht/w==", "dependencies": { "fs-extra": "^11.1.1", "tslib": "^2.6.0" @@ -2632,9 +3663,9 @@ } }, "node_modules/@docusaurus/types": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.2.1.tgz", - "integrity": "sha512-n/toxBzL2oxTtRTOFiGKsHypzn/Pm+sXyw+VSk1UbqbXQiHOwHwts55bpKwbcUgA530Is6kix3ELiFOv9GAMfw==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.6.3.tgz", + "integrity": "sha512-xD9oTGDrouWzefkhe9ogB2fDV96/82cRpNGx2HIvI5L87JHNhQVIWimQ/3JIiiX/TEd5S9s+VO6FFguwKNRVow==", "dependencies": { "@mdx-js/mdx": "^3.0.0", "@types/history": "^4.7.11", @@ -2643,7 +3674,7 @@ "joi": "^17.9.2", "react-helmet-async": "^1.3.0", "utility-types": "^3.10.0", - "webpack": "^5.88.1", + "webpack": "^5.95.0", "webpack-merge": "^5.9.0" }, "peerDependencies": { @@ -2651,14 +3682,28 @@ "react-dom": "^18.0.0" } }, - "node_modules/@docusaurus/utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.2.1.tgz", - "integrity": "sha512-DPkIS/EPc+pGAV798PUXgNzJFM3HJouoQXgr0KDZuJVz1EkWbDLOcQwLIz8Qx7liI9ddfkN/TXTRQdsTPZNakw==", + "node_modules/@docusaurus/types/node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", "dependencies": { - "@docusaurus/logger": "3.2.1", - "@docusaurus/utils-common": "3.2.1", - "@svgr/webpack": "^6.5.1", + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docusaurus/utils": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.6.3.tgz", + "integrity": "sha512-0R/FR3bKVl4yl8QwbL4TYFfR+OXBRpVUaTJdENapBGR3YMwfM6/JnhGilWQO8AOwPJGtGoDK7ib8+8UF9f3OZQ==", + "dependencies": { + "@docusaurus/logger": "3.6.3", + "@docusaurus/types": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "@svgr/webpack": "^8.1.0", "escape-string-regexp": "^4.0.0", "file-loader": "^6.2.0", "fs-extra": "^11.1.1", @@ -2674,49 +3719,37 @@ "shelljs": "^0.8.5", "tslib": "^2.6.0", "url-loader": "^4.1.1", + "utility-types": "^3.10.0", "webpack": "^5.88.1" }, "engines": { "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } } }, "node_modules/@docusaurus/utils-common": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.2.1.tgz", - "integrity": "sha512-N5vadULnRLiqX2QfTjVEU3u5vo6RG2EZTdyXvJdzDOdrLCGIZAfnf/VkssinFZ922sVfaFfQ4FnStdhn5TWdVg==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.6.3.tgz", + "integrity": "sha512-v4nKDaANLgT3pMBewHYEMAl/ufY0LkXao1QkFWzI5huWFOmNQ2UFzv2BiKeHX5Ownis0/w6cAyoxPhVdDonlSQ==", "dependencies": { + "@docusaurus/types": "3.6.3", "tslib": "^2.6.0" }, "engines": { "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/types": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/types": { - "optional": true - } } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.2.1.tgz", - "integrity": "sha512-+x7IR9hNMXi62L1YAglwd0s95fR7+EtirjTxSN4kahYRWGqOi3jlQl1EV0az/yTEvKbxVvOPcdYicGu9dk4LJw==", - "dependencies": { - "@docusaurus/logger": "3.2.1", - "@docusaurus/utils": "3.2.1", - "@docusaurus/utils-common": "3.2.1", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.6.3.tgz", + "integrity": "sha512-bhEGGiN5BE38h21vjqD70Gxg++j+PfYVddDUE5UFvLDup68QOcpD33CLr+2knPorlxRbEaNfz6HQDUMQ3HuqKw==", + "dependencies": { + "@docusaurus/logger": "3.6.3", + "@docusaurus/utils": "3.6.3", + "@docusaurus/utils-common": "3.6.3", + "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", + "lodash": "^4.17.21", "tslib": "^2.6.0" }, "engines": { @@ -2821,9 +3854,9 @@ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" }, "node_modules/@mdx-js/mdx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz", - "integrity": "sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", + "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", @@ -2831,14 +3864,15 @@ "@types/mdx": "^2.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", - "estree-util-to-js": "^2.0.0", + "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", - "hast-util-to-estree": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", - "periscopic": "^3.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", @@ -2855,9 +3889,9 @@ } }, "node_modules/@mdx-js/react": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz", - "integrity": "sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", + "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==", "dependencies": { "@types/mdx": "^2.0.0" }, @@ -2940,9 +3974,9 @@ } }, "node_modules/@polka/url": { - "version": "1.0.0-next.25", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", - "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==" + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==" }, "node_modules/@sideway/address": { "version": "4.1.5", @@ -2989,11 +4023,11 @@ } }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz", - "integrity": "sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3034,11 +4068,11 @@ } }, "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz", - "integrity": "sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3049,11 +4083,11 @@ } }, "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz", - "integrity": "sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3064,11 +4098,11 @@ } }, "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz", - "integrity": "sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3079,11 +4113,11 @@ } }, "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz", - "integrity": "sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3094,9 +4128,9 @@ } }, "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz", - "integrity": "sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", "engines": { "node": ">=12" }, @@ -3109,21 +4143,21 @@ } }, "node_modules/@svgr/babel-preset": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-6.5.1.tgz", - "integrity": "sha512-6127fvO/FF2oi5EzSQOAjo1LE3OtNVh11R+/8FXa+mHx1ptAaS4cknIjnUA7e6j6fwGGJ17NzaTJFUwOV2zwCw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "^6.5.1", - "@svgr/babel-plugin-remove-jsx-attribute": "*", - "@svgr/babel-plugin-remove-jsx-empty-expression": "*", - "@svgr/babel-plugin-replace-jsx-attribute-value": "^6.5.1", - "@svgr/babel-plugin-svg-dynamic-title": "^6.5.1", - "@svgr/babel-plugin-svg-em-dimensions": "^6.5.1", - "@svgr/babel-plugin-transform-react-native-svg": "^6.5.1", - "@svgr/babel-plugin-transform-svg-component": "^6.5.1" + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3134,18 +4168,18 @@ } }, "node_modules/@svgr/core": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-6.5.1.tgz", - "integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dependencies": { - "@babel/core": "^7.19.6", - "@svgr/babel-preset": "^6.5.1", - "@svgr/plugin-jsx": "^6.5.1", + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", "camelcase": "^6.2.0", - "cosmiconfig": "^7.0.1" + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3153,15 +4187,15 @@ } }, "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz", - "integrity": "sha512-1hnUxxjd83EAxbL4a0JDJoD3Dao3hmjvyvyEV8PzWmLK3B9m9NPlW7GKjFyoWE8nM7HnXzPcmmSyOW8yOddSXw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", "dependencies": { - "@babel/types": "^7.20.0", + "@babel/types": "^7.21.3", "entities": "^4.4.0" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3169,37 +4203,37 @@ } }, "node_modules/@svgr/plugin-jsx": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz", - "integrity": "sha512-+UdQxI3jgtSjCykNSlEMuy1jSRQlGC7pqBCPvkG/2dATdWo082zHTTK3uhnAju2/6XpE6B5mZ3z4Z8Ns01S8Gw==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", "dependencies": { - "@babel/core": "^7.19.6", - "@svgr/babel-preset": "^6.5.1", - "@svgr/hast-util-to-babel-ast": "^6.5.1", + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", "svg-parser": "^2.0.4" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", "url": "https://github.com/sponsors/gregberge" }, "peerDependencies": { - "@svgr/core": "^6.0.0" + "@svgr/core": "*" } }, "node_modules/@svgr/plugin-svgo": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz", - "integrity": "sha512-omvZKf8ixP9z6GWgwbtmP9qQMPX4ODXi+wzbVZgomNFsUIlHA1sf4fThdwTWSsZGgvGAG6yE+b/F5gWUkcZ/iQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", "dependencies": { - "cosmiconfig": "^7.0.1", - "deepmerge": "^4.2.2", - "svgo": "^2.8.0" + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3210,21 +4244,21 @@ } }, "node_modules/@svgr/webpack": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-6.5.1.tgz", - "integrity": "sha512-cQ/AsnBkXPkEK8cLbv4Dm7JGXq2XrumKnL1dRpJD9rIO2fTIlJI9a1uCciYG1F2aUsox/hJQyNGbt3soDxSRkA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", "dependencies": { - "@babel/core": "^7.19.6", - "@babel/plugin-transform-react-constant-elements": "^7.18.12", - "@babel/preset-env": "^7.19.4", + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.18.6", - "@svgr/core": "^6.5.1", - "@svgr/plugin-jsx": "^6.5.1", - "@svgr/plugin-svgo": "^6.5.1" + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" }, "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { "type": "github", @@ -3301,9 +4335,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.56.7", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz", - "integrity": "sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -3319,9 +4353,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", @@ -3421,9 +4455,9 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/mdast": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", - "integrity": "sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "dependencies": { "@types/unist": "*" } @@ -3465,9 +4499,9 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prismjs": { - "version": "1.26.3", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.3.tgz", - "integrity": "sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==" + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==" }, "node_modules/@types/prop-types": { "version": "15.7.12", @@ -3571,9 +4605,9 @@ } }, "node_modules/@types/unist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", - "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, "node_modules/@types/ws": { "version": "8.5.10", @@ -3584,9 +4618,9 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dependencies": { "@types/yargs-parser": "*" } @@ -3602,133 +4636,133 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -3774,9 +4808,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "bin": { "acorn": "bin/acorn" }, @@ -3784,14 +4818,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -3801,9 +4827,12 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dependencies": { + "acorn": "^8.11.0" + }, "engines": { "node": ">=0.4.0" } @@ -3871,31 +4900,31 @@ } }, "node_modules/algoliasearch": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.2.tgz", - "integrity": "sha512-8aCl055IsokLuPU8BzLjwzXjb7ty9TPcUFFOk0pYOwsE5DMVhE3kwCMFtsCFKcnoPZK7oObm+H5mbnSO/9ioxQ==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.23.2", - "@algolia/cache-common": "4.23.2", - "@algolia/cache-in-memory": "4.23.2", - "@algolia/client-account": "4.23.2", - "@algolia/client-analytics": "4.23.2", - "@algolia/client-common": "4.23.2", - "@algolia/client-personalization": "4.23.2", - "@algolia/client-search": "4.23.2", - "@algolia/logger-common": "4.23.2", - "@algolia/logger-console": "4.23.2", - "@algolia/recommend": "4.23.2", - "@algolia/requester-browser-xhr": "4.23.2", - "@algolia/requester-common": "4.23.2", - "@algolia/requester-node-http": "4.23.2", - "@algolia/transporter": "4.23.2" + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", + "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.24.0", + "@algolia/cache-common": "4.24.0", + "@algolia/cache-in-memory": "4.24.0", + "@algolia/client-account": "4.24.0", + "@algolia/client-analytics": "4.24.0", + "@algolia/client-common": "4.24.0", + "@algolia/client-personalization": "4.24.0", + "@algolia/client-search": "4.24.0", + "@algolia/logger-common": "4.24.0", + "@algolia/logger-console": "4.24.0", + "@algolia/recommend": "4.24.0", + "@algolia/requester-browser-xhr": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/requester-node-http": "4.24.0", + "@algolia/transporter": "4.24.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.17.0.tgz", - "integrity": "sha512-R5422OiQjvjlK3VdpNQ/Qk7KsTIGeM5ACm8civGifOVWdRRV/3SgXuKmeNxe94Dz6fwj/IgpVmXbHutU4mHubg==", + "version": "3.22.5", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.5.tgz", + "integrity": "sha512-lWvhdnc+aKOKx8jyA3bsdEgHzm/sglC4cYdMG4xSQyRiPLJVJtH/IVYZG3Hp6PkTEhQqhyVYkeP9z2IlcHJsWw==", "dependencies": { "@algolia/events": "^4.0.1" }, @@ -3903,6 +4932,41 @@ "algoliasearch": ">= 3.1 < 6" } }, + "node_modules/algoliasearch/node_modules/@algolia/client-common": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", + "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "dependencies": { + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/client-search": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", + "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", + "dependencies": { + "@algolia/client-common": "4.24.0", + "@algolia/requester-common": "4.24.0", + "@algolia/transporter": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", + "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, + "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", + "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", + "dependencies": { + "@algolia/requester-common": "4.24.0" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -3929,6 +4993,31 @@ "node": ">=8" } }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -3998,9 +5087,9 @@ } }, "node_modules/astring": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz", - "integrity": "sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", "bin": { "astring": "bin/astring" } @@ -4014,9 +5103,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "funding": [ { "type": "opencollective", @@ -4032,11 +5121,11 @@ } ], "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -4050,9 +5139,9 @@ } }, "node_modules/babel-loader": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", - "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" @@ -4074,12 +5163,12 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", - "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", + "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.1", + "@babel/helper-define-polyfill-provider": "^0.6.3", "semver": "^6.3.1" }, "peerDependencies": { @@ -4095,23 +5184,23 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", - "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1" + "@babel/helper-define-polyfill-provider": "^0.6.3" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -4156,9 +5245,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -4168,7 +5257,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -4244,20 +5333,20 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "funding": [ { "type": "opencollective", @@ -4273,10 +5362,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -4392,9 +5481,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001608", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001608.tgz", - "integrity": "sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==", + "version": "1.0.30001686", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001686.tgz", + "integrity": "sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==", "funding": [ { "type": "opencollective", @@ -4642,21 +5731,10 @@ "node": ">=6" } }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "engines": { "node": ">=6" } @@ -4821,9 +5899,12 @@ } }, "node_modules/consola": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } }, "node_modules/content-disposition": { "version": "0.5.2", @@ -4847,9 +5928,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -4944,11 +6025,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", - "integrity": "sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", + "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", "dependencies": { - "browserslist": "^4.23.0" + "browserslist": "^4.24.2" }, "funding": { "type": "opencollective", @@ -4956,9 +6037,9 @@ } }, "node_modules/core-js-pure": { - "version": "3.36.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.1.tgz", - "integrity": "sha512-NXCvHvSVYSrewP0L5OhltzXeWFJLo2AL2TYnj6iLV3Bw8mM62wAQMNgUCRI6EBu6hVVpbCxmOPlxh1Ikw2PfUA==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.39.0.tgz", + "integrity": "sha512-7fEcWwKI4rJinnK+wLTezeg2smbFFdSBP6E2kQZNbnzM2s1rpKQ6aaRteZSSg7FLU3P0HGGVo/gbpfanU36urg==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -4971,67 +6052,172 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" }, "engines": { - "node": ">= 8" + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/css-blank-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", - "dependencies": { - "type-fest": "^1.0.1" - }, + "node_modules/css-declaration-sorter": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", "engines": { - "node": ">=12" + "node": "^14 || ^16 || >=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.0.9" } }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "node_modules/css-has-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.1.tgz", + "integrity": "sha512-EOcoyJt+OsuKfCADgLT7gADZI5jMzIe/AeI6MeAYKiFBDmNmM7kk46DtSfMj5AohUJisqVzopBpnQTlvbyaBWg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/css-declaration-sorter": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", - "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.0.9" + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/css-loader": { @@ -5069,16 +6255,16 @@ } }, "node_modules/css-minimizer-webpack-plugin": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.2.2.tgz", - "integrity": "sha512-s3Of/4jKfw1Hj9CxEO1E5oXhQAxlayuHO2y/ML+C6I9sQ7FdzfEV6QgMLN3vI+qFsjJGIAFLKtQK7t8BOXAIyA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", "dependencies": { - "cssnano": "^5.1.8", - "jest-worker": "^29.1.2", - "postcss": "^8.4.17", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1" + "@jridgewell/trace-mapping": "^0.3.18", + "cssnano": "^6.0.1", + "jest-worker": "^29.4.3", + "postcss": "^8.4.24", + "schema-utils": "^4.0.1", + "serialize-javascript": "^6.0.1" }, "engines": { "node": ">= 14.15.0" @@ -5111,12 +6297,25 @@ } } }, - "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/css-prefers-color-scheme": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, "node_modules/css-select": { @@ -5135,23 +6334,15 @@ } }, "node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, "node_modules/css-what": { @@ -5165,6 +6356,21 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cssdb": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.2.2.tgz", + "integrity": "sha512-Z3kpWyvN68aKyeMxOUGmffQeHjvrzDxbre2B2ikr/WqQ4ZMkhHu2nOD6uwSeq3TpuOYU7ckvmJRAUIt6orkYUg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ] + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5177,108 +6383,128 @@ } }, "node_modules/cssnano": { - "version": "5.1.15", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", - "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", "dependencies": { - "cssnano-preset-default": "^5.2.14", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/cssnano" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/cssnano-preset-advanced": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.10.tgz", - "integrity": "sha512-fnYJyCS9jgMU+cmHO1rPSPf9axbQyD7iUhLO5Df6O4G+fKIOMps+ZbU0PdGFejFBBZ3Pftf18fn1eG7MAPUSWQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", + "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", "dependencies": { - "autoprefixer": "^10.4.12", - "cssnano-preset-default": "^5.2.14", - "postcss-discard-unused": "^5.1.0", - "postcss-merge-idents": "^5.1.1", - "postcss-reduce-idents": "^5.2.0", - "postcss-zindex": "^5.1.0" + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.0", + "cssnano-preset-default": "^6.1.2", + "postcss-discard-unused": "^6.0.5", + "postcss-merge-idents": "^6.0.3", + "postcss-reduce-idents": "^6.0.3", + "postcss-zindex": "^6.0.2" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/cssnano-preset-default": { - "version": "5.2.14", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", - "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", - "dependencies": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.1", - "postcss-convert-values": "^5.1.3", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.7", - "postcss-merge-rules": "^5.1.4", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.4", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.1", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.2", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, "node_modules/cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "dependencies": { - "css-tree": "^1.1.2" + "css-tree": "~2.2.0" }, "engines": { - "node": ">=8.0.0" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -5650,9 +6876,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.732", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.732.tgz", - "integrity": "sha512-nLrzr6UML+oqCyvseTxJ1WxLsnMSQPJCoHu+MuOuNiCp7BHEdG9AmCTw2Y9FU/wFFc/ETE0F6JVtzzPryEjecw==" + "version": "1.5.71", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz", + "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -5673,26 +6899,26 @@ } }, "node_modules/emoticon": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.0.1.tgz", - "integrity": "sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", + "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } }, "node_modules/enhanced-resolve": { - "version": "5.16.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz", - "integrity": "sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -5744,10 +6970,40 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz", "integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==" }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "engines": { "node": ">=6" } @@ -5866,6 +7122,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-util-to-js": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", @@ -5881,12 +7150,11 @@ } }, "node_modules/estree-util-value-to-estree": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.1.tgz", - "integrity": "sha512-5mvUrF2suuv5f5cGDnDphIy4/gW86z82kl5qG6mM9z04SEQI4FB5Apmaw/TGEf3l55nLtMs5s51dmhUzvAHQCA==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.2.1.tgz", + "integrity": "sha512-Vt2UOjyPbNQQgT5eJh+K5aATti0OjCIAGc9SgMdOFYbohuifsWclR74l0iZTJwePMgWYdX1hlVS+dedH9XV8kw==", "dependencies": { - "@types/estree": "^1.0.0", - "is-plain-obj": "^4.0.0" + "@types/estree": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/remcohaszing" @@ -5988,36 +7256,37 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6026,6 +7295,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/content-disposition": { @@ -6053,9 +7326,10 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" }, "node_modules/express/node_modules/range-parser": { "version": "1.2.1", @@ -6106,14 +7380,6 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, - "node_modules/fast-url-parser": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", - "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", - "dependencies": { - "punycode": "^1.3.2" - } - }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -6156,6 +7422,28 @@ "node": ">=0.4.0" } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", @@ -6229,9 +7517,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -6240,12 +7528,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -6868,14 +8156,14 @@ } }, "node_modules/hast-util-from-parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", - "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz", + "integrity": "sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A==", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", - "hastscript": "^8.0.0", + "hastscript": "^9.0.0", "property-information": "^6.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", @@ -6899,9 +8187,9 @@ } }, "node_modules/hast-util-raw": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.2.tgz", - "integrity": "sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", @@ -6949,10 +8237,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-estree/node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "node_modules/hast-util-to-estree/node_modules/style-to-object": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", - "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", @@ -6975,19 +8276,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-to-jsx-runtime/node_modules/inline-style-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", - "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==" - }, - "node_modules/hast-util-to-jsx-runtime/node_modules/style-to-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.6.tgz", - "integrity": "sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==", - "dependencies": { - "inline-style-parser": "0.2.3" - } - }, "node_modules/hast-util-to-parse5": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", @@ -7019,9 +8307,9 @@ } }, "node_modules/hastscript": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", - "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.0.tgz", + "integrity": "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==", "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", @@ -7295,9 +8583,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -7441,9 +8729,9 @@ } }, "node_modules/infima": { - "version": "0.2.0-alpha.43", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.43.tgz", - "integrity": "sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==", + "version": "0.2.0-alpha.45", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", + "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", "engines": { "node": ">=12" } @@ -7468,9 +8756,9 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/inline-style-parser": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", - "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==" }, "node_modules/interpret": { "version": "1.4.0", @@ -7693,21 +8981,16 @@ } }, "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", - "dependencies": { - "@types/estree": "*" - } - }, "node_modules/is-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", @@ -7822,17 +9105,17 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "bin": { "jiti": "bin/jiti.js" } }, "node_modules/joi": { - "version": "17.12.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.3.tgz", - "integrity": "sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g==", + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", @@ -7858,14 +9141,14 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -7961,11 +9244,14 @@ } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -8087,9 +9373,9 @@ } }, "node_modules/markdown-table": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", - "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8141,9 +9427,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz", - "integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", @@ -8164,9 +9450,9 @@ } }, "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8225,9 +9511,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", @@ -8241,9 +9527,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8260,9 +9546,9 @@ } }, "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8352,9 +9638,9 @@ } }, "node_modules/mdast-util-mdx-expression": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", - "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", @@ -8369,9 +9655,9 @@ } }, "node_modules/mdast-util-mdx-jsx": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.2.tgz", - "integrity": "sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", @@ -8383,7 +9669,6 @@ "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", - "unist-util-remove-position": "^5.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" }, @@ -8423,9 +9708,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz", - "integrity": "sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", @@ -8443,15 +9728,16 @@ } }, "node_modules/mdast-util-to-markdown": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", - "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" @@ -8474,9 +9760,9 @@ } }, "node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" }, "node_modules/media-typer": { "version": "0.3.0", @@ -8498,9 +9784,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -8524,9 +9813,9 @@ } }, "node_modules/micromark": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", - "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", "funding": [ { "type": "GitHub Sponsors", @@ -8558,9 +9847,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz", - "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", "funding": [ { "type": "GitHub Sponsors", @@ -8591,9 +9880,9 @@ } }, "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -8610,9 +9899,9 @@ } }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8629,9 +9918,9 @@ } }, "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8644,9 +9933,9 @@ ] }, "node_modules/micromark-extension-directive": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.0.tgz", - "integrity": "sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -8662,9 +9951,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -8681,9 +9970,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8700,9 +9989,9 @@ } }, "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8730,9 +10019,9 @@ } }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8749,9 +10038,9 @@ } }, "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8783,9 +10072,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", @@ -8798,9 +10087,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8817,9 +10106,9 @@ } }, "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8832,9 +10121,9 @@ ] }, "node_modules/micromark-extension-gfm-footnote": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz", - "integrity": "sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", @@ -8851,9 +10140,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -8870,9 +10159,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8889,9 +10178,9 @@ } }, "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8904,9 +10193,9 @@ ] }, "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", @@ -8921,9 +10210,9 @@ } }, "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8936,9 +10225,9 @@ ] }, "node_modules/micromark-extension-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz", - "integrity": "sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -8952,9 +10241,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -8971,9 +10260,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -8990,9 +10279,9 @@ } }, "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9017,9 +10306,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz", - "integrity": "sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", @@ -9033,9 +10322,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9052,9 +10341,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9071,9 +10360,9 @@ } }, "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9111,9 +10400,9 @@ } }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9130,9 +10419,9 @@ } }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9149,9 +10438,9 @@ } }, "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9164,9 +10453,9 @@ ] }, "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.0.tgz", - "integrity": "sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.1.tgz", + "integrity": "sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==", "dependencies": { "@types/acorn": "^4.0.0", "@types/estree": "^1.0.0", @@ -9175,6 +10464,7 @@ "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" @@ -9185,9 +10475,9 @@ } }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9204,9 +10494,9 @@ } }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9223,9 +10513,9 @@ } }, "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9289,9 +10579,9 @@ } }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9308,9 +10598,9 @@ } }, "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9323,9 +10613,9 @@ ] }, "node_modules/micromark-factory-destination": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", - "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "funding": [ { "type": "GitHub Sponsors", @@ -9343,9 +10633,9 @@ } }, "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9362,9 +10652,9 @@ } }, "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9377,9 +10667,9 @@ ] }, "node_modules/micromark-factory-label": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", - "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "funding": [ { "type": "GitHub Sponsors", @@ -9398,9 +10688,9 @@ } }, "node_modules/micromark-factory-label/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9417,9 +10707,9 @@ } }, "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9432,9 +10722,9 @@ ] }, "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.1.tgz", - "integrity": "sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.2.tgz", + "integrity": "sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==", "funding": [ { "type": "GitHub Sponsors", @@ -9448,6 +10738,7 @@ "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", @@ -9456,10 +10747,29 @@ "vfile-message": "^4.0.0" } }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9476,9 +10786,9 @@ } }, "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9525,9 +10835,9 @@ ] }, "node_modules/micromark-factory-title": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", - "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "funding": [ { "type": "GitHub Sponsors", @@ -9546,9 +10856,9 @@ } }, "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9565,9 +10875,9 @@ } }, "node_modules/micromark-factory-title/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9584,9 +10894,9 @@ } }, "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9599,9 +10909,9 @@ ] }, "node_modules/micromark-factory-whitespace": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", - "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "funding": [ { "type": "GitHub Sponsors", @@ -9620,9 +10930,9 @@ } }, "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -9639,9 +10949,9 @@ } }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9658,9 +10968,9 @@ } }, "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9707,9 +11017,9 @@ ] }, "node_modules/micromark-util-chunked": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", - "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "funding": [ { "type": "GitHub Sponsors", @@ -9725,9 +11035,9 @@ } }, "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9740,9 +11050,9 @@ ] }, "node_modules/micromark-util-classify-character": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", - "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9760,9 +11070,9 @@ } }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9779,9 +11089,9 @@ } }, "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9794,9 +11104,9 @@ ] }, "node_modules/micromark-util-combine-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", - "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "funding": [ { "type": "GitHub Sponsors", @@ -9813,9 +11123,9 @@ } }, "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", - "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "funding": [ { "type": "GitHub Sponsors", @@ -9831,9 +11141,9 @@ } }, "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9846,9 +11156,9 @@ ] }, "node_modules/micromark-util-decode-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", - "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "funding": [ { "type": "GitHub Sponsors", @@ -9867,9 +11177,9 @@ } }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9886,9 +11196,9 @@ } }, "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9901,9 +11211,9 @@ ] }, "node_modules/micromark-util-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", - "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { "type": "GitHub Sponsors", @@ -9941,9 +11251,9 @@ } }, "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9956,9 +11266,9 @@ ] }, "node_modules/micromark-util-html-tag-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", - "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", "funding": [ { "type": "GitHub Sponsors", @@ -9971,9 +11281,9 @@ ] }, "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", - "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", "funding": [ { "type": "GitHub Sponsors", @@ -9989,9 +11299,9 @@ } }, "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10004,9 +11314,9 @@ ] }, "node_modules/micromark-util-resolve-all": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", - "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "funding": [ { "type": "GitHub Sponsors", @@ -10022,9 +11332,9 @@ } }, "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", - "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", "funding": [ { "type": "GitHub Sponsors", @@ -10042,9 +11352,9 @@ } }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10061,9 +11371,9 @@ } }, "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10076,9 +11386,9 @@ ] }, "node_modules/micromark-util-subtokenize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz", - "integrity": "sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", + "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", "funding": [ { "type": "GitHub Sponsors", @@ -10097,9 +11407,9 @@ } }, "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10127,9 +11437,9 @@ ] }, "node_modules/micromark-util-types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", - "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", "funding": [ { "type": "GitHub Sponsors", @@ -10142,9 +11452,9 @@ ] }, "node_modules/micromark/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "funding": [ { "type": "GitHub Sponsors", @@ -10161,9 +11471,9 @@ } }, "node_modules/micromark/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10180,9 +11490,9 @@ } }, "node_modules/micromark/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -10195,11 +11505,11 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -10256,9 +11566,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz", - "integrity": "sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", "dependencies": { "schema-utils": "^4.0.0", "tapable": "^2.2.1" @@ -10324,9 +11634,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", @@ -10363,9 +11673,9 @@ } }, "node_modules/node-emoji": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", - "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", @@ -10385,9 +11695,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -10405,17 +11715,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -10443,6 +11742,70 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/null-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", + "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/null-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/null-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/null-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/null-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10452,9 +11815,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10681,9 +12047,9 @@ } }, "node_modules/parse-entities/node_modules/@types/unist": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", - "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" }, "node_modules/parse-json": { "version": "5.2.0", @@ -10708,22 +12074,22 @@ "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" }, "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", "dependencies": { - "entities": "^4.4.0" + "entities": "^4.5.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", - "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", "dependencies": { - "domhandler": "^5.0.2", + "domhandler": "^5.0.3", "parse5": "^7.0.0" }, "funding": { @@ -10782,9 +12148,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "dependencies": { "isarray": "0.0.1" } @@ -10797,240 +12163,671 @@ "node": ">=8" } }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "engines": { - "node": ">=8.6" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", + "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.6.tgz", + "integrity": "sha512-wLXvm8RmLs14Z2nVpB4CWlnvaWPRcOZFltJSlcbYwSJ1EDZKsKDhPKIMecCnuU054KSmlmubkqczmm6qBPCBhA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", + "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", + "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-custom-media": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.5.tgz", + "integrity": "sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-properties": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.4.tgz", + "integrity": "sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "node_modules/postcss-custom-selectors": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.4.tgz", + "integrity": "sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "find-up": "^6.3.0" + "@csstools/cascade-layer-name-parser": "^2.0.4", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": ">=14.16" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dependencies": { - "find-up": "^3.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "node_modules/postcss-dir-pseudo-class": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", + "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "locate-path": "^3.0.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, - "node_modules/pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "engines": { + "node": "^14 || ^16 || >=18.0" }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", "engines": { - "node": ">=6" + "node": "^14 || ^16 || >=18.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "engines": { + "node": "^14 || ^16 || >=18.0" }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", "engines": { - "node": ">=6" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "node_modules/postcss-discard-unused": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", + "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, "engines": { - "node": ">=4" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" } }, - "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "node_modules/postcss-double-position-gradients": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.0.tgz", + "integrity": "sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg==", "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "type": "github", + "url": "https://github.com/sponsors/csstools" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "opencollective", + "url": "https://opencollective.com/csstools" } ], "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", - "dependencies": { - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2.2" + "postcss": "^8.4" } }, - "node_modules/postcss-colormin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", - "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "node_modules/postcss-focus-visible": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", + "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.2.0" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4" } }, - "node_modules/postcss-convert-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", - "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dependencies": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" + "node": ">=4" } }, - "node_modules/postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "node_modules/postcss-focus-within": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", + "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4" } }, - "node_modules/postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "engines": { - "node": "^10 || ^12 || >=14.0" + "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.1.0" } }, - "node_modules/postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "node_modules/postcss-gap-properties": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", + "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": "^10 || ^12 || >=14.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4" } }, - "node_modules/postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "node_modules/postcss-image-set-function": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", + "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4" } }, - "node_modules/postcss-discard-unused": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-5.1.0.tgz", - "integrity": "sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw==", + "node_modules/postcss-lab-function": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.6.tgz", + "integrity": "sha512-HPwvsoK7C949vBZ+eMyvH2cQeMr3UREoHvbtra76/UhDuiViZH6pir+z71UaJQohd7VDSVUdR6TkWYKExEc9aQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "postcss-selector-parser": "^6.0.5" + "@csstools/css-color-parser": "^3.0.6", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/utilities": "^2.0.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4" } }, "node_modules/postcss-loader": { @@ -11054,136 +12851,135 @@ "webpack": "^5.0.0" } }, - "node_modules/postcss-loader/node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "node_modules/postcss-logical": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.0.0.tgz", + "integrity": "sha512-HpIdsdieClTjXLOyYdUPAX/XQASNIwdKt5hoZW08ZOAiI+tbV0ta1oclkpVkW5ANU+xJvk3KkA0FejkjGLXUkg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" + "postcss-value-parser": "^4.2.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" } }, "node_modules/postcss-merge-idents": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-5.1.1.tgz", - "integrity": "sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", + "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", "dependencies": { - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-merge-longhand": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", - "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.1" + "stylehacks": "^6.1.1" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-merge-rules": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", - "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.23.0", "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", "dependencies": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-params": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", - "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", "dependencies": { - "browserslist": "^4.21.4", - "cssnano-utils": "^3.1.0", + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", "dependencies": { - "postcss-selector-parser": "^6.0.5" + "postcss-selector-parser": "^6.0.16" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-modules-extract-imports": { @@ -11198,12 +12994,12 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.1.0.tgz", + "integrity": "sha512-rm0bdSv4jC3BDma3s9H19ZddW0aHX6EoqwDYU2IfZhRN+53QrufTRo2IdkAbRqLx4R2IYbZnbjKKxg4VN5oU9Q==", "dependencies": { "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "engines": { @@ -11213,12 +13009,24 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dependencies": { - "postcss-selector-parser": "^6.0.4" + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^10 || ^12 || >= 14" @@ -11227,6 +13035,18 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-modules-values": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", @@ -11241,193 +13061,515 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-nesting": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", + "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/selector-resolve-nested": "^3.0.0", + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz", + "integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-unicode": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", - "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.23.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", "dependencies": { - "normalize-url": "^6.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", + "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "engines": { + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4" } }, "node_modules/postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", + "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", + "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "dependencies": { - "cssnano-utils": "^3.1.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-preset-env": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.1.1.tgz", + "integrity": "sha512-wqqsnBFD6VIwcHHRbhjTOcOi4qRVlB26RwSr0ordPj7OubRRxdWebv/aLjKLRR8zkZrbxZyuus03nOIgC5elMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/postcss-cascade-layers": "^5.0.1", + "@csstools/postcss-color-function": "^4.0.6", + "@csstools/postcss-color-mix-function": "^3.0.6", + "@csstools/postcss-content-alt-text": "^2.0.4", + "@csstools/postcss-exponential-functions": "^2.0.5", + "@csstools/postcss-font-format-keywords": "^4.0.0", + "@csstools/postcss-gamut-mapping": "^2.0.6", + "@csstools/postcss-gradients-interpolation-method": "^5.0.6", + "@csstools/postcss-hwb-function": "^4.0.6", + "@csstools/postcss-ic-unit": "^4.0.0", + "@csstools/postcss-initial": "^2.0.0", + "@csstools/postcss-is-pseudo-class": "^5.0.1", + "@csstools/postcss-light-dark-function": "^2.0.7", + "@csstools/postcss-logical-float-and-clear": "^3.0.0", + "@csstools/postcss-logical-overflow": "^2.0.0", + "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", + "@csstools/postcss-logical-resize": "^3.0.0", + "@csstools/postcss-logical-viewport-units": "^3.0.3", + "@csstools/postcss-media-minmax": "^2.0.5", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.4", + "@csstools/postcss-nested-calc": "^4.0.0", + "@csstools/postcss-normalize-display-values": "^4.0.0", + "@csstools/postcss-oklab-function": "^4.0.6", + "@csstools/postcss-progressive-custom-properties": "^4.0.0", + "@csstools/postcss-random-function": "^1.0.1", + "@csstools/postcss-relative-color-syntax": "^3.0.6", + "@csstools/postcss-scope-pseudo-class": "^4.0.1", + "@csstools/postcss-sign-functions": "^1.1.0", + "@csstools/postcss-stepped-value-functions": "^4.0.5", + "@csstools/postcss-text-decoration-shorthand": "^4.0.1", + "@csstools/postcss-trigonometric-functions": "^4.0.5", + "@csstools/postcss-unset-value": "^4.0.0", + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.1", + "css-blank-pseudo": "^7.0.1", + "css-has-pseudo": "^7.0.1", + "css-prefers-color-scheme": "^10.0.0", + "cssdb": "^8.2.1", + "postcss-attribute-case-insensitive": "^7.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^7.0.6", + "postcss-color-hex-alpha": "^10.0.0", + "postcss-color-rebeccapurple": "^10.0.0", + "postcss-custom-media": "^11.0.5", + "postcss-custom-properties": "^14.0.4", + "postcss-custom-selectors": "^8.0.4", + "postcss-dir-pseudo-class": "^9.0.1", + "postcss-double-position-gradients": "^6.0.0", + "postcss-focus-visible": "^10.0.1", + "postcss-focus-within": "^9.0.1", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^6.0.0", + "postcss-image-set-function": "^7.0.0", + "postcss-lab-function": "^7.0.6", + "postcss-logical": "^8.0.0", + "postcss-nesting": "^13.0.1", + "postcss-opacity-percentage": "^3.0.0", + "postcss-overflow-shorthand": "^6.0.0", + "postcss-page-break": "^3.0.4", + "postcss-place": "^10.0.0", + "postcss-pseudo-class-any-link": "^10.0.1", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^8.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", + "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/postcss-reduce-idents": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-5.2.0.tgz", - "integrity": "sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", + "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-reduce-initial": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", - "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", "dependencies": { - "browserslist": "^4.21.4", + "browserslist": "^4.23.0", "caniuse-api": "^3.0.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", + "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -11437,46 +13579,46 @@ } }, "node_modules/postcss-sort-media-queries": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-4.4.1.tgz", - "integrity": "sha512-QDESFzDDGKgpiIh4GYXsSy6sek2yAwQx1JASl5AxBtU1Lq2JfKBljIPNdil989NcSKRQX1ToiaKphImtBuhXWw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", + "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", "dependencies": { - "sort-css-media-queries": "2.1.0" + "sort-css-media-queries": "2.2.0" }, "engines": { - "node": ">=10.0.0" + "node": ">=14.0.0" }, "peerDependencies": { - "postcss": "^8.4.16" + "postcss": "^8.4.23" } }, "node_modules/postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" + "svgo": "^3.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >= 18" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", "dependencies": { - "postcss-selector-parser": "^6.0.5" + "postcss-selector-parser": "^6.0.16" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/postcss-value-parser": { @@ -11485,14 +13627,14 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/postcss-zindex": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-5.1.0.tgz", - "integrity": "sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", + "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/pretty-error": { @@ -11513,9 +13655,9 @@ } }, "node_modules/prism-react-renderer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz", - "integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz", + "integrity": "sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==", "dependencies": { "@types/prismjs": "^1.26.0", "clsx": "^2.0.0" @@ -11593,11 +13735,6 @@ "node": ">= 0.10" } }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, "node_modules/pupa": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", @@ -11613,11 +13750,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -11725,9 +13862,9 @@ } }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dependencies": { "loose-envify": "^1.1.0" }, @@ -11854,15 +13991,15 @@ } }, "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-error-overlay": { @@ -11897,9 +14034,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-json-view-lite": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.3.0.tgz", - "integrity": "sha512-aN1biKC5v4DQkmQBlZjuMFR09MKZGMPtIg+cut8zEeg2HXd6gl2gRy0n4HMacHf0dznQgo0SVXN7eT8zV3hEuQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.5.0.tgz", + "integrity": "sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw==", "engines": { "node": ">=14" }, @@ -11909,12 +14046,11 @@ }, "node_modules/react-loadable": { "name": "@docusaurus/react-loadable", - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-5.5.2.tgz", - "integrity": "sha512-A3dYjdBGuy0IGT+wyLIGIKLRE+sAk1iNk0f1HjNDysO7u8lhL4N3VEm+FAubmJbAztn94F7MxBTPmnixbiyFdQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", + "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", "dependencies": { - "@types/react": "*", - "prop-types": "^15.6.2" + "@types/react": "*" }, "peerDependencies": { "react": "*" @@ -12023,6 +14159,66 @@ "node": ">= 0.10" } }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz", + "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/recursive-readdir": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", @@ -12040,9 +14236,9 @@ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "node_modules/regenerate-unicode-properties": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", - "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", "dependencies": { "regenerate": "^1.4.2" }, @@ -12064,14 +14260,14 @@ } }, "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "dependencies": { - "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -12104,25 +14300,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==" + }, "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "dependencies": { - "jsesc": "~0.5.0" + "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "bin": { - "jsesc": "bin/jsesc" - } - }, "node_modules/rehype-raw": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", @@ -12137,6 +14330,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -12208,9 +14415,9 @@ } }, "node_modules/remark-mdx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.1.tgz", - "integrity": "sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", + "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" @@ -12236,9 +14443,9 @@ } }, "node_modules/remark-rehype": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz", - "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", @@ -12358,6 +14565,14 @@ "entities": "^2.0.0" } }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -12464,9 +14679,9 @@ "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" }, "node_modules/rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", @@ -12527,14 +14742,14 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sax": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "dependencies": { "loose-envify": "^1.1.0" } @@ -12558,9 +14773,9 @@ } }, "node_modules/search-insights": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.13.0.tgz", - "integrity": "sha512-Orrsjf9trHHxFRuo9/rzm0KIWmgzE8RMlZMzuhZOJ01Rnz3D0YBAe+V6473t6/H6c7irs6Lt48brULAiRWb3Vw==", + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", "peer": true }, "node_modules/section-matter": { @@ -12637,9 +14852,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -12672,6 +14887,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -12694,24 +14917,23 @@ } }, "node_modules/serve-handler": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", - "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", - "fast-url-parser": "1.1.3", "mime-types": "2.1.18", "minimatch": "3.1.2", "path-is-inside": "1.0.2", - "path-to-regexp": "2.2.1", + "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", - "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==" }, "node_modules/serve-index": { "version": "1.9.1", @@ -12784,14 +15006,14 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -12918,9 +15140,9 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, "node_modules/sitemap": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.1.tgz", - "integrity": "sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", + "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", "dependencies": { "@types/node": "^17.0.5", "@types/sax": "^1.2.1", @@ -12959,6 +15181,15 @@ "node": ">=8" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -12970,9 +15201,9 @@ } }, "node_modules/sort-css-media-queries": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz", - "integrity": "sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", + "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", "engines": { "node": ">= 6.3.0" } @@ -12986,9 +15217,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "engines": { "node": ">=0.10.0" } @@ -13063,12 +15294,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -13078,9 +15303,9 @@ } }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==" }, "node_modules/string_decoder": { "version": "1.3.0", @@ -13196,26 +15421,26 @@ } }, "node_modules/style-to-object": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", - "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", "dependencies": { - "inline-style-parser": "0.1.1" + "inline-style-parser": "0.2.4" } }, "node_modules/stylehacks": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", - "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", "dependencies": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { - "postcss": "^8.2.15" + "postcss": "^8.4.31" } }, "node_modules/supports-color": { @@ -13246,23 +15471,27 @@ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" }, "node_modules/svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" }, "bin": { "svgo": "bin/svgo" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" } }, "node_modules/svgo/node_modules/commander": { @@ -13273,69 +15502,6 @@ "node": ">= 10" } }, - "node_modules/svgo/node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/svgo/node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/svgo/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/svgo/node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/svgo/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -13491,14 +15657,6 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -13618,9 +15776,9 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "engines": { "node": ">=4" } @@ -13646,9 +15804,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", "engines": { "node": ">=4" } @@ -13662,9 +15820,9 @@ } }, "node_modules/unified": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz", - "integrity": "sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", @@ -13729,19 +15887,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", - "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", @@ -13798,9 +15943,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -13816,8 +15961,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -14050,12 +16195,11 @@ } }, "node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "dependencies": { "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" }, "funding": { @@ -14064,9 +16208,9 @@ } }, "node_modules/vfile-location": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", - "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" @@ -14119,20 +16263,19 @@ } }, "node_modules/webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.21.10", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -14165,9 +16308,9 @@ } }, "node_modules/webpack-bundle-analyzer": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", - "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", "dependencies": { "@discoveryjs/json-ext": "0.5.7", "acorn": "^8.0.4", @@ -14177,7 +16320,6 @@ "escape-string-regexp": "^4.0.0", "gzip-size": "^6.0.0", "html-escaper": "^2.0.2", - "is-plain-object": "^5.0.0", "opener": "^1.5.2", "picocolors": "^1.0.0", "sirv": "^2.0.3", @@ -14306,9 +16448,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "engines": { "node": ">=10.0.0" }, @@ -14326,16 +16468,16 @@ } }, "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", - "wildcard": "^2.0.0" + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/webpack-sources": { @@ -14411,22 +16553,72 @@ } }, "node_modules/webpackbar": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.2.tgz", - "integrity": "sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", + "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", "dependencies": { - "chalk": "^4.1.0", - "consola": "^2.15.3", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "consola": "^3.2.3", + "figures": "^3.2.0", + "markdown-table": "^2.0.0", "pretty-time": "^1.1.0", - "std-env": "^3.0.1" + "std-env": "^3.7.0", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=14.21.3" }, "peerDependencies": { "webpack": "3 || 4 || 5" } }, + "node_modules/webpackbar/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/webpackbar/node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpackbar/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpackbar/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -14550,9 +16742,9 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "engines": { "node": ">=8.3.0" }, @@ -14605,9 +16797,9 @@ } }, "node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", "engines": { "node": ">=12.20" }, diff --git a/docs/package.json b/docs/package.json index fc9bb80f..45195f20 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,17 +14,18 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "3.2.1", - "@docusaurus/preset-classic": "3.2.1", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "prism-react-renderer": "^2.3.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "@docusaurus/core": "^3.6.3", + "@docusaurus/preset-classic": "^3.6.3", + "@docusaurus/theme-common": "^3.6.3", + "@mdx-js/react": "^3.1.0", + "clsx": "^2.1.1", + "prism-react-renderer": "^2.4.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" }, "devDependencies": { - "@docusaurus/module-type-aliases": "3.2.1", - "@docusaurus/types": "3.2.1" + "@docusaurus/module-type-aliases": "^3.6.3", + "@docusaurus/types": "^3.6.3" }, "browserslist": { "production": [ diff --git a/docs/src/components/ThemeImage/index.js b/docs/src/components/ThemeImage/index.js new file mode 100644 index 00000000..956d892a --- /dev/null +++ b/docs/src/components/ThemeImage/index.js @@ -0,0 +1,32 @@ +import React from 'react'; +import { useColorMode } from '@docusaurus/theme-common'; + +const scenarios = { + '1': { + dark: require('@site/static/img/scenarios/1-scenario-dark.svg').default, + light: require('@site/static/img/scenarios/1-scenario.svg').default + }, + '2': { + dark: require('@site/static/img/scenarios/2-scenario-dark.svg').default, + light: require('@site/static/img/scenarios/2-scenario.svg').default + }, +// '3': { +// dark: require('@site/static/img/scenarios/3-scenario-dark.svg').default, +// light: require('@site/static/img/scenarios/3-scenario.svg').default +// }, +} + +console.log(scenarios) + +const ThemeImage = ({ scenario }) => { + const { colorMode } = useColorMode(); + + // Import the images using `require` + const Svg = scenarios[scenario][colorMode]; + + return ( + + ); +}; + +export default ThemeImage; diff --git a/docs/static/img/scenarios/1-scenario-dark.svg b/docs/static/img/scenarios/1-scenario-dark.svg new file mode 100644 index 00000000..178d099f --- /dev/null +++ b/docs/static/img/scenarios/1-scenario-dark.svg @@ -0,0 +1,10 @@ + + + + + + + + APP 2APP 1BrokeremitlistenDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)HandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...) \ No newline at end of file diff --git a/docs/static/img/scenarios/1-scenario.svg b/docs/static/img/scenarios/1-scenario.svg new file mode 100644 index 00000000..57a42d7a --- /dev/null +++ b/docs/static/img/scenarios/1-scenario.svg @@ -0,0 +1,10 @@ + + + + + + + + APP 2APP 1BrokeremitlistenDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)HandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...) \ No newline at end of file diff --git a/docs/static/img/scenarios/2-scenario-dark.svg b/docs/static/img/scenarios/2-scenario-dark.svg new file mode 100644 index 00000000..65ffb6e0 --- /dev/null +++ b/docs/static/img/scenarios/2-scenario-dark.svg @@ -0,0 +1,11 @@ + + + + + + + + APP 3APP 2Broker 2emitDirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)Broker 1listenAPP 1emitDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)Context AContext BHandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...)emitlistenlistenlistenDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)HandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...)emitHandlerRegistry.listenDomainEvent(...)HandlerRegistry.listenDomainCloudEvent(...) \ No newline at end of file diff --git a/docs/static/img/scenarios/2-scenario.svg b/docs/static/img/scenarios/2-scenario.svg new file mode 100644 index 00000000..c0ecaf08 --- /dev/null +++ b/docs/static/img/scenarios/2-scenario.svg @@ -0,0 +1,11 @@ + + + + + + + + APP 3APP 2Broker 2emitDirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)Broker 1listenAPP 1emitDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)Context AContext BHandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...)emitlistenlistenlistenDomainEventBus.emit(...)DirectAsyncGateway.sendCommand(...)DirectAsyncGateway.requestReply(...)HandlerRegistry.listenEvent(...)HandlerRegistry.listenCloudEvent(...)HandlerRegistry.listenNotificationEvent(...)HandlerRegistry.listenNotificationCloudEvent(...)HandlerRegistry.handleCommand(...)HandlerRegistry.handleCloudEventCommand(...)HandlerRegistry.serveQuery(...)HandlerRegistry.serveCloudEventQuery(...)emitHandlerRegistry.listenDomainEvent(...)HandlerRegistry.listenDomainCloudEvent(...) \ No newline at end of file diff --git a/domain/domain-events/domain-events-api.gradle b/domain/domain-events/domain-events-api.gradle index 7af7f448..702f146a 100644 --- a/domain/domain-events/domain-events-api.gradle +++ b/domain/domain-events/domain-events-api.gradle @@ -6,4 +6,5 @@ ext { dependencies { api 'org.reactivestreams:reactive-streams:1.0.4' api 'io.cloudevents:cloudevents-api:4.0.1' + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' } \ No newline at end of file diff --git a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/Command.java b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/Command.java index 35aa0c23..f61956b7 100644 --- a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/Command.java +++ b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/Command.java @@ -1,11 +1,11 @@ package org.reactivecommons.api.domain; -import lombok.AllArgsConstructor; import lombok.Data; +import lombok.RequiredArgsConstructor; @Data -@AllArgsConstructor +@RequiredArgsConstructor public class Command { private final String name; private final String commandId; diff --git a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEvent.java b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEvent.java index 23717f00..b9319690 100644 --- a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEvent.java +++ b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEvent.java @@ -1,10 +1,10 @@ package org.reactivecommons.api.domain; -import lombok.AllArgsConstructor; import lombok.Data; +import lombok.RequiredArgsConstructor; @Data -@AllArgsConstructor +@RequiredArgsConstructor public class DomainEvent { private final String name; private final String eventId; diff --git a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEventBus.java b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEventBus.java index df697403..da5b7315 100644 --- a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEventBus.java +++ b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/DomainEventBus.java @@ -6,5 +6,12 @@ public interface DomainEventBus { Publisher emit(DomainEvent event); + Publisher emit(String domain, DomainEvent event); + Publisher emit(CloudEvent event); + + Publisher emit(String domain, CloudEvent event); + + Publisher emit(RawMessage event); + Publisher emit(String domain, RawMessage event); } diff --git a/domain/domain-events/src/main/java/org/reactivecommons/api/domain/RawMessage.java b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/RawMessage.java new file mode 100644 index 00000000..1dd9fa01 --- /dev/null +++ b/domain/domain-events/src/main/java/org/reactivecommons/api/domain/RawMessage.java @@ -0,0 +1,5 @@ +package org.reactivecommons.api.domain; + +public interface RawMessage { + String getType(); +} diff --git a/gradle.properties b/gradle.properties index b7d0d356..163a961c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -version=4.1.2 -toPublish=async-commons,async-commons-api,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-commons-rabbit-starter-eda,domain-events-api,async-rabbit +version=5.4.1 +toPublish=domain-events-api,async-commons-api,async-commons,shared-starter,async-rabbit,async-commons-rabbit-standalone,async-commons-rabbit-starter,async-kafka,async-kafka-starter,async-commons-starter onlyUpdater=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbf..a4b76b95 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3499ded5..e18bc253 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d..f3b75f3b 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +85,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +133,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +200,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85b..9b42019c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/main.gradle b/main.gradle index f06cfc60..39c14551 100644 --- a/main.gradle +++ b/main.gradle @@ -2,16 +2,38 @@ allprojects { apply plugin: 'java' apply plugin: 'jacoco' - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_17 + java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } repositories { mavenCentral() - maven { url 'https://repo.spring.io/snapshot' } - maven { url 'https://repo.spring.io/milestone' } + maven { url = 'https://repo.spring.io/snapshot' } + maven { url = 'https://repo.spring.io/milestone' } + } + + if (toPublish.split(',').contains(project.name) || project.name == rootProject.name) { + + sonar { + properties { + property "sonar.sourceEncoding", "UTF-8" + property 'sonar.projectKey', 'reactive-commons_reactive-commons-java' + property 'sonar.organization', 'reactive-commons' + property 'sonar.host.url', 'https://sonarcloud.io' + property "sonar.sources", "src/main" + property "sonar.test", "src/test" + property "sonar.java.binaries", "build/classes" + property "sonar.junit.reportPaths", "build/test-results/test" + property "sonar.java-coveragePlugin", "jacoco" + property "sonar.coverage.jacoco.xmlReportPaths", "${rootDir}/build/reports/jacoco/generateMergedReport/generateMergedReport.xml" + property "sonar.exclusions", ".github/**,samples/**/*,**/mybroker/**/*,**/standalone/**/*" + property 'sonar.coverage.exclusions', 'samples/**/*' + } + } } - group 'org.reactivecommons' + group = 'org.reactivecommons' } nexusPublishing { @@ -39,15 +61,20 @@ subprojects { testCompileOnly 'org.projectlombok:lombok' } + test.finalizedBy(project.tasks.jacocoTestReport) + jacocoTestReport { + dependsOn test reports { xml.setRequired true + html.setRequired true + csv.setRequired false } } test { useJUnitPlatform() - if (System.getProperty("env.ci").equals("true")) { + if (System.getProperty("env.ci") == "true") { systemProperty "env.ci", System.getProperty("env.ci") } @@ -55,7 +82,7 @@ subprojects { dependencyManagement { imports { - mavenBom 'org.springframework.boot:spring-boot-dependencies:3.3.1' + mavenBom 'org.springframework.boot:spring-boot-dependencies:3.4.4' } } @@ -63,7 +90,7 @@ subprojects { apply plugin: 'maven-publish' apply plugin: 'signing' - group groupId + group = groupId tasks.named("jar") { enabled = true @@ -134,6 +161,22 @@ subprojects { } } +tasks.register('generateMergedReport', JacocoReport) { + dependsOn test + dependsOn subprojects.test + dependsOn subprojects.javadoc + dependsOn subprojects.jacocoTestReport + additionalSourceDirs.setFrom files(subprojects.sourceSets.main.allSource.srcDirs) + sourceDirectories.setFrom files(subprojects.sourceSets.main.allSource.srcDirs) + classDirectories.setFrom files(subprojects.sourceSets.main.output).filter({ !it.toString().contains("sample") }) + executionData.setFrom project.fileTree(dir: '.', include: '**/build/jacoco/test.exec') + reports { + xml.setRequired true + csv.setRequired false + html.setRequired true + } +} + tasks.named('wrapper') { - gradleVersion = '8.8' + gradleVersion = '8.12.1' } \ No newline at end of file diff --git a/samples/async/async-kafka-sender-client/async-kafka-sender-client.gradle b/samples/async/async-kafka-sender-client/async-kafka-sender-client.gradle new file mode 100644 index 00000000..7a8e2851 --- /dev/null +++ b/samples/async/async-kafka-sender-client/async-kafka-sender-client.gradle @@ -0,0 +1,10 @@ +apply plugin: 'org.springframework.boot' + +dependencies { + implementation project(':shared') + implementation project(':async-kafka-starter') + implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'io.micrometer:micrometer-registry-prometheus' + implementation 'io.cloudevents:cloudevents-json-jackson:4.0.1' +} \ No newline at end of file diff --git a/samples/async/async-kafka-sender-client/src/main/java/sample/EDASampleSenderApp.java b/samples/async/async-kafka-sender-client/src/main/java/sample/EDASampleSenderApp.java new file mode 100644 index 00000000..eaf287da --- /dev/null +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/EDASampleSenderApp.java @@ -0,0 +1,17 @@ +package sample; + +import lombok.extern.java.Log; +import org.reactivecommons.async.impl.config.annotations.EnableDomainEventBus; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@Log +//@EnableDirectAsyncGateway +@EnableDomainEventBus +@SpringBootApplication +public class EDASampleSenderApp { + + public static void main(String[] args) { + SpringApplication.run(EDASampleSenderApp.class, args); + } +} diff --git a/samples/async/async-kafka-sender-client/src/main/java/sample/KafkaConfig.java b/samples/async/async-kafka-sender-client/src/main/java/sample/KafkaConfig.java new file mode 100644 index 00000000..f1eabbba --- /dev/null +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/KafkaConfig.java @@ -0,0 +1,26 @@ +package sample; + +import org.reactivecommons.async.kafka.KafkaSetupUtils; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import java.io.IOException; +import java.nio.file.Path; + +@Configuration +public class KafkaConfig { + + @Bean + @Primary + public AsyncKafkaProps kafkaProps() throws IOException { + AsyncKafkaProps kafkaProps = new AsyncKafkaProps(); + kafkaProps.setCreateTopology(true); + kafkaProps.setMaxRetries(5); + kafkaProps.setRetryDelay(1000); + kafkaProps.setWithDLQRetry(true); + kafkaProps.setConnectionProperties(KafkaSetupUtils.readPropsFromDotEnv(Path.of(".kafka-env"))); + return kafkaProps; + } +} diff --git a/samples/async/async-kafka-sender-client/src/main/java/sample/ListenerConfig.java b/samples/async/async-kafka-sender-client/src/main/java/sample/ListenerConfig.java new file mode 100644 index 00000000..a9b4c9f1 --- /dev/null +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/ListenerConfig.java @@ -0,0 +1,36 @@ +package sample; + +import io.cloudevents.CloudEvent; +import lombok.extern.log4j.Log4j2; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.async.api.HandlerRegistry; +import org.reactivecommons.async.impl.config.annotations.EnableEventListeners; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import reactor.core.publisher.Mono; +import sample.model.Constants; + +@Log4j2 +@Configuration +@EnableEventListeners +public class ListenerConfig { + + @Bean + @Primary + public HandlerRegistry handlerRegistrySubs() { + return HandlerRegistry.register() + .listenEvent(Constants.DATA_RESET, this::reset, String.class) + .listenCloudEvent("event-name", this::reset2); + } + + private Mono reset2(CloudEvent cloudEvent) { + log.info("reset2: " + cloudEvent); + return Mono.empty(); + } + + public Mono reset(DomainEvent ignored) { + log.info("reset: {}", ignored); + return Mono.empty(); + } +} diff --git a/samples/async/async-kafka-sender-client/src/main/java/sample/SampleRestController.java b/samples/async/async-kafka-sender-client/src/main/java/sample/SampleRestController.java new file mode 100644 index 00000000..27c11203 --- /dev/null +++ b/samples/async/async-kafka-sender-client/src/main/java/sample/SampleRestController.java @@ -0,0 +1,125 @@ +package sample; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.jackson.JsonCloudEventData; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; +import sample.model.Constants; + +import java.net.URI; +import java.time.OffsetDateTime; +import java.util.UUID; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + + +@RestController +@RequiredArgsConstructor +public class SampleRestController { + // private final DirectAsyncGateway directAsyncGateway; + private final DomainEventBus domainEventBus; + private final ObjectMapperSupplier supplier; + private final String target = "receiver-eda"; + + // Notification Event + @DeleteMapping(path = "/api/teams/fail", produces = APPLICATION_JSON_VALUE) + public Mono> resetData() { + DomainEvent event = new DomainEvent<>(Constants.DATA_RESET, UUID.randomUUID().toString(), ""); + return Mono.from(domainEventBus.emit(event)).thenReturn(event); + } + + // Notification Event + @DeleteMapping(path = "/api/teams/ok", produces = APPLICATION_JSON_VALUE) + public Mono> event() { + DomainEvent event = new DomainEvent<>("event-name", UUID.randomUUID().toString(), ""); + return Mono.from(domainEventBus.emit(event)).thenReturn(event); + } + + // Notification Event + @DeleteMapping(path = "/api/teams/cloud/event", produces = APPLICATION_JSON_VALUE) + public Mono cloudEvent() throws JsonProcessingException { + SampleEvent eventData = new SampleEvent(); + eventData.setName("Juan"); + eventData.setDescription("A software developer"); + + CloudEvent event = CloudEventBuilder.v1() + .withId(UUID.randomUUID().toString()) + .withSource(URI.create("https://reactive-commons.org/foos")) + .withType("event-name") + .withTime(OffsetDateTime.now()) + .withData("application/json", JsonCloudEventData.wrap(supplier.get().valueToTree(eventData))) + .build(); + return Mono.from(domainEventBus.emit(event)).thenReturn(event); + } +// +// // Query +// @GetMapping(path = "/api/teams", produces = APPLICATION_JSON_VALUE) +// public Mono getTeams() { +// CloudEvent query = CloudEventBuilder.v1() +// .withId(UUID.randomUUID().toString()) +// .withSource(URI.create("https://reactive-commons.org/foos")) +// .withType(Constants.GET_TEAMS) +// .withTime(OffsetDateTime.now()) +// .withData("application/json", CloudEventBuilderExt.asBytes("")) +// .build(); +// return directAsyncGateway.requestReply(query, target, CloudEvent.class) +// .map(event -> CloudEventBuilderExt.fromCloudEventData(event, Teams.class)); +// } +// +// // Query +// @GetMapping(path = "/api/teams/{team}", produces = APPLICATION_JSON_VALUE) +// public Mono getTeamMembers(@PathVariable("team") String team) { +// CloudEvent query = CloudEventBuilder.v1() +// .withId(UUID.randomUUID().toString()) +// .withSource(URI.create("https://reactive-commons.org/foos")) +// .withType(Constants.GET_TEAM_MEMBERS) +// .withTime(OffsetDateTime.now()) +// .withData("application/json", CloudEventBuilderExt.asBytes(team)) +// .build(); +// return directAsyncGateway.requestReply(query, target, CloudEvent.class) +// .map(event -> CloudEventBuilderExt.fromCloudEventData(event, Members.class)); +// } +// +// // Command +// @PostMapping(path = "/api/teams/{team}/members", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE) +// public Mono addMember(@PathVariable("team") String team, @RequestBody Member member) { +// AddMemberCommand commandData = AddMemberCommand.builder().member(member).teamName(team).build(); +// CloudEvent command = CloudEventBuilder.v1() +// .withId(UUID.randomUUID().toString()) +// .withSource(URI.create("https://reactive-commons.org/foos")) +// .withType(Constants.ADD_MEMBER) +// .withTime(OffsetDateTime.now()) +// .withData("application/json", CloudEventBuilderExt.asBytes(commandData)) +// .build(); +// return directAsyncGateway.sendCommand(command, target).thenReturn(commandData); +// } +// +// // Event +// @DeleteMapping(path = "/api/teams/{team}/members/{member}", produces = APPLICATION_JSON_VALUE) +// public Mono removeMember(@PathVariable("team") String team, +// @PathVariable("member") String member) { +// RemovedMemberEvent eventData = RemovedMemberEvent.builder().teamName(team).username(member).build(); +// CloudEvent event = CloudEventBuilder.v1() +// .withId(UUID.randomUUID().toString()) +// .withSource(URI.create("https://reactive-commons.org/foos")) +// .withType(Constants.MEMBER_REMOVED) +// .withTime(OffsetDateTime.now()) +// .withData("application/json", CloudEventBuilderExt.asBytes(eventData)) +// .build(); +// return Mono.from(domainEventBus.emit(event)).thenReturn(eventData); +// } + + @Data + public static class SampleEvent { + private String name; + private String description; + } +} diff --git a/samples/async/async-kafka-sender-client/src/main/resources/application.yaml b/samples/async/async-kafka-sender-client/src/main/resources/application.yaml new file mode 100644 index 00000000..323bc046 --- /dev/null +++ b/samples/async/async-kafka-sender-client/src/main/resources/application.yaml @@ -0,0 +1,17 @@ +spring: + application: + name: sender-eda + rabbitmq: + virtual-host: / +server: + port: 4001 +management: + endpoint: + health: + show-details: always + endpoints: + web: + exposure: + include: health,prometheus + + diff --git a/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java b/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java index 9c86cfa2..75f79669 100644 --- a/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java +++ b/samples/async/async-receiver-responder/src/main/java/sample/HandlersConfig.java @@ -3,7 +3,7 @@ import lombok.extern.log4j.Log4j2; import org.reactivecommons.async.api.DynamicRegistry; import org.reactivecommons.async.api.HandlerRegistry; -import org.reactivecommons.async.api.handlers.EventHandler; +import org.reactivecommons.async.api.handlers.DomainEventHandler; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -18,7 +18,7 @@ @Configuration public class HandlersConfig { - private EventHandler animalEventEventHandler; + private DomainEventHandler animalEventEventHandler; @Bean @Primary diff --git a/samples/async/eda-async-receiver-responder/eda-async-receiver-sample.gradle b/samples/async/eda-async-receiver-responder/eda-async-receiver-sample.gradle index 538e3120..f8e8ef06 100644 --- a/samples/async/eda-async-receiver-responder/eda-async-receiver-sample.gradle +++ b/samples/async/eda-async-receiver-responder/eda-async-receiver-sample.gradle @@ -2,7 +2,7 @@ apply plugin: 'org.springframework.boot' dependencies { implementation project(":shared") - implementation project(":async-commons-rabbit-starter-eda") + implementation project(":async-commons-rabbit-starter") implementation 'org.springframework.boot:spring-boot-starter' implementation 'io.cloudevents:cloudevents-core:4.0.1' } \ No newline at end of file diff --git a/samples/async/eda-async-receiver-responder/src/main/java/sample/MyDomainConfig.java b/samples/async/eda-async-receiver-responder/src/main/java/sample/MyDomainConfig.java index 8b95a111..2d3b8924 100644 --- a/samples/async/eda-async-receiver-responder/src/main/java/sample/MyDomainConfig.java +++ b/samples/async/eda-async-receiver-responder/src/main/java/sample/MyDomainConfig.java @@ -2,7 +2,7 @@ import org.reactivecommons.async.rabbit.config.RabbitProperties; import org.reactivecommons.async.rabbit.config.props.AsyncProps; -import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomainProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -11,7 +11,7 @@ public class MyDomainConfig { @Bean @Primary - public AsyncPropsDomainProperties customDomainProperties() { + public AsyncRabbitPropsDomainProperties customDomainProperties() { RabbitProperties propertiesApp = new RabbitProperties(); propertiesApp.setHost("localhost"); propertiesApp.setPort(5672); @@ -26,7 +26,7 @@ public AsyncPropsDomainProperties customDomainProperties() { propertiesAccounts.setUsername("guest"); propertiesAccounts.setPassword("guest"); - return AsyncPropsDomainProperties.builder() + return AsyncRabbitPropsDomainProperties.builder() .withDomain("app", AsyncProps.builder() .connectionProperties(propertiesApp) .build()) diff --git a/samples/async/eda-async-receiver-responder/src/main/java/sample/UseCase.java b/samples/async/eda-async-receiver-responder/src/main/java/sample/UseCase.java index 676903cc..946cd642 100644 --- a/samples/async/eda-async-receiver-responder/src/main/java/sample/UseCase.java +++ b/samples/async/eda-async-receiver-responder/src/main/java/sample/UseCase.java @@ -6,7 +6,7 @@ import lombok.extern.log4j.Log4j2; import org.reactivecommons.api.domain.Command; import org.reactivecommons.api.domain.DomainEvent; -import org.reactivecommons.async.rabbit.converters.json.CloudEventBuilderExt; +import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; import sample.model.Constants; diff --git a/samples/async/eda-async-sender-client-domain-a/eda-async-sender-client-domain-a.gradle b/samples/async/eda-async-sender-client-domain-a/eda-async-sender-client-domain-a.gradle index 0c3c74d6..b4fa1123 100644 --- a/samples/async/eda-async-sender-client-domain-a/eda-async-sender-client-domain-a.gradle +++ b/samples/async/eda-async-sender-client-domain-a/eda-async-sender-client-domain-a.gradle @@ -2,7 +2,7 @@ apply plugin: 'org.springframework.boot' dependencies { implementation project(':shared') - implementation project(':async-commons-rabbit-starter-eda') + implementation project(':async-commons-rabbit-starter') implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' diff --git a/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java b/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java index f422b54b..1e26740f 100644 --- a/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java +++ b/samples/async/eda-async-sender-client-domain-a/src/main/java/sample/SampleRestController.java @@ -3,10 +3,9 @@ import io.cloudevents.CloudEvent; import io.cloudevents.core.builder.CloudEventBuilder; import lombok.RequiredArgsConstructor; -import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DirectAsyncGateway; -import org.reactivecommons.async.rabbit.converters.json.CloudEventBuilderExt; +import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -38,16 +37,16 @@ public class SampleRestController { // Query @GetMapping(path = "/api/teams", produces = APPLICATION_JSON_VALUE) - public Mono getTeams() { - CloudEvent query = CloudEventBuilder.v1() + public Mono getTeams() { + RemovedMemberEvent eventData = RemovedMemberEvent.builder().teamName("team").username("member").build(); + CloudEvent event = CloudEventBuilder.v1() .withId(UUID.randomUUID().toString()) .withSource(URI.create("https://reactive-commons.org/foos")) - .withType(Constants.GET_TEAMS) + .withType(Constants.MEMBER_REMOVED_EXTERNAL_DOMAIN) .withTime(OffsetDateTime.now()) - .withData("application/json", CloudEventBuilderExt.asBytes("")) + .withData("application/json", CloudEventBuilderExt.asBytes(eventData)) .build(); - return directAsyncGateway.requestReply(query, target, CloudEvent.class, externalDomain) - .map(event -> CloudEventBuilderExt.fromCloudEventData(event, Teams.class)); + return Mono.from(domainEventBus.emit(event)); } // Query diff --git a/samples/async/eda-async-sender-client-domain-a/src/main/resources/application.yaml b/samples/async/eda-async-sender-client-domain-a/src/main/resources/application.yaml index 878800fa..5b0a5e0a 100644 --- a/samples/async/eda-async-sender-client-domain-a/src/main/resources/application.yaml +++ b/samples/async/eda-async-sender-client-domain-a/src/main/resources/application.yaml @@ -17,7 +17,7 @@ app: async: app: # domain-a connectionProperties: - virtualHost: domain-a + virtualHost: / teams: connectionProperties: virtualHost: / diff --git a/samples/async/eda-async-sender-client/eda-async-sender-client.gradle b/samples/async/eda-async-sender-client/eda-async-sender-client.gradle index 0c3c74d6..b4fa1123 100644 --- a/samples/async/eda-async-sender-client/eda-async-sender-client.gradle +++ b/samples/async/eda-async-sender-client/eda-async-sender-client.gradle @@ -2,7 +2,7 @@ apply plugin: 'org.springframework.boot' dependencies { implementation project(':shared') - implementation project(':async-commons-rabbit-starter-eda') + implementation project(':async-commons-rabbit-starter') implementation 'org.springframework.boot:spring-boot-starter-webflux' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' diff --git a/samples/async/eda-async-sender-client/src/main/java/sample/SampleRestController.java b/samples/async/eda-async-sender-client/src/main/java/sample/SampleRestController.java index 1986bb9b..cd395029 100644 --- a/samples/async/eda-async-sender-client/src/main/java/sample/SampleRestController.java +++ b/samples/async/eda-async-sender-client/src/main/java/sample/SampleRestController.java @@ -6,7 +6,7 @@ import org.reactivecommons.api.domain.DomainEvent; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.api.DirectAsyncGateway; -import org.reactivecommons.async.rabbit.converters.json.CloudEventBuilderExt; +import org.reactivecommons.async.commons.converters.json.CloudEventBuilderExt; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/samples/async/no-springboot-client/no-springboot-client.gradle b/samples/async/no-springboot-client/no-springboot-client.gradle deleted file mode 100644 index 5685503e..00000000 --- a/samples/async/no-springboot-client/no-springboot-client.gradle +++ /dev/null @@ -1,3 +0,0 @@ -dependencies { - implementation project(':async-commons-rabbit-starter') -} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index b64577d1..410651d8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,7 +13,7 @@ FileTree buildFiles = fileTree(rootDir) { String rootDirPath = rootDir.absolutePath + File.separator buildFiles.each { File buildFile -> - boolean isDefaultName = 'build.gradle'.equals(buildFile.name) + boolean isDefaultName = 'build.gradle' == buildFile.name if (isDefaultName) { String buildFilePath = buildFile.parentFile.absolutePath String projectPath = buildFilePath.replace(rootDirPath, '').replace(File.separator, ':') diff --git a/starters/async-commons-starter/async-commons-starter.gradle b/starters/async-commons-starter/async-commons-starter.gradle new file mode 100644 index 00000000..43493bd0 --- /dev/null +++ b/starters/async-commons-starter/async-commons-starter.gradle @@ -0,0 +1,17 @@ +ext { + artifactId = 'async-commons-starter' + artifactDescription = 'Async Commons Starter for Spring Boot' +} +dependencies { + api 'io.projectreactor:reactor-core' + api project(':async-commons') + compileOnly 'org.springframework.boot:spring-boot-starter' + compileOnly 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-aop' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + + testImplementation 'io.projectreactor:reactor-test' + testImplementation 'org.springframework.boot:spring-boot-starter-actuator' +} \ No newline at end of file diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java similarity index 50% rename from async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java index fcbe9833..e3181e2e 100644 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableCommandListeners.java @@ -1,16 +1,20 @@ package org.reactivecommons.async.impl.config.annotations; -import org.reactivecommons.async.rabbit.config.CommandListenersConfig; +import org.reactivecommons.async.starter.listeners.CommandsListenerConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -@Import(CommandListenersConfig.class) +@Import(CommandsListenerConfig.class) @Configuration public @interface EnableCommandListeners { } diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java similarity index 56% rename from async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java index 359913fd..300297dd 100644 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDirectAsyncGateway.java @@ -1,10 +1,14 @@ package org.reactivecommons.async.impl.config.annotations; -import org.reactivecommons.async.rabbit.config.DirectAsyncGatewayConfig; +import org.reactivecommons.async.starter.senders.DirectAsyncGatewayConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java similarity index 56% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java index 6820ab50..b3e33c09 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableDomainEventBus.java @@ -1,10 +1,14 @@ package org.reactivecommons.async.impl.config.annotations; -import org.reactivecommons.async.rabbit.config.EventBusConfig; +import org.reactivecommons.async.starter.senders.EventBusConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java similarity index 50% rename from async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java index 87791c20..c5f9839d 100644 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableEventListeners.java @@ -1,15 +1,19 @@ package org.reactivecommons.async.impl.config.annotations; -import org.reactivecommons.async.rabbit.config.EventListenersConfig; +import org.reactivecommons.async.starter.listeners.EventsListenerConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -@Import(EventListenersConfig.class) +@Import(EventsListenerConfig.class) @Configuration public @interface EnableEventListeners { } diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java new file mode 100644 index 00000000..3107c83e --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableMessageListeners.java @@ -0,0 +1,30 @@ +package org.reactivecommons.async.impl.config.annotations; + +import org.reactivecommons.async.starter.listeners.CommandsListenerConfig; +import org.reactivecommons.async.starter.listeners.EventsListenerConfig; +import org.reactivecommons.async.starter.listeners.NotificationEventsListenerConfig; +import org.reactivecommons.async.starter.listeners.QueriesListenerConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation enables all messages listeners (Query, Commands, Events). If you want to enable separately, please use + * EnableCommandListeners, EnableQueryListeners or EnableEventListeners. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +@Import({CommandsListenerConfig.class, QueriesListenerConfig.class, EventsListenerConfig.class, + NotificationEventsListenerConfig.class}) +@Configuration +public @interface EnableMessageListeners { +} + + + diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java new file mode 100644 index 00000000..e4ce2e36 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableNotificationListener.java @@ -0,0 +1,23 @@ +package org.reactivecommons.async.impl.config.annotations; + +import org.reactivecommons.async.starter.listeners.NotificationEventsListenerConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +@Import(NotificationEventsListenerConfig.class) +@Configuration +public @interface EnableNotificationListener { +} + + + diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java similarity index 50% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java index 6eb878b0..001433e2 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/impl/config/annotations/EnableQueryListeners.java @@ -1,16 +1,20 @@ package org.reactivecommons.async.impl.config.annotations; -import org.reactivecommons.async.rabbit.config.QueryListenerConfig; +import org.reactivecommons.async.starter.listeners.QueriesListenerConfig; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented -@Import(QueryListenerConfig.class) +@Import(QueriesListenerConfig.class) @Configuration public @interface EnableQueryListeners { } diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProvider.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProvider.java new file mode 100644 index 00000000..771b9e88 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProvider.java @@ -0,0 +1,29 @@ +package org.reactivecommons.async.starter.broker; + +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.config.health.RCHealth; +import org.reactivecommons.async.starter.props.GenericAsyncProps; +import reactor.core.publisher.Mono; + +@SuppressWarnings("rawtypes") +public interface BrokerProvider { + T getProps(); + + DomainEventBus getDomainBus(); + + DirectAsyncGateway getDirectAsyncGateway(); + + void listenDomainEvents(HandlerResolver resolver); + + void listenNotificationEvents(HandlerResolver resolver); + + void listenCommands(HandlerResolver resolver); + + void listenQueries(HandlerResolver resolver); + + void listenReplies(); + + Mono healthCheck(); +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProviderFactory.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProviderFactory.java new file mode 100644 index 00000000..8a599069 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/BrokerProviderFactory.java @@ -0,0 +1,13 @@ +package org.reactivecommons.async.starter.broker; + +import org.reactivecommons.async.starter.props.GenericAsyncProps; + +@SuppressWarnings("rawtypes") +public interface BrokerProviderFactory { + String getBrokerType(); + + DiscardProvider getDiscardProvider(T props); + + BrokerProvider getProvider(String domain, T props, DiscardProvider discardProvider); + +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/DiscardProvider.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/DiscardProvider.java new file mode 100644 index 00000000..aa735233 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/broker/DiscardProvider.java @@ -0,0 +1,8 @@ +package org.reactivecommons.async.starter.broker; + +import org.reactivecommons.async.commons.DiscardNotifier; + +import java.util.function.Supplier; + +public interface DiscardProvider extends Supplier { +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ConnectionManager.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ConnectionManager.java new file mode 100644 index 00000000..92ba2630 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ConnectionManager.java @@ -0,0 +1,25 @@ +package org.reactivecommons.async.starter.config; + +import org.reactivecommons.async.starter.broker.BrokerProvider; + +import java.util.Map; +import java.util.TreeMap; +import java.util.function.BiConsumer; + +@SuppressWarnings("rawtypes") +public class ConnectionManager { + private final Map connections = new TreeMap<>(); + + public void forDomain(BiConsumer consumer) { + connections.forEach(consumer); + } + + public ConnectionManager addDomain(String domain, BrokerProvider domainConn) { + connections.put(domain, domainConn); + return this; + } + + public Map getProviders() { + return connections; + } +} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/DomainHandlers.java similarity index 65% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/DomainHandlers.java index 120649b0..0704f715 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/DomainHandlers.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/DomainHandlers.java @@ -1,6 +1,7 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.starter.config; -import org.reactivecommons.async.rabbit.HandlerResolver; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; import java.util.Map; import java.util.TreeMap; @@ -15,7 +16,7 @@ public void add(String domain, HandlerResolver resolver) { public HandlerResolver get(String domain) { HandlerResolver handlerResolver = handlers.get(domain); if (handlerResolver == null) { - throw new RuntimeException("You are trying to use the domain " + domain + throw new InvalidConfigurationException("You are trying to use the domain " + domain + " but this connection is not defined"); } return handlerResolver; diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfig.java new file mode 100644 index 00000000..fdb02d8d --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfig.java @@ -0,0 +1,100 @@ +package org.reactivecommons.async.starter.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.converters.json.DefaultObjectMapperSupplier; +import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.ext.DefaultCustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthConfig; +import org.reactivecommons.async.starter.props.GenericAsyncProps; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.util.Map; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@Log +@Configuration +@RequiredArgsConstructor +@Import(ReactiveCommonsHealthConfig.class) +@ComponentScan("org.reactivecommons.async.starter.impl.common") +public class ReactiveCommonsConfig { + + @Bean + @SuppressWarnings({"rawtypes", "unchecked"}) + public ConnectionManager buildConnectionManager(ApplicationContext context) { + final Map props = context.getBeansOfType(GenericAsyncPropsDomain.class); + final Map providers = context.getBeansOfType(BrokerProviderFactory.class); + + ConnectionManager connectionManager = new ConnectionManager(); + props.forEach((beanName, domainProps) -> { + final GenericAsyncProps defaultDomainProps = domainProps.getProps(DEFAULT_DOMAIN); + domainProps.forEach((domain, asyncPropsObject) -> { + String domainName = (String) domain; + final GenericAsyncProps asyncProps = (GenericAsyncProps) asyncPropsObject; + if (asyncProps.isEnabled()) { + BrokerProviderFactory factory = providers.get(asyncProps.getBrokerType()); + if (!defaultDomainProps.isEnabled()) { + asyncProps.setUseDiscardNotifierPerDomain(true); + } + DiscardProvider discardProvider = factory.getDiscardProvider(defaultDomainProps); + BrokerProvider provider = factory.getProvider(domainName, asyncProps, discardProvider); + connectionManager.addDomain(domainName, provider); + } + }); + }); + return connectionManager; + } + + @Bean + @ConditionalOnMissingBean + public BrokerConfig brokerConfig() { + return new BrokerConfig(); + } + + @Bean + @ConditionalOnMissingBean + public ObjectMapperSupplier objectMapperSupplier() { + return new DefaultObjectMapperSupplier(); + } + + @Bean + @ConditionalOnMissingBean + public ObjectMapper defaultReactiveCommonsObjectMapper(ObjectMapperSupplier supplier) { + return supplier.get(); + } + + @Bean + @ConditionalOnMissingBean(ReactiveReplyRouter.class) + public ReactiveReplyRouter defaultReactiveReplyRouter() { + return new ReactiveReplyRouter(); + } + + @Bean + @ConditionalOnMissingBean(MeterRegistry.class) + public MeterRegistry defaultRabbitMeterRegistry() { + return new SimpleMeterRegistry(); + } + + @Bean + @ConditionalOnMissingBean + public CustomReporter reactiveCommonsCustomErrorReporter() { + return new DefaultCustomReporter(); + } + +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java new file mode 100644 index 00000000..0f7a7a11 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/ReactiveCommonsListenersConfig.java @@ -0,0 +1,68 @@ +package org.reactivecommons.async.starter.config; + +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import org.reactivecommons.async.api.DefaultCommandHandler; +import org.reactivecommons.async.api.DefaultQueryHandler; +import org.reactivecommons.async.api.HandlerRegistry; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.utils.resolver.HandlerResolverBuilder; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import reactor.core.publisher.Mono; + +import java.util.Map; + +@Log +@Configuration +@RequiredArgsConstructor +@ComponentScan("org.reactivecommons.async.starter.impl.listener") +public class ReactiveCommonsListenersConfig { + + @Bean + @SuppressWarnings({"rawtypes", "unchecked"}) + public DomainHandlers buildHandlers(ApplicationContext context, + HandlerRegistry primaryRegistry, DefaultCommandHandler commandHandler) { + DomainHandlers handlers = new DomainHandlers(); + final Map registries = context.getBeansOfType(HandlerRegistry.class); + if (!registries.containsValue(primaryRegistry)) { + registries.put("primaryHandlerRegistry", primaryRegistry); + } + final Map props = context.getBeansOfType(GenericAsyncPropsDomain.class); + props.forEach((beanName, properties) -> properties + .forEach((domain, asyncProps) -> { + String domainName = (String) domain; + HandlerResolver resolver = HandlerResolverBuilder.buildResolver( + domainName, registries, commandHandler + ); + handlers.add(domainName, resolver); + }) + ); + return handlers; + } + + @Bean + @ConditionalOnMissingBean + @SuppressWarnings("rawtypes") + public DefaultQueryHandler defaultHandler() { + return (DefaultQueryHandler) command -> + Mono.error(new RuntimeException("No Handler Registered")); + } + + @Bean + @ConditionalOnMissingBean + @SuppressWarnings("rawtypes") + public DefaultCommandHandler defaultCommandHandler() { + return message -> Mono.error(new RuntimeException("No Handler Registered")); + } + + @Bean + @ConditionalOnMissingBean + public HandlerRegistry defaultHandlerRegistry() { + return HandlerRegistry.register(); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealth.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealth.java new file mode 100644 index 00000000..1c15a937 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealth.java @@ -0,0 +1,46 @@ +package org.reactivecommons.async.starter.config.health; + +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +@Getter +@Builder +@RequiredArgsConstructor +public class RCHealth { + private final Status status; + private final Map details; + + public enum Status { + UP, + DOWN + } + + public static class RCHealthBuilder { + public RCHealthBuilder() { + this.details = new HashMap<>(); + } + + public RCHealthBuilder up() { + this.status = Status.UP; + return this; + } + + public RCHealthBuilder down() { + this.status = Status.DOWN; + return this; + } + + public RCHealthBuilder withDetail(String key, Object value) { + this.details.put(key, value); + return this; + } + + public RCHealth build() { + return new RCHealth(this.status, this.details); + } + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealthIndicator.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealthIndicator.java new file mode 100644 index 00000000..da158e66 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/RCHealthIndicator.java @@ -0,0 +1,15 @@ +package org.reactivecommons.async.starter.config.health; + +import reactor.core.publisher.Mono; + +public abstract class RCHealthIndicator { + + public Mono health() { + return doHealthCheck(RCHealth.builder()) + .onErrorResume(e -> + Mono.just(RCHealth.builder().down().withDetail("error", e.getMessage()).build()) + ); + } + + public abstract Mono doHealthCheck(RCHealth.RCHealthBuilder builder); +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthConfig.java new file mode 100644 index 00000000..c649942d --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthConfig.java @@ -0,0 +1,20 @@ +package org.reactivecommons.async.starter.config.health; + +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnClass(AbstractReactiveHealthIndicator.class) +public class ReactiveCommonsHealthConfig { + + @Bean + @ConditionalOnProperty(prefix = "management.health.reactive-commons", name = "enabled", havingValue = "true", + matchIfMissing = true) + public ReactiveCommonsHealthIndicator reactiveCommonsHealthIndicator(ConnectionManager manager) { + return new ReactiveCommonsHealthIndicator(manager); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicator.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicator.java new file mode 100644 index 00000000..76178b28 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicator.java @@ -0,0 +1,39 @@ +package org.reactivecommons.async.starter.config.health; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Log4j2 +@RequiredArgsConstructor +public class ReactiveCommonsHealthIndicator extends AbstractReactiveHealthIndicator { + public static final String DOMAIN = "domain"; + public static final String VERSION = "version"; + private final ConnectionManager manager; + + @Override + @SuppressWarnings("unchecked") + protected Mono doHealthCheck(Health.Builder builder) { + return Flux.fromIterable(manager.getProviders().values()) + .flatMap(BrokerProvider::healthCheck) + .reduceWith(Health::up, (health, status) -> + reduceHealth((Health.Builder) health, (RCHealth) status) + ) + .map(b -> ((Health.Builder) b).build()); + + } + + private Health.Builder reduceHealth(Health.Builder builder, RCHealth status) { + String domain = status.getDetails().get(DOMAIN).toString(); + if (status.getStatus().equals(RCHealth.Status.DOWN)) { + log.error("Broker of domain {} is down", domain); + return builder.down().withDetail(domain, status.getDetails()); + } + return builder.withDetail(domain, status.getDetails()); + } +} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/exceptions/InvalidConfigurationException.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/exceptions/InvalidConfigurationException.java similarity index 72% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/exceptions/InvalidConfigurationException.java rename to starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/exceptions/InvalidConfigurationException.java index 8db77105..8c076938 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/exceptions/InvalidConfigurationException.java +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/exceptions/InvalidConfigurationException.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.rabbit.config.exceptions; +package org.reactivecommons.async.starter.exceptions; public class InvalidConfigurationException extends RuntimeException { public InvalidConfigurationException(String message) { diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/AbstractListenerConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/AbstractListenerConfig.java new file mode 100644 index 00000000..e17d19e0 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/AbstractListenerConfig.java @@ -0,0 +1,16 @@ +package org.reactivecommons.async.starter.listeners; + +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; + +public abstract class AbstractListenerConfig { + + protected AbstractListenerConfig(ConnectionManager manager, DomainHandlers handlers) { + manager.forDomain((domain, provider) -> listen(domain, provider, handlers.get(domain))); + } + + @SuppressWarnings("rawtypes") + abstract void listen(String domain, BrokerProvider provider, HandlerResolver resolver); +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfig.java new file mode 100644 index 00000000..80f1dbc6 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfig.java @@ -0,0 +1,26 @@ +package org.reactivecommons.async.starter.listeners; + + +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import({ReactiveCommonsConfig.class, ReactiveCommonsListenersConfig.class}) +public class CommandsListenerConfig extends AbstractListenerConfig { + + public CommandsListenerConfig(ConnectionManager manager, DomainHandlers handlers) { + super(manager, handlers); + } + + @SuppressWarnings("rawtypes") + @Override + void listen(String domain, BrokerProvider provider, HandlerResolver resolver) { + provider.listenCommands(resolver); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/EventsListenerConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/EventsListenerConfig.java new file mode 100644 index 00000000..3e31d9fa --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/EventsListenerConfig.java @@ -0,0 +1,26 @@ +package org.reactivecommons.async.starter.listeners; + + +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import({ReactiveCommonsConfig.class, ReactiveCommonsListenersConfig.class}) +public class EventsListenerConfig extends AbstractListenerConfig { + + public EventsListenerConfig(ConnectionManager manager, DomainHandlers handlers) { + super(manager, handlers); + } + + @SuppressWarnings("rawtypes") + @Override + void listen(String domain, BrokerProvider provider, HandlerResolver resolver) { + provider.listenDomainEvents(resolver); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfig.java new file mode 100644 index 00000000..8e918915 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfig.java @@ -0,0 +1,26 @@ +package org.reactivecommons.async.starter.listeners; + + +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import({ReactiveCommonsConfig.class, ReactiveCommonsListenersConfig.class}) +public class NotificationEventsListenerConfig extends AbstractListenerConfig { + + public NotificationEventsListenerConfig(ConnectionManager manager, DomainHandlers handlers) { + super(manager, handlers); + } + + @SuppressWarnings("rawtypes") + @Override + void listen(String domain, BrokerProvider provider, HandlerResolver resolver) { + provider.listenNotificationEvents(resolver); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfig.java new file mode 100644 index 00000000..35ed0f21 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfig.java @@ -0,0 +1,26 @@ +package org.reactivecommons.async.starter.listeners; + + +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import({ReactiveCommonsConfig.class, ReactiveCommonsListenersConfig.class}) +public class QueriesListenerConfig extends AbstractListenerConfig { + + public QueriesListenerConfig(ConnectionManager manager, DomainHandlers handlers) { + super(manager, handlers); + } + + @SuppressWarnings("rawtypes") + @Override + void listen(String domain, BrokerProvider provider, HandlerResolver resolver) { + provider.listenQueries(resolver); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncProps.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncProps.java new file mode 100644 index 00000000..20dabcb1 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncProps.java @@ -0,0 +1,27 @@ +package org.reactivecommons.async.starter.props; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public abstract class GenericAsyncProps

{ + private String appName; + private String secret; + + public abstract void setConnectionProperties(P properties); + + public abstract P getConnectionProperties(); + + public abstract String getBrokerType(); + + public abstract boolean isEnabled(); + + public abstract void setUseDiscardNotifierPerDomain(boolean enabled); +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomain.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomain.java new file mode 100644 index 00000000..a6717026 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomain.java @@ -0,0 +1,169 @@ +package org.reactivecommons.async.starter.props; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; + +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + + +@Getter +@Setter +public class GenericAsyncPropsDomain, P> extends HashMap { + private Class asyncPropsClass; + private Class

propsClass; + + public GenericAsyncPropsDomain(String defaultAppName, + P defaultProperties, + GenericAsyncPropsDomainProperties configured, + SecretFiller

secretFiller, + Class asyncPropsClass, + Class

propsClass) { + super(configured); + this.propsClass = propsClass; + this.asyncPropsClass = asyncPropsClass; + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + this.computeIfAbsent(DEFAULT_DOMAIN, k -> { + T defaultApp = AsyncPropsDomainBuilder.instantiate(asyncPropsClass); + defaultApp.setConnectionProperties(mapper.convertValue(defaultProperties, propsClass)); + return defaultApp; + }); + super.forEach((key, value) -> { // To ensure that each domain has an appName + if (value.getAppName() == null) { + if (defaultAppName == null || defaultAppName.isEmpty()) { + throw new InvalidConfigurationException("defaultAppName does not has value and domain " + key + + " has not set the property appName. please use respective property or " + + " spring.application.name property or withDefaultAppName in builder"); + } + value.setAppName(defaultAppName); + } + if (value.getConnectionProperties() == null) { + if (defaultProperties == null) { + throw new InvalidConfigurationException("Domain " + key + " could not be instantiated because no" + + " properties found, please use withDefaultProperties or define the" + + "default " + key + " domain with properties explicitly"); + } + value.setConnectionProperties(mapper.convertValue(defaultProperties, propsClass)); + } + if (secretFiller != null) { + secretFiller.fillWithSecret(key, value); + } + fillCustoms(value); + }); + } + + protected void fillCustoms(T asyncProps) { + // To be overridden called after the default properties are set + } + + public T getProps(String domain) { + T props = get(domain); + if (props == null) { + throw new InvalidConfigurationException("Domain " + domain + " id not defined"); + } + return props; + } + + // Static builder strategy + + public static < + T extends GenericAsyncProps

, + P, + X extends GenericAsyncPropsDomainProperties, + R extends GenericAsyncPropsDomain> + AsyncPropsDomainBuilder builder(Class

propsClass, + Class asyncPropsDomainClass, + Constructor returnType) { + return new AsyncPropsDomainBuilder<>(propsClass, asyncPropsDomainClass, returnType); + } + + public static class AsyncPropsDomainBuilder< + T extends GenericAsyncProps

, + P, + X extends GenericAsyncPropsDomainProperties, + R extends GenericAsyncPropsDomain> { + private final Class

propsClass; + private final Class asyncPropsDomainClass; + private final Constructor returnType; + private String defaultAppName; + private final HashMap domains = new HashMap<>(); + private P defaultProperties; + private SecretFiller

secretFiller; + + public AsyncPropsDomainBuilder(Class

propsClass, Class asyncPropsDomainClass, + Constructor returnType) { + this.propsClass = propsClass; + this.asyncPropsDomainClass = asyncPropsDomainClass; + this.returnType = returnType; + } + + public AsyncPropsDomainBuilder withDefaultProperties(P defaultProperties) { + this.defaultProperties = defaultProperties; + return this; + } + + + public AsyncPropsDomainBuilder withDefaultAppName(String defaultAppName) { + this.defaultAppName = defaultAppName; + return this; + } + + + public AsyncPropsDomainBuilder withSecretFiller(SecretFiller

secretFiller) { + this.secretFiller = secretFiller; + return this; + } + + public AsyncPropsDomainBuilder withDomain(String domain, T props) { + domains.put(domain, props); + return this; + } + + @SneakyThrows + public R build() { + X domainProperties = instantiate(asyncPropsDomainClass, domains); + if (defaultProperties == null) { + defaultProperties = instantiate(propsClass); + } + return returnType.newInstance(defaultAppName, defaultProperties, domainProperties, secretFiller); + } + + @SneakyThrows + private static X instantiate(Class xClass) { + return xClass.getDeclaredConstructor().newInstance(); + } + + @SneakyThrows + private static X instantiate(Class xClass, Map arg) { + return xClass.getDeclaredConstructor(Map.class).newInstance(arg); + } + + } + + public interface SecretFiller

{ + void fillWithSecret(String domain, GenericAsyncProps

props); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + GenericAsyncPropsDomain that = (GenericAsyncPropsDomain) o; + return Objects.equals(asyncPropsClass, that.asyncPropsClass) && Objects.equals(propsClass, that.propsClass); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), asyncPropsClass, propsClass); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainProperties.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainProperties.java new file mode 100644 index 00000000..c9a784f5 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainProperties.java @@ -0,0 +1,47 @@ +package org.reactivecommons.async.starter.props; + +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; + +import java.util.HashMap; +import java.util.Map; + +@Getter +@Setter +public class GenericAsyncPropsDomainProperties, P> extends HashMap { + + public GenericAsyncPropsDomainProperties(Map m) { + super(m); + } + + public GenericAsyncPropsDomainProperties() { + } + + public static , + P, + X extends GenericAsyncPropsDomainProperties> AsyncPropsDomainPropertiesBuilder + builder(Class returnType) { + return new AsyncPropsDomainPropertiesBuilder<>(returnType); + } + + public static class AsyncPropsDomainPropertiesBuilder, P, + X extends GenericAsyncPropsDomainProperties> { + private final Map domains = new HashMap<>(); + private final Class returnType; + + public AsyncPropsDomainPropertiesBuilder(Class returnType) { + this.returnType = returnType; + } + + public AsyncPropsDomainPropertiesBuilder withDomain(String domain, T props) { + domains.put(domain, props); + return this; + } + + @SneakyThrows + public X build() { + return returnType.getDeclaredConstructor(Map.class).newInstance(domains); + } + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfig.java new file mode 100644 index 00000000..e1654baf --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfig.java @@ -0,0 +1,28 @@ +package org.reactivecommons.async.starter.senders; + +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +@Log +@Configuration +@RequiredArgsConstructor +@Import(ReactiveCommonsConfig.class) +public class DirectAsyncGatewayConfig { + + @Bean + public DirectAsyncGateway genericDirectAsyncGateway(ConnectionManager manager) { + ConcurrentMap directAsyncGateways = new ConcurrentHashMap<>(); + manager.forDomain((domain, provider) -> directAsyncGateways.put(domain, + provider.getDirectAsyncGateway())); + return new GenericDirectAsyncGateway(directAsyncGateways); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/EventBusConfig.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/EventBusConfig.java new file mode 100644 index 00000000..b2b69e0b --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/EventBusConfig.java @@ -0,0 +1,23 @@ +package org.reactivecommons.async.starter.senders; + +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +@Configuration +@Import(ReactiveCommonsConfig.class) +public class EventBusConfig { + + @Bean + public DomainEventBus genericDomainEventBus(ConnectionManager manager) { + ConcurrentMap domainEventBuses = new ConcurrentHashMap<>(); + manager.forDomain((domain, provider) -> domainEventBuses.put(domain, provider.getDomainBus())); + return new GenericDomainEventBus(domainEventBuses); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGateway.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGateway.java new file mode 100644 index 00000000..67acbf81 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGateway.java @@ -0,0 +1,83 @@ +package org.reactivecommons.async.starter.senders; + +import io.cloudevents.CloudEvent; +import lombok.AllArgsConstructor; +import org.reactivecommons.api.domain.Command; +import org.reactivecommons.async.api.AsyncQuery; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.api.From; +import reactor.core.publisher.Mono; + +import java.util.concurrent.ConcurrentMap; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@AllArgsConstructor +public class GenericDirectAsyncGateway implements DirectAsyncGateway { + private final ConcurrentMap directAsyncGateways; + + @Override + public Mono sendCommand(Command command, String targetName) { + return sendCommand(command, targetName, DEFAULT_DOMAIN); + } + + @Override + public Mono sendCommand(Command command, String targetName, long delayMillis) { + return sendCommand(command, targetName, delayMillis, DEFAULT_DOMAIN); + } + + @Override + public Mono sendCommand(Command command, String targetName, String domain) { + return directAsyncGateways.get(domain).sendCommand(command, targetName); + } + + @Override + public Mono sendCommand(Command command, String targetName, long delayMillis, String domain) { + return directAsyncGateways.get(domain).sendCommand(command, targetName, delayMillis); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName) { + return sendCommand(command, targetName, DEFAULT_DOMAIN); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, long delayMillis) { + return sendCommand(command, targetName, delayMillis, DEFAULT_DOMAIN); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, String domain) { + return directAsyncGateways.get(domain).sendCommand(command, targetName); + } + + @Override + public Mono sendCommand(CloudEvent command, String targetName, long delayMillis, String domain) { + return directAsyncGateways.get(domain).sendCommand(command, targetName, delayMillis); + } + + @Override + public Mono requestReply(AsyncQuery query, String targetName, Class type) { + return requestReply(query, targetName, type, DEFAULT_DOMAIN); + } + + @Override + public Mono requestReply(AsyncQuery query, String targetName, Class type, String domain) { + return directAsyncGateways.get(domain).requestReply(query, targetName, type); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type) { + return requestReply(query, targetName, type, DEFAULT_DOMAIN); + } + + @Override + public Mono requestReply(CloudEvent query, String targetName, Class type, String domain) { + return directAsyncGateways.get(domain).requestReply(query, targetName, type); + } + + @Override + public Mono reply(T response, From from) { + return directAsyncGateways.get(DEFAULT_DOMAIN).reply(response, from); + } +} diff --git a/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDomainEventBus.java b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDomainEventBus.java new file mode 100644 index 00000000..fd7fbe25 --- /dev/null +++ b/starters/async-commons-starter/src/main/java/org/reactivecommons/async/starter/senders/GenericDomainEventBus.java @@ -0,0 +1,63 @@ +package org.reactivecommons.async.starter.senders; + +import io.cloudevents.CloudEvent; +import lombok.RequiredArgsConstructor; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.api.domain.RawMessage; +import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; + +import java.util.concurrent.ConcurrentMap; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@RequiredArgsConstructor +public class GenericDomainEventBus implements DomainEventBus { + private static final String DOMAIN_NOT_FOUND = "Domain not found: "; + private final ConcurrentMap domainEventBuses; + + + @Override + public Publisher emit(DomainEvent event) { + return emit(DEFAULT_DOMAIN, event); + } + + @Override + public Publisher emit(String domain, DomainEvent event) { + DomainEventBus domainEventBus = domainEventBuses.get(domain); + if (domainEventBus == null) { + return Mono.error(() -> new InvalidConfigurationException(DOMAIN_NOT_FOUND + domain)); + } + return domainEventBus.emit(event); + } + + @Override + public Publisher emit(CloudEvent event) { + return emit(DEFAULT_DOMAIN, event); + } + + @Override + public Publisher emit(String domain, CloudEvent event) { + DomainEventBus domainEventBus = domainEventBuses.get(domain); + if (domainEventBus == null) { + return Mono.error(() -> new InvalidConfigurationException(DOMAIN_NOT_FOUND + domain)); + } + return domainEventBus.emit(event); + } + + @Override + public Publisher emit(RawMessage event) { + return emit(DEFAULT_DOMAIN, event); + } + + @Override + public Publisher emit(String domain, RawMessage event) { + DomainEventBus domainEventBus = domainEventBuses.get(domain); + if (domainEventBus == null) { + return Mono.error(() -> new InvalidConfigurationException(DOMAIN_NOT_FOUND + domain)); + } + return domainEventBus.emit(event); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfigTest.java new file mode 100644 index 00000000..34722ca4 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/ReactiveCommonsConfigTest.java @@ -0,0 +1,30 @@ +package org.reactivecommons.async.starter.config; + + +import org.junit.jupiter.api.Test; +import org.mockito.Spy; +import org.reactivecommons.async.starter.impl.mybroker.MyBrokerConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest(classes = { + MyBrokerConfig.class, + ReactiveCommonsConfig.class +}) +class ReactiveCommonsConfigTest { + @Spy + @Autowired + private ApplicationContext context; + + @Test + void shouldCreateConnectionManager() { + // Arrange + // Act + ConnectionManager manager = context.getBean(ConnectionManager.class); + // Assert + assertNotNull(manager); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicatorTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicatorTest.java new file mode 100644 index 00000000..b616dd74 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/config/health/ReactiveCommonsHealthIndicatorTest.java @@ -0,0 +1,74 @@ +package org.reactivecommons.async.starter.config.health; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.springframework.boot.actuate.health.Health; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.DOMAIN; +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.VERSION; + +@ExtendWith(MockitoExtension.class) +class ReactiveCommonsHealthIndicatorTest { + public static final String OTHER = "other"; + @Mock + private BrokerProvider brokerProvider; + @Mock + private BrokerProvider brokerProvider2; + private ReactiveCommonsHealthIndicator healthIndicator; + + @BeforeEach + void setUp() { + ConnectionManager connectionManager = new ConnectionManager(); + connectionManager.addDomain(DEFAULT_DOMAIN, brokerProvider); + connectionManager.addDomain(OTHER, brokerProvider2); + ReactiveCommonsHealthConfig healthConfig = new ReactiveCommonsHealthConfig(); + healthIndicator = healthConfig.reactiveCommonsHealthIndicator(connectionManager); + } + + @Test + void shouldBeUp() { + // Arrange + when(brokerProvider.healthCheck()).thenReturn(Mono.just(RCHealth.builder().up() + .withDetail(DOMAIN, DEFAULT_DOMAIN) + .withDetail(VERSION, "123") + .build())); + when(brokerProvider2.healthCheck()).thenReturn(Mono.just(RCHealth.builder().up() + .withDetail(DOMAIN, OTHER) + .withDetail(VERSION, "1234") + .build())); + // Act + Mono flow = healthIndicator.health(); + // Assert + StepVerifier.create(flow) + .expectNextMatches(health -> health.getStatus().toString().equals("UP")) + .verifyComplete(); + } + + @Test + void shouldBeDown() { + // Arrange + when(brokerProvider.healthCheck()).thenReturn(Mono.just(RCHealth.builder().down() + .withDetail(DOMAIN, DEFAULT_DOMAIN) + .withDetail(VERSION, "123") + .build())); + when(brokerProvider2.healthCheck()).thenReturn(Mono.just(RCHealth.builder().up() + .withDetail(DOMAIN, OTHER) + .withDetail(VERSION, "1234") + .build())); + // Act + Mono flow = healthIndicator.health(); + // Assert + StepVerifier.create(flow) + .expectNextMatches(health -> health.getStatus().toString().equals("DOWN")) + .verifyComplete(); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/impl/mybroker/MyBrokerConfig.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/impl/mybroker/MyBrokerConfig.java new file mode 100644 index 00000000..6fff5b03 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/impl/mybroker/MyBrokerConfig.java @@ -0,0 +1,29 @@ +package org.reactivecommons.async.starter.impl.mybroker; + +import org.reactivecommons.async.starter.mybroker.MyBrokerProviderFactory; +import org.reactivecommons.async.starter.mybroker.MyBrokerSecretFiller; +import org.reactivecommons.async.starter.mybroker.props.AsyncMyBrokerPropsDomainProperties; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncPropsDomain; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerConnProps; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +@EnableConfigurationProperties(AsyncMyBrokerPropsDomainProperties.class) +@Import({MyBrokerAsyncPropsDomain.class, MyBrokerProviderFactory.class}) +public class MyBrokerConfig { + + @Bean + public MyBrokerConnProps defaultMyBrokerConnProps() { + MyBrokerConnProps myBrokerConnProps = new MyBrokerConnProps(); + myBrokerConnProps.setHost("localhost"); + myBrokerConnProps.setPort("1234"); + return myBrokerConnProps; + } + + @Bean + public MyBrokerSecretFiller defaultMyBrokerSecretFiller() { + return (domain, props) -> { + }; + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfigTest.java new file mode 100644 index 00000000..986c0081 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/CommandsListenerConfigTest.java @@ -0,0 +1,40 @@ +package org.reactivecommons.async.starter.listeners; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; + +import static org.mockito.Mockito.verify; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class CommandsListenerConfigTest { + @Mock + private BrokerProvider provider; + @Mock + private HandlerResolver resolver; + + @BeforeEach + void setUp() { + ConnectionManager manager = new ConnectionManager(); + manager.addDomain(DEFAULT_DOMAIN, provider); + DomainHandlers handlers = new DomainHandlers(); + handlers.add(DEFAULT_DOMAIN, resolver); + new CommandsListenerConfig(manager, handlers); + } + + @Test + void shouldListen() { + // Arrange + // Act + // Assert + verify(provider).listenCommands(resolver); + } + +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/EventsListenerConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/EventsListenerConfigTest.java new file mode 100644 index 00000000..bfcab084 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/EventsListenerConfigTest.java @@ -0,0 +1,40 @@ +package org.reactivecommons.async.starter.listeners; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; + +import static org.mockito.Mockito.verify; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class EventsListenerConfigTest { + @Mock + private BrokerProvider provider; + @Mock + private HandlerResolver resolver; + + @BeforeEach + void setUp() { + ConnectionManager manager = new ConnectionManager(); + manager.addDomain(DEFAULT_DOMAIN, provider); + DomainHandlers handlers = new DomainHandlers(); + handlers.add(DEFAULT_DOMAIN, resolver); + new EventsListenerConfig(manager, handlers); + } + + @Test + void shouldListen() { + // Arrange + // Act + // Assert + verify(provider).listenDomainEvents(resolver); + } + +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfigTest.java new file mode 100644 index 00000000..ece9dbd6 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/NotificationEventsListenerConfigTest.java @@ -0,0 +1,40 @@ +package org.reactivecommons.async.starter.listeners; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; + +import static org.mockito.Mockito.verify; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class NotificationEventsListenerConfigTest { + @Mock + private BrokerProvider provider; + @Mock + private HandlerResolver resolver; + + @BeforeEach + void setUp() { + ConnectionManager manager = new ConnectionManager(); + manager.addDomain(DEFAULT_DOMAIN, provider); + DomainHandlers handlers = new DomainHandlers(); + handlers.add(DEFAULT_DOMAIN, resolver); + new NotificationEventsListenerConfig(manager, handlers); + } + + @Test + void shouldListen() { + // Arrange + // Act + // Assert + verify(provider).listenNotificationEvents(resolver); + } + +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfigTest.java new file mode 100644 index 00000000..d327c9a1 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/listeners/QueriesListenerConfigTest.java @@ -0,0 +1,40 @@ +package org.reactivecommons.async.starter.listeners; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.DomainHandlers; + +import static org.mockito.Mockito.verify; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class QueriesListenerConfigTest { + @Mock + private BrokerProvider provider; + @Mock + private HandlerResolver resolver; + + @BeforeEach + void setUp() { + ConnectionManager manager = new ConnectionManager(); + manager.addDomain(DEFAULT_DOMAIN, provider); + DomainHandlers handlers = new DomainHandlers(); + handlers.add(DEFAULT_DOMAIN, resolver); + new QueriesListenerConfig(manager, handlers); + } + + @Test + void shouldListen() { + // Arrange + // Act + // Assert + verify(provider).listenQueries(resolver); + } + +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProvider.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProvider.java new file mode 100644 index 00000000..28a86de0 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProvider.java @@ -0,0 +1,63 @@ +package org.reactivecommons.async.starter.mybroker; + +import lombok.RequiredArgsConstructor; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.reactivecommons.async.starter.config.health.RCHealth; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncProps; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +public class MyBrokerProvider implements BrokerProvider { + private final String domain; + private final MyBrokerAsyncProps props; + private final DiscardProvider discardProvider; + + @Override + public MyBrokerAsyncProps getProps() { + return null; + } + + @Override + public DomainEventBus getDomainBus() { + return null; + } + + @Override + public DirectAsyncGateway getDirectAsyncGateway() { + return null; + } + + @Override + public void listenDomainEvents(HandlerResolver resolver) { + // for testing purposes + } + + @Override + public void listenNotificationEvents(HandlerResolver resolver) { + // for testing purposes + } + + @Override + public void listenCommands(HandlerResolver resolver) { + // for testing purposes + } + + @Override + public void listenQueries(HandlerResolver resolver) { + // for testing purposes + } + + @Override + public void listenReplies() { + // for testing purposes + } + + @Override + public Mono healthCheck() { + return null; + } +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProviderFactory.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProviderFactory.java new file mode 100644 index 00000000..edecf27e --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerProviderFactory.java @@ -0,0 +1,27 @@ +package org.reactivecommons.async.starter.mybroker; + +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncProps; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Mono; + +@Service("mybroker") +public class MyBrokerProviderFactory implements BrokerProviderFactory { + + @Override + public String getBrokerType() { + return "mybroker"; + } + + @Override + public DiscardProvider getDiscardProvider(MyBrokerAsyncProps props) { + return () -> message -> Mono.empty(); + } + + @Override + public BrokerProvider getProvider(String domain, MyBrokerAsyncProps props, DiscardProvider discardProvider) { + return new MyBrokerProvider(domain, props, discardProvider); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerSecretFiller.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerSecretFiller.java new file mode 100644 index 00000000..29a8a841 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/MyBrokerSecretFiller.java @@ -0,0 +1,7 @@ +package org.reactivecommons.async.starter.mybroker; + +import org.reactivecommons.async.starter.mybroker.props.MyBrokerConnProps; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; + +public interface MyBrokerSecretFiller extends GenericAsyncPropsDomain.SecretFiller { +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/AsyncMyBrokerPropsDomainProperties.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/AsyncMyBrokerPropsDomainProperties.java new file mode 100644 index 00000000..792ef21e --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/AsyncMyBrokerPropsDomainProperties.java @@ -0,0 +1,23 @@ +package org.reactivecommons.async.starter.mybroker.props; + +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomainProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Map; + +@ConfigurationProperties(prefix = "my.broker") +public class AsyncMyBrokerPropsDomainProperties + extends GenericAsyncPropsDomainProperties { + + public AsyncMyBrokerPropsDomainProperties() { + } + + public AsyncMyBrokerPropsDomainProperties(Map m) { + super(m); + } + + public static AsyncPropsDomainPropertiesBuilder builder() { + return GenericAsyncPropsDomainProperties.builder(AsyncMyBrokerPropsDomainProperties.class); + } +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncProps.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncProps.java new file mode 100644 index 00000000..0b5666ef --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncProps.java @@ -0,0 +1,22 @@ +package org.reactivecommons.async.starter.mybroker.props; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; +import org.reactivecommons.async.starter.props.GenericAsyncProps; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public class MyBrokerAsyncProps extends GenericAsyncProps { + private MyBrokerConnProps connectionProperties; + @Builder.Default + private String brokerType = "mybroker"; + private boolean enabled; + private boolean useDiscardNotifierPerDomain; +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncPropsDomain.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncPropsDomain.java new file mode 100644 index 00000000..fa348806 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerAsyncPropsDomain.java @@ -0,0 +1,20 @@ +package org.reactivecommons.async.starter.mybroker.props; + +import lombok.Getter; +import lombok.Setter; +import org.reactivecommons.async.starter.mybroker.MyBrokerSecretFiller; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; +import org.springframework.beans.factory.annotation.Value; + +@Getter +@Setter +public class MyBrokerAsyncPropsDomain extends GenericAsyncPropsDomain { + + public MyBrokerAsyncPropsDomain(@Value("${spring.application.name}") String defaultAppName, + MyBrokerConnProps defaultRabbitProperties, + AsyncMyBrokerPropsDomainProperties configured, + MyBrokerSecretFiller secretFiller) { + super(defaultAppName, defaultRabbitProperties, configured, secretFiller, MyBrokerAsyncProps.class, + MyBrokerConnProps.class); + } +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerConnProps.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerConnProps.java new file mode 100644 index 00000000..29ba7345 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/mybroker/props/MyBrokerConnProps.java @@ -0,0 +1,9 @@ +package org.reactivecommons.async.starter.mybroker.props; + +import lombok.Data; + +@Data +public class MyBrokerConnProps { + private String host; + private String port; +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainTest.java new file mode 100644 index 00000000..b69b8fde --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/props/GenericAsyncPropsDomainTest.java @@ -0,0 +1,101 @@ +package org.reactivecommons.async.starter.props; + +import org.junit.jupiter.api.Test; +import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; +import org.reactivecommons.async.starter.mybroker.MyBrokerSecretFiller; +import org.reactivecommons.async.starter.mybroker.props.AsyncMyBrokerPropsDomainProperties; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncProps; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerAsyncPropsDomain; +import org.reactivecommons.async.starter.mybroker.props.MyBrokerConnProps; + +import java.lang.reflect.Constructor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@SuppressWarnings("unchecked") +class GenericAsyncPropsDomainTest { + + public static final String OTHER = "other"; + + @Test + void shouldCreateProps() { + // Arrange + String defaultAppName = "sample"; + MyBrokerConnProps defaultMyBrokerProps = new MyBrokerConnProps(); + AsyncMyBrokerPropsDomainProperties configured = new AsyncMyBrokerPropsDomainProperties(); + MyBrokerAsyncProps other = new MyBrokerAsyncProps(); + other.setAppName(OTHER); + configured.put(OTHER, other); + MyBrokerSecretFiller secretFiller = (domain, props) -> { + }; + MyBrokerAsyncPropsDomain propsDomain = new MyBrokerAsyncPropsDomain( + defaultAppName, defaultMyBrokerProps, configured, secretFiller + ); + // Act + MyBrokerAsyncProps props = propsDomain.getProps(DEFAULT_DOMAIN); + MyBrokerAsyncProps otherProps = propsDomain.getProps(OTHER); + // Assert + assertEquals("sample", props.getAppName()); + assertEquals(OTHER, otherProps.getAppName()); + assertThrows(InvalidConfigurationException.class, () -> propsDomain.getProps("non-existing-domain")); + } + + @Test + void shouldCreatePropsWithDefaultConnectionProperties() { + // Arrange + String defaultAppName = "sample"; + MyBrokerConnProps defaultMyBrokerProps = new MyBrokerConnProps(); + MyBrokerAsyncProps propsConfigured = new MyBrokerAsyncProps(); + MyBrokerAsyncPropsDomain propsDomain = MyBrokerAsyncPropsDomain.builder(MyBrokerConnProps.class, + AsyncMyBrokerPropsDomainProperties.class, + (Constructor) MyBrokerAsyncPropsDomain.class + .getDeclaredConstructors()[0] + ) + .withDefaultAppName(defaultAppName) + .withDefaultProperties(defaultMyBrokerProps) + .withDomain(DEFAULT_DOMAIN, propsConfigured) + .withSecretFiller(null) + .build(); + // Act + MyBrokerAsyncProps props = propsDomain.getProps(DEFAULT_DOMAIN); + // Assert + assertEquals("sample", props.getAppName()); + assertEquals(defaultMyBrokerProps, props.getConnectionProperties()); + } + + @Test + void shouldFailCreatePropsWhenAppNameIsNullOrEmpty() { + // Arrange + MyBrokerConnProps defaultMyBrokerProps = new MyBrokerConnProps(); + AsyncMyBrokerPropsDomainProperties configured = new AsyncMyBrokerPropsDomainProperties(); + MyBrokerSecretFiller secretFiller = (domain, props) -> { + }; + // Assert + assertThrows(InvalidConfigurationException.class, + // Act + () -> new MyBrokerAsyncPropsDomain(null, defaultMyBrokerProps, configured, secretFiller)); + assertThrows(InvalidConfigurationException.class, + // Act + () -> new MyBrokerAsyncPropsDomain("", defaultMyBrokerProps, configured, secretFiller)); + } + + @Test + void shouldFailCreatePropsWhenDefaultConnectionPropertiesAreNul() { + // Arrange + String defaultAppName = "sample"; + AsyncMyBrokerPropsDomainProperties configured = AsyncMyBrokerPropsDomainProperties + .builder(AsyncMyBrokerPropsDomainProperties.class) + .withDomain(OTHER, new MyBrokerAsyncProps()) + .build(); + MyBrokerSecretFiller secretFiller = (domain, props) -> { + }; + // Assert + assertThrows(InvalidConfigurationException.class, + // Act + () -> new MyBrokerAsyncPropsDomain(defaultAppName, null, configured, secretFiller)); + } + + +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfigTest.java new file mode 100644 index 00000000..506f619d --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/DirectAsyncGatewayConfigTest.java @@ -0,0 +1,48 @@ +package org.reactivecommons.async.starter.senders; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class DirectAsyncGatewayConfigTest { + @Mock + private DirectAsyncGateway domainEventBus; + @Mock + private BrokerProvider brokerProvider; + @Mock + private HandlerResolver resolver; + + private ConnectionManager manager; + private DirectAsyncGatewayConfig directAsyncGatewayConfig; + + @BeforeEach + void setUp() { + directAsyncGatewayConfig = new DirectAsyncGatewayConfig(); + manager = new ConnectionManager(); + manager.addDomain("domain", brokerProvider); + manager.addDomain("domain2", brokerProvider); + } + + @Test + void shouldCreateAllDomainEventBuses() { + // Arrange + when(brokerProvider.getDirectAsyncGateway()).thenReturn(domainEventBus); + // Act + DirectAsyncGateway genericDomainEventBus = directAsyncGatewayConfig.genericDirectAsyncGateway(manager); + // Assert + assertNotNull(genericDomainEventBus); + verify(brokerProvider, times(2)).getDirectAsyncGateway(); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/EventBusConfigTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/EventBusConfigTest.java new file mode 100644 index 00000000..1be56082 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/EventBusConfigTest.java @@ -0,0 +1,45 @@ +package org.reactivecommons.async.starter.senders; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.ConnectionManager; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class EventBusConfigTest { + @Mock + private DomainEventBus domainEventBus; + @Mock + private BrokerProvider brokerProvider; + + private ConnectionManager manager; + private EventBusConfig eventBusConfig; + + @BeforeEach + void setUp() { + eventBusConfig = new EventBusConfig(); + manager = new ConnectionManager(); + manager.addDomain("domain", brokerProvider); + manager.addDomain("domain2", brokerProvider); + } + + @Test + void shouldCreateAllDomainEventBuses() { + // Arrange + when(brokerProvider.getDomainBus()).thenReturn(domainEventBus); + // Act + DomainEventBus genericDomainEventBus = eventBusConfig.genericDomainEventBus(manager); + // Assert + assertNotNull(genericDomainEventBus); + verify(brokerProvider, times(2)).getDomainBus(); + } +} diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGatewayTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGatewayTest.java new file mode 100644 index 00000000..5b6ca9bd --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDirectAsyncGatewayTest.java @@ -0,0 +1,161 @@ +package org.reactivecommons.async.starter.senders; + +import io.cloudevents.CloudEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.Command; +import org.reactivecommons.async.api.AsyncQuery; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.api.From; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.concurrent.ConcurrentHashMap; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class GenericDirectAsyncGatewayTest { + + public static final String DOMAIN_2 = "domain2"; + + @Mock + private DirectAsyncGateway directAsyncGateway1; + + @Mock + private DirectAsyncGateway directAsyncGateway2; + + @Mock + private CloudEvent cloudEvent; + + @Mock + private Command command; + + @Mock + private AsyncQuery asyncQuery; + + @Mock + private From from; + + private final long delayMillis = 100L; + + private GenericDirectAsyncGateway genericDirectAsyncGateway; + + @BeforeEach + void setUp() { + ConcurrentHashMap directAsyncGateways = new ConcurrentHashMap<>(); + directAsyncGateways.put(DEFAULT_DOMAIN, directAsyncGateway1); + directAsyncGateways.put(DOMAIN_2, directAsyncGateway2); + genericDirectAsyncGateway = new GenericDirectAsyncGateway(directAsyncGateways); + } + + @Test + void shouldSendCommandWithDefaultDomain() { + when(directAsyncGateway1.sendCommand(command, "target")).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(command, "target"); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway1).sendCommand(command, "target"); + } + + @Test + void shouldSendCommandWithDefaultDomainWithDelay() { + when(directAsyncGateway1.sendCommand(command, "target", delayMillis)).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(command, "target", delayMillis); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway1).sendCommand(command, "target", delayMillis); + } + + @Test + void shouldSendCommandWithSpecificDomain() { + when(directAsyncGateway2.sendCommand(command, "target")).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(command, "target", DOMAIN_2); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway2).sendCommand(command, "target"); + } + + @Test + void shouldSendCommandWithSpecificDomainWithDelay() { + when(directAsyncGateway2.sendCommand(command, "target", delayMillis)).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(command, "target", delayMillis, DOMAIN_2); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway2).sendCommand(command, "target", delayMillis); + } + + @Test + void shouldSendCloudEventWithDefaultDomain() { + when(directAsyncGateway1.sendCommand(cloudEvent, "target")).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(cloudEvent, "target"); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway1).sendCommand(cloudEvent, "target"); + } + + @Test + void shouldSendCloudEventWithDefaultDomainWithDelay() { + when(directAsyncGateway1.sendCommand(cloudEvent, "target", delayMillis)).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(cloudEvent, "target", delayMillis); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway1).sendCommand(cloudEvent, "target", delayMillis); + } + + @Test + void shouldSendCloudEventWithSpecificDomain() { + when(directAsyncGateway2.sendCommand(cloudEvent, "target")).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(cloudEvent, "target", DOMAIN_2); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway2).sendCommand(cloudEvent, "target"); + } + + @Test + void shouldSendCloudEventWithSpecificDomainWithDelay() { + when(directAsyncGateway2.sendCommand(cloudEvent, "target", delayMillis)).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.sendCommand(cloudEvent, "target", delayMillis, DOMAIN_2); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway2).sendCommand(cloudEvent, "target", delayMillis); + } + + @Test + void shouldRequestReplyWithDefaultDomain() { + when(directAsyncGateway1.requestReply(asyncQuery, "target", String.class)).thenReturn(Mono.just("response")); + Mono flow = genericDirectAsyncGateway.requestReply(asyncQuery, "target", String.class); + StepVerifier.create(flow).expectNext("response").verifyComplete(); + verify(directAsyncGateway1).requestReply(asyncQuery, "target", String.class); + } + + + @Test + void shouldRequestReplyWithDefaultDomainCloudEvent() { + when(directAsyncGateway1.requestReply(cloudEvent, "target", CloudEvent.class)).thenReturn(Mono.just(cloudEvent)); + Mono flow = genericDirectAsyncGateway.requestReply(cloudEvent, "target", CloudEvent.class); + StepVerifier.create(flow).expectNext(cloudEvent).verifyComplete(); + verify(directAsyncGateway1).requestReply(cloudEvent, "target", CloudEvent.class); + } + + @Test + void shouldRequestReplyWithSpecificDomain() { + when(directAsyncGateway2.requestReply(asyncQuery, "target", String.class)).thenReturn(Mono.just("response")); + Mono flow = genericDirectAsyncGateway.requestReply(asyncQuery, "target", String.class, DOMAIN_2); + StepVerifier.create(flow).expectNext("response").verifyComplete(); + verify(directAsyncGateway2).requestReply(asyncQuery, "target", String.class); + } + + @Test + void shouldRequestReplyWithSpecificDomainCloudEvent() { + when(directAsyncGateway2.requestReply(cloudEvent, "target", CloudEvent.class)).thenReturn(Mono.just(cloudEvent)); + Mono flow = genericDirectAsyncGateway.requestReply(cloudEvent, "target", CloudEvent.class, DOMAIN_2); + StepVerifier.create(flow).expectNext(cloudEvent).verifyComplete(); + verify(directAsyncGateway2).requestReply(cloudEvent, "target", CloudEvent.class); + } + + @Test + void shouldReplyWithDefaultDomain() { + when(directAsyncGateway1.reply("response", from)).thenReturn(Mono.empty()); + Mono flow = genericDirectAsyncGateway.reply("response", from); + StepVerifier.create(flow).verifyComplete(); + verify(directAsyncGateway1).reply("response", from); + } +} \ No newline at end of file diff --git a/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDomainEventBusTest.java b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDomainEventBusTest.java new file mode 100644 index 00000000..43d62071 --- /dev/null +++ b/starters/async-commons-starter/src/test/java/org/reactivecommons/async/starter/senders/GenericDomainEventBusTest.java @@ -0,0 +1,137 @@ +package org.reactivecommons.async.starter.senders; + +import io.cloudevents.CloudEvent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEvent; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.commons.communications.Message; +import org.reactivecommons.async.starter.exceptions.InvalidConfigurationException; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.concurrent.ConcurrentHashMap; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class GenericDomainEventBusTest { + public static final String DOMAIN_2 = "domain2"; + @Mock + private DomainEventBus domainEventBus1; + @Mock + private DomainEventBus domainEventBus2; + @Mock + private CloudEvent cloudEvent; + @Mock + private Message rawMessage; + @Mock + private DomainEvent domainEvent; + private GenericDomainEventBus genericDomainEventBus; + + @BeforeEach + void setUp() { + ConcurrentHashMap domainEventBuses = new ConcurrentHashMap<>(); + domainEventBuses.put(DEFAULT_DOMAIN, domainEventBus1); + domainEventBuses.put(DOMAIN_2, domainEventBus2); + genericDomainEventBus = new GenericDomainEventBus(domainEventBuses); + } + + @Test + void shouldEmitWithDefaultDomain() { + // Arrange + when(domainEventBus1.emit(domainEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(genericDomainEventBus.emit(domainEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + verify(domainEventBus1).emit(domainEvent); + } + + @Test + void shouldEmitCloudEventWithDefaultDomain() { + // Arrange + when(domainEventBus1.emit(cloudEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(genericDomainEventBus.emit(cloudEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + verify(domainEventBus1).emit(cloudEvent); + } + + @Test + void shouldEmitWithSpecificDomain() { + // Arrange + when(domainEventBus2.emit(domainEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(genericDomainEventBus.emit(DOMAIN_2, domainEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + verify(domainEventBus2).emit(domainEvent); + } + + @Test + void shouldEmitCloudEventWithSpecificDomain() { + // Arrange + when(domainEventBus2.emit(cloudEvent)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(genericDomainEventBus.emit(DOMAIN_2, cloudEvent)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + verify(domainEventBus2).emit(cloudEvent); + } + + @Test + void shouldFailWhenNoDomainFound() { + // Arrange + // Act + Mono flow = Mono.from(genericDomainEventBus.emit("another", domainEvent)); + // Assert + StepVerifier.create(flow) + .expectError(InvalidConfigurationException.class) + .verify(); + } + + @Test + void shouldFailWhenNoDomainFoundWithCloudEvent() { + // Arrange + // Act + Mono flow = Mono.from(genericDomainEventBus.emit("another", cloudEvent)); + // Assert + StepVerifier.create(flow) + .expectError(InvalidConfigurationException.class) + .verify(); + } + + @Test + void shouldEmitRawEventWithSpecificDomain() { + // Arrange + when(domainEventBus2.emit(rawMessage)).thenReturn(Mono.empty()); + // Act + Mono flow = Mono.from(genericDomainEventBus.emit(DOMAIN_2, rawMessage)); + // Assert + StepVerifier.create(flow) + .verifyComplete(); + verify(domainEventBus2).emit(rawMessage); + } + + @Test + void shouldFailWhenNoDomainFoundEmittingRawEvent() { + // Arrange + // Act + Mono flow = Mono.from(genericDomainEventBus.emit("another", rawMessage)); + // Assert + StepVerifier.create(flow) + .expectError(InvalidConfigurationException.class) + .verify(); + } +} diff --git a/starters/async-commons-starter/src/test/resources/application.yaml b/starters/async-commons-starter/src/test/resources/application.yaml new file mode 100644 index 00000000..30c7fd10 --- /dev/null +++ b/starters/async-commons-starter/src/test/resources/application.yaml @@ -0,0 +1,7 @@ +spring: + application: + name: test-app +my: + broker: + app: + enabled: true \ No newline at end of file diff --git a/async/async-rabbit-starter-eda/async-commons-rabbit-starter-eda.gradle b/starters/async-kafka-starter/async-kafka-starter.gradle similarity index 67% rename from async/async-rabbit-starter-eda/async-commons-rabbit-starter-eda.gradle rename to starters/async-kafka-starter/async-kafka-starter.gradle index 7daa1162..49439d3c 100644 --- a/async/async-rabbit-starter-eda/async-commons-rabbit-starter-eda.gradle +++ b/starters/async-kafka-starter/async-kafka-starter.gradle @@ -1,10 +1,12 @@ ext { - artifactId = 'async-commons-rabbit-starter-eda' - artifactDescription = 'Async Commons Starter EDA' + artifactId = 'async-commons-kafka-starter' + artifactDescription = 'Async Commons Starter' } dependencies { - api project(':async-rabbit') + api project(':async-kafka') + api project(':async-commons-starter') + implementation 'org.apache.kafka:kafka-clients' compileOnly 'org.springframework.boot:spring-boot-starter' compileOnly 'org.springframework.boot:spring-boot-starter-actuator' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProvider.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProvider.java new file mode 100644 index 00000000..3a022754 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProvider.java @@ -0,0 +1,106 @@ +package org.reactivecommons.async.kafka; + +import io.micrometer.core.instrument.MeterRegistry; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.reactivecommons.async.kafka.health.KafkaReactiveHealthIndicator; +import org.reactivecommons.async.kafka.listeners.ApplicationEventListener; +import org.reactivecommons.async.kafka.listeners.ApplicationNotificationsListener; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.health.RCHealth; +import org.springframework.boot.ssl.SslBundles; +import reactor.core.publisher.Mono; + +@Getter +@RequiredArgsConstructor +public class KafkaBrokerProvider implements BrokerProvider { + private final String domain; + private final AsyncKafkaProps props; + private final ReactiveReplyRouter router; + private final KafkaJacksonMessageConverter converter; + private final MeterRegistry meterRegistry; + private final CustomReporter errorReporter; + private final KafkaReactiveHealthIndicator healthIndicator; + private final ReactiveMessageListener receiver; + private final ReactiveMessageSender sender; + private final DiscardNotifier discardNotifier; + private final TopologyCreator topologyCreator; + private final KafkaCustomizations customizations; + private final SslBundles sslBundles; + + @Override + public DomainEventBus getDomainBus() { + return new KafkaDomainEventBus(sender); + } + + @Override + public DirectAsyncGateway getDirectAsyncGateway() { + return new KafkaDirectAsyncGateway(); + } + + @Override + public void listenDomainEvents(HandlerResolver resolver) { + if (!props.getDomain().isIgnoreThisListener() && !resolver.getEventListeners().isEmpty()) { + ApplicationEventListener eventListener = new ApplicationEventListener(receiver, + resolver, + converter, + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getMaxRetries(), + props.getRetryDelay(), + discardNotifier, + errorReporter, + props.getAppName()); + eventListener.startListener(topologyCreator); + } + } + + @Override + public void listenNotificationEvents(HandlerResolver resolver) { + if (!resolver.getNotificationListeners().isEmpty()) { + ApplicationNotificationsListener notificationEventListener = new ApplicationNotificationsListener(receiver, + resolver, + converter, + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getMaxRetries(), + props.getRetryDelay(), + discardNotifier, + errorReporter, + props.getAppName()); + notificationEventListener.startListener(topologyCreator); + } + } + + @Override + public void listenCommands(HandlerResolver resolver) { + // Implemented in the future + } + + @Override + public void listenQueries(HandlerResolver resolver) { + // May be implemented in the future + } + + @Override + public void listenReplies() { + // May be implemented in the future + } + + @Override + public Mono healthCheck() { + return healthIndicator.health(); + } +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactory.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactory.java new file mode 100644 index 00000000..d6ebece4 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactory.java @@ -0,0 +1,60 @@ +package org.reactivecommons.async.kafka; + +import io.micrometer.core.instrument.MeterRegistry; +import lombok.RequiredArgsConstructor; +import org.apache.kafka.clients.admin.AdminClient; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.reactivecommons.async.kafka.health.KafkaReactiveHealthIndicator; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.stereotype.Service; + +@Service("kafka") +@RequiredArgsConstructor +public class KafkaBrokerProviderFactory implements BrokerProviderFactory { + private final ReactiveReplyRouter router; + private final KafkaJacksonMessageConverter converter; + private final MeterRegistry meterRegistry; + private final CustomReporter errorReporter; + private final KafkaCustomizations customizations; + private final SslBundles sslBundles; + + @Override + public String getBrokerType() { + return "kafka"; + } + + @Override + public DiscardProvider getDiscardProvider(AsyncKafkaProps props) { + return new KafkaDiscardProvider(props, converter, customizations, sslBundles); + } + + @Override + public BrokerProvider getProvider(String domain, AsyncKafkaProps props, + DiscardProvider discardProvider) { + TopologyCreator creator = KafkaSetupUtils.createTopologyCreator(props, customizations, sslBundles); + ReactiveMessageSender sender = KafkaSetupUtils.createMessageSender(props, converter, creator, sslBundles); + ReactiveMessageListener listener = KafkaSetupUtils.createMessageListener(props, sslBundles); + AdminClient adminClient = AdminClient.create(props.getConnectionProperties().buildAdminProperties(sslBundles)); + KafkaReactiveHealthIndicator healthIndicator = new KafkaReactiveHealthIndicator(domain, adminClient); + DiscardNotifier discardNotifier; + if (props.isUseDiscardNotifierPerDomain()) { + discardNotifier = KafkaSetupUtils.createDiscardNotifier(sender, converter); + } else { + discardNotifier = discardProvider.get(); + } + return new KafkaBrokerProvider(domain, props, router, converter, meterRegistry, errorReporter, + healthIndicator, listener, sender, discardNotifier, creator, customizations, sslBundles); + } + +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaDiscardProvider.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaDiscardProvider.java new file mode 100644 index 00000000..7ac62293 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaDiscardProvider.java @@ -0,0 +1,34 @@ +package org.reactivecommons.async.kafka; + +import lombok.RequiredArgsConstructor; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.springframework.boot.ssl.SslBundles; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@RequiredArgsConstructor +public class KafkaDiscardProvider implements DiscardProvider { + private final AsyncKafkaProps props; + private final MessageConverter converter; + private final KafkaCustomizations customizations; + private final SslBundles sslBundles; + private final Map discardNotifier = new ConcurrentHashMap<>(); + + @Override + public DiscardNotifier get() { + return discardNotifier.computeIfAbsent(true, this::buildDiscardNotifier); + } + + private DiscardNotifier buildDiscardNotifier(boolean ignored) { + TopologyCreator creator = KafkaSetupUtils.createTopologyCreator(props, customizations, sslBundles); + ReactiveMessageSender sender = KafkaSetupUtils.createMessageSender(props, converter, creator, sslBundles); + return KafkaSetupUtils.createDiscardNotifier(sender, converter); + } +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaSetupUtils.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaSetupUtils.java new file mode 100644 index 00000000..2912d72b --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/KafkaSetupUtils.java @@ -0,0 +1,90 @@ +package org.reactivecommons.async.kafka; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.common.serialization.ByteArrayDeserializer; +import org.apache.kafka.common.serialization.ByteArraySerializer; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.reactivecommons.async.commons.DLQDiscardNotifier; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.springframework.boot.ssl.SslBundles; +import reactor.kafka.receiver.ReceiverOptions; +import reactor.kafka.sender.KafkaSender; +import reactor.kafka.sender.SenderOptions; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class KafkaSetupUtils { + + public static DiscardNotifier createDiscardNotifier(ReactiveMessageSender sender, MessageConverter converter) { + return new DLQDiscardNotifier(new KafkaDomainEventBus(sender), converter); + } + + + public static ReactiveMessageSender createMessageSender(AsyncKafkaProps config, + MessageConverter converter, + TopologyCreator topologyCreator, + SslBundles sslBundles) { + KafkaProperties props = config.getConnectionProperties(); + props.setClientId(config.getAppName()); + props.getProducer().setKeySerializer(StringSerializer.class); + props.getProducer().setValueSerializer(ByteArraySerializer.class); + SenderOptions senderOptions = SenderOptions.create(props.buildProducerProperties(sslBundles)); + KafkaSender kafkaSender = KafkaSender.create(senderOptions); + return new ReactiveMessageSender(kafkaSender, converter, topologyCreator); + } + + // Receiver + + public static ReactiveMessageListener createMessageListener(AsyncKafkaProps config, SslBundles sslBundles) { + KafkaProperties props = config.getConnectionProperties(); + props.getConsumer().setKeyDeserializer(StringDeserializer.class); + props.getConsumer().setValueDeserializer(ByteArrayDeserializer.class); + ReceiverOptions receiverOptions = ReceiverOptions.create( + props.buildConsumerProperties(sslBundles) + ); + return new ReactiveMessageListener(receiverOptions); + } + + // Shared + public static TopologyCreator createTopologyCreator(AsyncKafkaProps config, KafkaCustomizations customizations, + SslBundles sslBundles) { + AdminClient adminClient = AdminClient.create(config.getConnectionProperties().buildAdminProperties(sslBundles)); + return new TopologyCreator(adminClient, customizations, config.getCheckExistingTopics()); + } + + // Utilities + + public static KafkaProperties readPropsFromDotEnv(Path path) throws IOException { + String env = Files.readString(path); + String[] split = env.split("\n"); + KafkaProperties props = new KafkaProperties(); + Map properties = props.getProperties(); + for (String s : split) { + if (s.startsWith("#")) { + continue; + } + String[] split1 = s.split("=", 2); + properties.put(split1[0], split1[1]); + } + return props; + } + + public static String jassConfig(String username, String password) { + return String.format("org.apache.kafka.common.security.plain.PlainLoginModule required " + + "username=\"%s\" password=\"%s\";", username, password); + } +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaProperties.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaProperties.java new file mode 100644 index 00000000..e39782e5 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaProperties.java @@ -0,0 +1,8 @@ +package org.reactivecommons.async.kafka.config; + + +import org.reactivecommons.async.kafka.config.spring.KafkaPropertiesBase; + +public class KafkaProperties extends KafkaPropertiesBase { + +} \ No newline at end of file diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaPropertiesAutoConfig.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaPropertiesAutoConfig.java new file mode 100644 index 00000000..a80b8e75 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/KafkaPropertiesAutoConfig.java @@ -0,0 +1,12 @@ +package org.reactivecommons.async.kafka.config; + + +import org.reactivecommons.async.kafka.config.spring.KafkaPropertiesBase; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "spring.kafka") +public class KafkaPropertiesAutoConfig extends KafkaPropertiesBase { + public KafkaPropertiesAutoConfig() { +// put("bootstrap.servers", "localhost:9092"); + } +} \ No newline at end of file diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java new file mode 100644 index 00000000..26eefb03 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaProps.java @@ -0,0 +1,55 @@ +package org.reactivecommons.async.kafka.config.props; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.starter.props.GenericAsyncProps; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@SuperBuilder +public class AsyncKafkaProps extends GenericAsyncProps { + + @NestedConfigurationProperty + @Builder.Default + private KafkaProperties connectionProperties = new KafkaProperties(); + + @NestedConfigurationProperty + @Builder.Default + private DomainProps domain = new DomainProps(); + + /** + * -1 will be considered default value. + * When withDLQRetry is true, it will be retried 10 times. + * When withDLQRetry is false, it will be retried indefinitely. + */ + @Builder.Default + private Integer maxRetries = -1; + + @Builder.Default + private Integer retryDelay = 1000; + + @Builder.Default + private Boolean withDLQRetry = false; + @Builder.Default + private Boolean createTopology = true; + @Builder.Default + private Boolean checkExistingTopics = true; + + @Builder.Default + private boolean useDiscardNotifierPerDomain = false; + + @Builder.Default + private boolean enabled = true; + + @Builder.Default + private String brokerType = "kafka"; +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomain.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomain.java new file mode 100644 index 00000000..ea9f9496 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomain.java @@ -0,0 +1,34 @@ +package org.reactivecommons.async.kafka.config.props; + +import lombok.Getter; +import lombok.Setter; +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; +import org.springframework.beans.factory.annotation.Value; + +import java.lang.reflect.Constructor; + +@Getter +@Setter +public class AsyncKafkaPropsDomain extends GenericAsyncPropsDomain { + + public AsyncKafkaPropsDomain(@Value("${spring.application.name}") String defaultAppName, + KafkaProperties defaultKafkaProperties, + AsyncKafkaPropsDomainProperties configured, + KafkaSecretFiller kafkaSecretFiller) { + super(defaultAppName, defaultKafkaProperties, configured, kafkaSecretFiller, AsyncKafkaProps.class, + KafkaProperties.class); + } + + @SuppressWarnings("unchecked") + public static AsyncPropsDomainBuilder builder() { + return GenericAsyncPropsDomain.builder(KafkaProperties.class, + AsyncKafkaPropsDomainProperties.class, + (Constructor) AsyncKafkaPropsDomain.class.getDeclaredConstructors()[0]); + } + + public interface KafkaSecretFiller extends GenericAsyncPropsDomain.SecretFiller { + } + +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomainProperties.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomainProperties.java new file mode 100644 index 00000000..186910d3 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/AsyncKafkaPropsDomainProperties.java @@ -0,0 +1,23 @@ +package org.reactivecommons.async.kafka.config.props; + +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomainProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Map; + +@ConfigurationProperties(prefix = "reactive.commons.kafka") +public class AsyncKafkaPropsDomainProperties extends GenericAsyncPropsDomainProperties { + + public AsyncKafkaPropsDomainProperties(Map m) { + super(m); + } + + public AsyncKafkaPropsDomainProperties() { + } + + public static AsyncPropsDomainPropertiesBuilder builder() { + return GenericAsyncPropsDomainProperties.builder(AsyncKafkaPropsDomainProperties.class); + } +} diff --git a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/DomainProps.java similarity index 52% rename from async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java rename to starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/DomainProps.java index 4bc5fb1e..da41f4f6 100644 --- a/async/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/props/DomainProps.java @@ -1,11 +1,10 @@ -package org.reactivecommons.async.rabbit.config.props; +package org.reactivecommons.async.kafka.config.props; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.springframework.boot.context.properties.NestedConfigurationProperty; @Getter @Setter @@ -13,9 +12,10 @@ @NoArgsConstructor @Builder public class DomainProps { - - @NestedConfigurationProperty + // +// @NestedConfigurationProperty +// @Builder.Default +// private EventsProps events = new EventsProps(); @Builder.Default - private EventsProps events = new EventsProps(); - + private boolean ignoreThisListener = false; } diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/spring/KafkaPropertiesBase.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/spring/KafkaPropertiesBase.java new file mode 100644 index 00000000..036432b4 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/config/spring/KafkaPropertiesBase.java @@ -0,0 +1,1711 @@ +/* +Copied from https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaProperties.java + */ + +package org.reactivecommons.async.kafka.config.spring; + +import org.apache.kafka.clients.CommonClientConfigs; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.config.SslConfigs; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.boot.autoconfigure.kafka.SslBundleSslEngineFactory; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException; +import org.springframework.boot.convert.DurationUnit; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.core.io.Resource; +import org.springframework.util.CollectionUtils; +import org.springframework.util.unit.DataSize; + +import java.io.IOException; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Configuration properties for Spring for Apache Kafka. + *

+ * Users should refer to Kafka documentation for complete descriptions of these + * properties. + * + * @author Gary Russell + * @author Stephane Nicoll + * @author Artem Bilan + * @author Nakul Mishra + * @author Tomaz Fernandes + * @author Andy Wilkinson + * @author Scott Frederick + * @since 1.5.0 + */ +public class KafkaPropertiesBase { + + /** + * Comma-delimited list of host:port pairs to use for establishing the initial + * connections to the Kafka cluster. Applies to all components unless overridden. + */ + private List bootstrapServers = new ArrayList<>(Collections.singletonList("localhost:9092")); + + /** + * ID to pass to the server when making requests. Used for server-side logging. + */ + private String clientId; + + /** + * Additional properties, common to producers and consumers, used to configure the + * client. + */ + private final Map properties = new HashMap<>(); + + private final Consumer consumer = new Consumer(); + + private final Producer producer = new Producer(); + + private final Admin admin = new Admin(); + + private final Streams streams = new Streams(); + + private final Listener listener = new Listener(); + + private final Ssl ssl = new Ssl(); + + private final Jaas jaas = new Jaas(); + + private final Template template = new Template(); + + private final Security security = new Security(); + + private final Retry retry = new Retry(); + + public List getBootstrapServers() { + return this.bootstrapServers; + } + + public void setBootstrapServers(List bootstrapServers) { + this.bootstrapServers = bootstrapServers; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Map getProperties() { + return this.properties; + } + + public Consumer getConsumer() { + return this.consumer; + } + + public Producer getProducer() { + return this.producer; + } + + public Listener getListener() { + return this.listener; + } + + public Admin getAdmin() { + return this.admin; + } + + public Streams getStreams() { + return this.streams; + } + + public Ssl getSsl() { + return this.ssl; + } + + public Jaas getJaas() { + return this.jaas; + } + + public Template getTemplate() { + return this.template; + } + + public Security getSecurity() { + return this.security; + } + + public Retry getRetry() { + return this.retry; + } + + private Map buildCommonProperties(SslBundles sslBundles) { + Map properties = new HashMap<>(); + if (this.bootstrapServers != null) { + properties.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, this.bootstrapServers); + } + if (this.clientId != null) { + properties.put(CommonClientConfigs.CLIENT_ID_CONFIG, this.clientId); + } + properties.putAll(this.ssl.buildProperties(sslBundles)); + properties.putAll(this.security.buildProperties()); + if (!CollectionUtils.isEmpty(this.properties)) { + properties.putAll(this.properties); + } + return properties; + } + + /** + * Create an initial map of consumer properties from the state of this instance. + *

+ * This allows you to add additional properties, if necessary, and override the + * default {@code kafkaConsumerFactory} bean. + * + * @param sslBundles bundles providing SSL trust material + * @return the consumer properties initialized with the customizations defined on this + * instance + */ + public Map buildConsumerProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.consumer.buildProperties(sslBundles)); + return properties; + } + + /** + * Create an initial map of producer properties from the state of this instance. + *

+ * This allows you to add additional properties, if necessary, and override the + * default {@code kafkaProducerFactory} bean. + * + * @param sslBundles bundles providing SSL trust material + * @return the producer properties initialized with the customizations defined on this + * instance + */ + public Map buildProducerProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.producer.buildProperties(sslBundles)); + return properties; + } + + /** + * Create an initial map of admin properties from the state of this instance. + *

+ * This allows you to add additional properties, if necessary, and override the + * default {@code kafkaAdmin} bean. + * + * @param sslBundles bundles providing SSL trust material + * @return the admin properties initialized with the customizations defined on this + * instance + */ + public Map buildAdminProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.admin.buildProperties(sslBundles)); + return properties; + } + + /** + * Create an initial map of streams properties from the state of this instance. + *

+ * This allows you to add additional properties, if necessary. + * + * @param sslBundles bundles providing SSL trust material + * @return the streams properties initialized with the customizations defined on this + * instance + */ + public Map buildStreamsProperties(SslBundles sslBundles) { + Map properties = buildCommonProperties(sslBundles); + properties.putAll(this.streams.buildProperties(sslBundles)); + return properties; + } + + public static class Consumer { + + private final Ssl ssl = new Ssl(); + + private final Security security = new Security(); + + /** + * Frequency with which the consumer offsets are auto-committed to Kafka if + * 'enable.auto.commit' is set to true. + */ + private Duration autoCommitInterval; + + /** + * What to do when there is no initial offset in Kafka or if the current offset no + * longer exists on the server. + */ + private String autoOffsetReset; + + /** + * Comma-delimited list of host:port pairs to use for establishing the initial + * connections to the Kafka cluster. Overrides the global property, for consumers. + */ + private List bootstrapServers; + + /** + * ID to pass to the server when making requests. Used for server-side logging. + */ + private String clientId; + + /** + * Whether the consumer's offset is periodically committed in the background. + */ + private Boolean enableAutoCommit; + + /** + * Maximum amount of time the server blocks before answering the fetch request if + * there isn't sufficient data to immediately satisfy the requirement given by + * "fetch-min-size". + */ + private Duration fetchMaxWait; + + /** + * Minimum amount of data the server should return for a fetch request. + */ + private DataSize fetchMinSize; + + /** + * Unique string that identifies the consumer group to which this consumer + * belongs. + */ + private String groupId; + + /** + * Expected time between heartbeats to the consumer coordinator. + */ + private Duration heartbeatInterval; + + /** + * Isolation level for reading messages that have been written transactionally. + */ + private IsolationLevel isolationLevel = IsolationLevel.READ_UNCOMMITTED; + + /** + * Deserializer class for keys. + */ + private Class keyDeserializer = StringDeserializer.class; + + /** + * Deserializer class for values. + */ + private Class valueDeserializer = StringDeserializer.class; + + /** + * Maximum number of records returned in a single call to poll(). + */ + private Integer maxPollRecords; + + /** + * Additional consumer-specific properties used to configure the client. + */ + private final Map properties = new HashMap<>(); + + public Ssl getSsl() { + return this.ssl; + } + + public Security getSecurity() { + return this.security; + } + + public Duration getAutoCommitInterval() { + return this.autoCommitInterval; + } + + public void setAutoCommitInterval(Duration autoCommitInterval) { + this.autoCommitInterval = autoCommitInterval; + } + + public String getAutoOffsetReset() { + return this.autoOffsetReset; + } + + public void setAutoOffsetReset(String autoOffsetReset) { + this.autoOffsetReset = autoOffsetReset; + } + + public List getBootstrapServers() { + return this.bootstrapServers; + } + + public void setBootstrapServers(List bootstrapServers) { + this.bootstrapServers = bootstrapServers; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Boolean getEnableAutoCommit() { + return this.enableAutoCommit; + } + + public void setEnableAutoCommit(Boolean enableAutoCommit) { + this.enableAutoCommit = enableAutoCommit; + } + + public Duration getFetchMaxWait() { + return this.fetchMaxWait; + } + + public void setFetchMaxWait(Duration fetchMaxWait) { + this.fetchMaxWait = fetchMaxWait; + } + + public DataSize getFetchMinSize() { + return this.fetchMinSize; + } + + public void setFetchMinSize(DataSize fetchMinSize) { + this.fetchMinSize = fetchMinSize; + } + + public String getGroupId() { + return this.groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public Duration getHeartbeatInterval() { + return this.heartbeatInterval; + } + + public void setHeartbeatInterval(Duration heartbeatInterval) { + this.heartbeatInterval = heartbeatInterval; + } + + public IsolationLevel getIsolationLevel() { + return this.isolationLevel; + } + + public void setIsolationLevel(IsolationLevel isolationLevel) { + this.isolationLevel = isolationLevel; + } + + public Class getKeyDeserializer() { + return this.keyDeserializer; + } + + public void setKeyDeserializer(Class keyDeserializer) { + this.keyDeserializer = keyDeserializer; + } + + public Class getValueDeserializer() { + return this.valueDeserializer; + } + + public void setValueDeserializer(Class valueDeserializer) { + this.valueDeserializer = valueDeserializer; + } + + public Integer getMaxPollRecords() { + return this.maxPollRecords; + } + + public void setMaxPollRecords(Integer maxPollRecords) { + this.maxPollRecords = maxPollRecords; + } + + public Map getProperties() { + return this.properties; + } + + public Map buildProperties(SslBundles sslBundles) { + Properties properties = new Properties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getAutoCommitInterval) + .asInt(Duration::toMillis) + .to(properties.in(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG)); + map.from(this::getAutoOffsetReset).to(properties.in(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG)); + map.from(this::getBootstrapServers).to(properties.in(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG)); + map.from(this::getClientId).to(properties.in(ConsumerConfig.CLIENT_ID_CONFIG)); + map.from(this::getEnableAutoCommit).to(properties.in(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG)); + map.from(this::getFetchMaxWait) + .asInt(Duration::toMillis) + .to(properties.in(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG)); + map.from(this::getFetchMinSize) + .asInt(DataSize::toBytes) + .to(properties.in(ConsumerConfig.FETCH_MIN_BYTES_CONFIG)); + map.from(this::getGroupId).to(properties.in(ConsumerConfig.GROUP_ID_CONFIG)); + map.from(this::getHeartbeatInterval) + .asInt(Duration::toMillis) + .to(properties.in(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG)); + map.from(() -> getIsolationLevel().name().toLowerCase(Locale.ROOT)) + .to(properties.in(ConsumerConfig.ISOLATION_LEVEL_CONFIG)); + map.from(this::getKeyDeserializer).to(properties.in(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG)); + map.from(this::getValueDeserializer).to(properties.in(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG)); + map.from(this::getMaxPollRecords).to(properties.in(ConsumerConfig.MAX_POLL_RECORDS_CONFIG)); + return properties.with(this.ssl, this.security, this.properties, sslBundles); + } + + } + + public static class Producer { + + private final Ssl ssl = new Ssl(); + + private final Security security = new Security(); + + /** + * Number of acknowledgments the producer requires the leader to have received + * before considering a request complete. + */ + private String acks; + + /** + * Default batch size. A small batch size will make batching less common and may + * reduce throughput (a batch size of zero disables batching entirely). + */ + private DataSize batchSize; + + /** + * Comma-delimited list of host:port pairs to use for establishing the initial + * connections to the Kafka cluster. Overrides the global property, for producers. + */ + private List bootstrapServers; + + /** + * Total memory size the producer can use to buffer records waiting to be sent to + * the server. + */ + private DataSize bufferMemory; + + /** + * ID to pass to the server when making requests. Used for server-side logging. + */ + private String clientId; + + /** + * Compression type for all data generated by the producer. + */ + private String compressionType; + + /** + * Serializer class for keys. + */ + private Class keySerializer = StringSerializer.class; + + /** + * Serializer class for values. + */ + private Class valueSerializer = StringSerializer.class; + + /** + * When greater than zero, enables retrying of failed sends. + */ + private Integer retries; + + /** + * When non empty, enables transaction support for producer. + */ + private String transactionIdPrefix; + + /** + * Additional producer-specific properties used to configure the client. + */ + private final Map properties = new HashMap<>(); + + public Ssl getSsl() { + return this.ssl; + } + + public Security getSecurity() { + return this.security; + } + + public String getAcks() { + return this.acks; + } + + public void setAcks(String acks) { + this.acks = acks; + } + + public DataSize getBatchSize() { + return this.batchSize; + } + + public void setBatchSize(DataSize batchSize) { + this.batchSize = batchSize; + } + + public List getBootstrapServers() { + return this.bootstrapServers; + } + + public void setBootstrapServers(List bootstrapServers) { + this.bootstrapServers = bootstrapServers; + } + + public DataSize getBufferMemory() { + return this.bufferMemory; + } + + public void setBufferMemory(DataSize bufferMemory) { + this.bufferMemory = bufferMemory; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getCompressionType() { + return this.compressionType; + } + + public void setCompressionType(String compressionType) { + this.compressionType = compressionType; + } + + public Class getKeySerializer() { + return this.keySerializer; + } + + public void setKeySerializer(Class keySerializer) { + this.keySerializer = keySerializer; + } + + public Class getValueSerializer() { + return this.valueSerializer; + } + + public void setValueSerializer(Class valueSerializer) { + this.valueSerializer = valueSerializer; + } + + public Integer getRetries() { + return this.retries; + } + + public void setRetries(Integer retries) { + this.retries = retries; + } + + public String getTransactionIdPrefix() { + return this.transactionIdPrefix; + } + + public void setTransactionIdPrefix(String transactionIdPrefix) { + this.transactionIdPrefix = transactionIdPrefix; + } + + public Map getProperties() { + return this.properties; + } + + public Map buildProperties(SslBundles sslBundles) { + Properties properties = new Properties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getAcks).to(properties.in(ProducerConfig.ACKS_CONFIG)); + map.from(this::getBatchSize).asInt(DataSize::toBytes).to(properties.in(ProducerConfig.BATCH_SIZE_CONFIG)); + map.from(this::getBootstrapServers).to(properties.in(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG)); + map.from(this::getBufferMemory) + .as(DataSize::toBytes) + .to(properties.in(ProducerConfig.BUFFER_MEMORY_CONFIG)); + map.from(this::getClientId).to(properties.in(ProducerConfig.CLIENT_ID_CONFIG)); + map.from(this::getCompressionType).to(properties.in(ProducerConfig.COMPRESSION_TYPE_CONFIG)); + map.from(this::getKeySerializer).to(properties.in(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG)); + map.from(this::getRetries).to(properties.in(ProducerConfig.RETRIES_CONFIG)); + map.from(this::getValueSerializer).to(properties.in(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG)); + return properties.with(this.ssl, this.security, this.properties, sslBundles); + } + + } + + public static class Admin { + + private final Ssl ssl = new Ssl(); + + private final Security security = new Security(); + + /** + * ID to pass to the server when making requests. Used for server-side logging. + */ + private String clientId; + + /** + * Additional admin-specific properties used to configure the client. + */ + private final Map properties = new HashMap<>(); + + /** + * Close timeout. + */ + private Duration closeTimeout; + + /** + * Operation timeout. + */ + private Duration operationTimeout; + + /** + * Whether to fail fast if the broker is not available on startup. + */ + private boolean failFast; + + /** + * Whether to enable modification of existing topic configuration. + */ + private boolean modifyTopicConfigs; + + /** + * Whether to automatically create topics during context initialization. When set + * to false, disables automatic topic creation during context initialization. + */ + private boolean autoCreate = true; + + public Ssl getSsl() { + return this.ssl; + } + + public Security getSecurity() { + return this.security; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Duration getCloseTimeout() { + return this.closeTimeout; + } + + public void setCloseTimeout(Duration closeTimeout) { + this.closeTimeout = closeTimeout; + } + + public Duration getOperationTimeout() { + return this.operationTimeout; + } + + public void setOperationTimeout(Duration operationTimeout) { + this.operationTimeout = operationTimeout; + } + + public boolean isFailFast() { + return this.failFast; + } + + public void setFailFast(boolean failFast) { + this.failFast = failFast; + } + + public boolean isModifyTopicConfigs() { + return this.modifyTopicConfigs; + } + + public void setModifyTopicConfigs(boolean modifyTopicConfigs) { + this.modifyTopicConfigs = modifyTopicConfigs; + } + + public boolean isAutoCreate() { + return this.autoCreate; + } + + public void setAutoCreate(boolean autoCreate) { + this.autoCreate = autoCreate; + } + + public Map getProperties() { + return this.properties; + } + + public Map buildProperties(SslBundles sslBundles) { + Properties properties = new Properties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getClientId).to(properties.in(ProducerConfig.CLIENT_ID_CONFIG)); + return properties.with(this.ssl, this.security, this.properties, sslBundles); + } + + } + + /** + * High (and some medium) priority Streams properties and a general properties bucket. + */ + public static class Streams { + + private final Ssl ssl = new Ssl(); + + private final Security security = new Security(); + + private final Cleanup cleanup = new Cleanup(); + + /** + * Kafka streams application.id property; default spring.application.name. + */ + private String applicationId; + + /** + * Whether to auto-start the streams factory bean. + */ + private boolean autoStartup = true; + + /** + * Comma-delimited list of host:port pairs to use for establishing the initial + * connections to the Kafka cluster. Overrides the global property, for streams. + */ + private List bootstrapServers; + + /** + * Maximum size of the in-memory state store cache across all threads. + */ + private DataSize stateStoreCacheMaxSize; + + /** + * ID to pass to the server when making requests. Used for server-side logging. + */ + private String clientId; + + /** + * The replication factor for change log topics and repartition topics created by + * the stream processing application. + */ + private Integer replicationFactor; + + /** + * Directory location for the state store. + */ + private String stateDir; + + /** + * Additional Kafka properties used to configure the streams. + */ + private final Map properties = new HashMap<>(); + + public Ssl getSsl() { + return this.ssl; + } + + public Security getSecurity() { + return this.security; + } + + public Cleanup getCleanup() { + return this.cleanup; + } + + public String getApplicationId() { + return this.applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public boolean isAutoStartup() { + return this.autoStartup; + } + + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + public List getBootstrapServers() { + return this.bootstrapServers; + } + + public void setBootstrapServers(List bootstrapServers) { + this.bootstrapServers = bootstrapServers; + } + + public DataSize getStateStoreCacheMaxSize() { + return this.stateStoreCacheMaxSize; + } + + public void setStateStoreCacheMaxSize(DataSize stateStoreCacheMaxSize) { + this.stateStoreCacheMaxSize = stateStoreCacheMaxSize; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Integer getReplicationFactor() { + return this.replicationFactor; + } + + public void setReplicationFactor(Integer replicationFactor) { + this.replicationFactor = replicationFactor; + } + + public String getStateDir() { + return this.stateDir; + } + + public void setStateDir(String stateDir) { + this.stateDir = stateDir; + } + + public Map getProperties() { + return this.properties; + } + + public Map buildProperties(SslBundles sslBundles) { + Properties properties = new Properties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getApplicationId).to(properties.in("application.id")); + map.from(this::getBootstrapServers).to(properties.in(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG)); + map.from(this::getStateStoreCacheMaxSize) + .asInt(DataSize::toBytes) + .to(properties.in("statestore.cache.max.bytes")); + map.from(this::getClientId).to(properties.in(CommonClientConfigs.CLIENT_ID_CONFIG)); + map.from(this::getReplicationFactor).to(properties.in("replication.factor")); + map.from(this::getStateDir).to(properties.in("state.dir")); + return properties.with(this.ssl, this.security, this.properties, sslBundles); + } + + } + + public static class Template { + + /** + * Default topic to which messages are sent. + */ + private String defaultTopic; + + /** + * Transaction id prefix, override the transaction id prefix in the producer + * factory. + */ + private String transactionIdPrefix; + + /** + * Whether to enable observation. + */ + private boolean observationEnabled; + + public String getDefaultTopic() { + return this.defaultTopic; + } + + public void setDefaultTopic(String defaultTopic) { + this.defaultTopic = defaultTopic; + } + + public String getTransactionIdPrefix() { + return this.transactionIdPrefix; + } + + public void setTransactionIdPrefix(String transactionIdPrefix) { + this.transactionIdPrefix = transactionIdPrefix; + } + + public boolean isObservationEnabled() { + return this.observationEnabled; + } + + public void setObservationEnabled(boolean observationEnabled) { + this.observationEnabled = observationEnabled; + } + + } + + public static class Listener { + + public enum Type { + + /** + * Invokes the endpoint with one ConsumerRecord at a time. + */ + SINGLE, + + /** + * Invokes the endpoint with a batch of ConsumerRecords. + */ + BATCH + + } + + /** + * Listener type. + */ + private Type type = Type.SINGLE; + + /** + * Support for asynchronous record acknowledgements. Only applies when + * spring.kafka.listener.ack-mode is manual or manual-immediate. + */ + private Boolean asyncAcks; + + /** + * Prefix for the listener's consumer client.id property. + */ + private String clientId; + + /** + * Number of threads to run in the listener containers. + */ + private Integer concurrency; + + /** + * Timeout to use when polling the consumer. + */ + private Duration pollTimeout; + + /** + * Multiplier applied to "pollTimeout" to determine if a consumer is + * non-responsive. + */ + private Float noPollThreshold; + + /** + * Number of records between offset commits when ackMode is "COUNT" or + * "COUNT_TIME". + */ + private Integer ackCount; + + /** + * Time between offset commits when ackMode is "TIME" or "COUNT_TIME". + */ + private Duration ackTime; + + /** + * Sleep interval between Consumer.poll(Duration) calls. + */ + private Duration idleBetweenPolls = Duration.ZERO; + + /** + * Time between publishing idle consumer events (no data received). + */ + private Duration idleEventInterval; + + /** + * Time between publishing idle partition consumer events (no data received for + * partition). + */ + private Duration idlePartitionEventInterval; + + /** + * Time between checks for non-responsive consumers. If a duration suffix is not + * specified, seconds will be used. + */ + @DurationUnit(ChronoUnit.SECONDS) + private Duration monitorInterval; + + /** + * Whether to log the container configuration during initialization (INFO level). + */ + private Boolean logContainerConfig; + + /** + * Whether the container should fail to start if at least one of the configured + * topics are not present on the broker. + */ + private boolean missingTopicsFatal = false; + + /** + * Whether the container stops after the current record is processed or after all + * the records from the previous poll are processed. + */ + private boolean immediateStop = false; + + /** + * Whether to auto start the container. + */ + private boolean autoStartup = true; + + /** + * Whether to instruct the container to change the consumer thread name during + * initialization. + */ + private Boolean changeConsumerThreadName; + + /** + * Whether to enable observation. + */ + private boolean observationEnabled; + + public Type getType() { + return this.type; + } + + public void setType(Type type) { + this.type = type; + } + + public Boolean getAsyncAcks() { + return this.asyncAcks; + } + + public void setAsyncAcks(Boolean asyncAcks) { + this.asyncAcks = asyncAcks; + } + + public String getClientId() { + return this.clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Integer getConcurrency() { + return this.concurrency; + } + + public void setConcurrency(Integer concurrency) { + this.concurrency = concurrency; + } + + public Duration getPollTimeout() { + return this.pollTimeout; + } + + public void setPollTimeout(Duration pollTimeout) { + this.pollTimeout = pollTimeout; + } + + public Float getNoPollThreshold() { + return this.noPollThreshold; + } + + public void setNoPollThreshold(Float noPollThreshold) { + this.noPollThreshold = noPollThreshold; + } + + public Integer getAckCount() { + return this.ackCount; + } + + public void setAckCount(Integer ackCount) { + this.ackCount = ackCount; + } + + public Duration getAckTime() { + return this.ackTime; + } + + public void setAckTime(Duration ackTime) { + this.ackTime = ackTime; + } + + public Duration getIdleBetweenPolls() { + return this.idleBetweenPolls; + } + + public void setIdleBetweenPolls(Duration idleBetweenPolls) { + this.idleBetweenPolls = idleBetweenPolls; + } + + public Duration getIdleEventInterval() { + return this.idleEventInterval; + } + + public void setIdleEventInterval(Duration idleEventInterval) { + this.idleEventInterval = idleEventInterval; + } + + public Duration getIdlePartitionEventInterval() { + return this.idlePartitionEventInterval; + } + + public void setIdlePartitionEventInterval(Duration idlePartitionEventInterval) { + this.idlePartitionEventInterval = idlePartitionEventInterval; + } + + public Duration getMonitorInterval() { + return this.monitorInterval; + } + + public void setMonitorInterval(Duration monitorInterval) { + this.monitorInterval = monitorInterval; + } + + public Boolean getLogContainerConfig() { + return this.logContainerConfig; + } + + public void setLogContainerConfig(Boolean logContainerConfig) { + this.logContainerConfig = logContainerConfig; + } + + public boolean isMissingTopicsFatal() { + return this.missingTopicsFatal; + } + + public void setMissingTopicsFatal(boolean missingTopicsFatal) { + this.missingTopicsFatal = missingTopicsFatal; + } + + public boolean isImmediateStop() { + return this.immediateStop; + } + + public void setImmediateStop(boolean immediateStop) { + this.immediateStop = immediateStop; + } + + public boolean isAutoStartup() { + return this.autoStartup; + } + + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + public Boolean getChangeConsumerThreadName() { + return this.changeConsumerThreadName; + } + + public void setChangeConsumerThreadName(Boolean changeConsumerThreadName) { + this.changeConsumerThreadName = changeConsumerThreadName; + } + + public boolean isObservationEnabled() { + return this.observationEnabled; + } + + public void setObservationEnabled(boolean observationEnabled) { + this.observationEnabled = observationEnabled; + } + + } + + public static class Ssl { + + /** + * Name of the SSL bundle to use. + */ + private String bundle; + + /** + * Password of the private key in either key store key or key store file. + */ + private String keyPassword; + + /** + * Certificate chain in PEM format with a list of X.509 certificates. + */ + private String keyStoreCertificateChain; + + /** + * Private key in PEM format with PKCS#8 keys. + */ + private String keyStoreKey; + + /** + * Location of the key store file. + */ + private Resource keyStoreLocation; + + /** + * Store password for the key store file. + */ + private String keyStorePassword; + + /** + * Type of the key store. + */ + private String keyStoreType; + + /** + * Trusted certificates in PEM format with X.509 certificates. + */ + private String trustStoreCertificates; + + /** + * Location of the trust store file. + */ + private Resource trustStoreLocation; + + /** + * Store password for the trust store file. + */ + private String trustStorePassword; + + /** + * Type of the trust store. + */ + private String trustStoreType; + + /** + * SSL protocol to use. + */ + private String protocol; + + public String getBundle() { + return this.bundle; + } + + public void setBundle(String bundle) { + this.bundle = bundle; + } + + public String getKeyPassword() { + return this.keyPassword; + } + + public void setKeyPassword(String keyPassword) { + this.keyPassword = keyPassword; + } + + public String getKeyStoreCertificateChain() { + return this.keyStoreCertificateChain; + } + + public void setKeyStoreCertificateChain(String keyStoreCertificateChain) { + this.keyStoreCertificateChain = keyStoreCertificateChain; + } + + public String getKeyStoreKey() { + return this.keyStoreKey; + } + + public void setKeyStoreKey(String keyStoreKey) { + this.keyStoreKey = keyStoreKey; + } + + public Resource getKeyStoreLocation() { + return this.keyStoreLocation; + } + + public void setKeyStoreLocation(Resource keyStoreLocation) { + this.keyStoreLocation = keyStoreLocation; + } + + public String getKeyStorePassword() { + return this.keyStorePassword; + } + + public void setKeyStorePassword(String keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + public String getKeyStoreType() { + return this.keyStoreType; + } + + public void setKeyStoreType(String keyStoreType) { + this.keyStoreType = keyStoreType; + } + + public String getTrustStoreCertificates() { + return this.trustStoreCertificates; + } + + public void setTrustStoreCertificates(String trustStoreCertificates) { + this.trustStoreCertificates = trustStoreCertificates; + } + + public Resource getTrustStoreLocation() { + return this.trustStoreLocation; + } + + public void setTrustStoreLocation(Resource trustStoreLocation) { + this.trustStoreLocation = trustStoreLocation; + } + + public String getTrustStorePassword() { + return this.trustStorePassword; + } + + public void setTrustStorePassword(String trustStorePassword) { + this.trustStorePassword = trustStorePassword; + } + + public String getTrustStoreType() { + return this.trustStoreType; + } + + public void setTrustStoreType(String trustStoreType) { + this.trustStoreType = trustStoreType; + } + + public String getProtocol() { + return this.protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + @Deprecated(since = "3.2.0", forRemoval = true) + public Map buildProperties() { + return buildProperties(null); + } + + public Map buildProperties(SslBundles sslBundles) { + validate(); + Properties properties = new Properties(); + if (getBundle() != null) { + properties.in(SslConfigs.SSL_ENGINE_FACTORY_CLASS_CONFIG) + .accept(SslBundleSslEngineFactory.class.getName()); + properties.in(SslBundle.class.getName()).accept(sslBundles.getBundle(getBundle())); + } else { + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getKeyPassword).to(properties.in(SslConfigs.SSL_KEY_PASSWORD_CONFIG)); + map.from(this::getKeyStoreCertificateChain) + .to(properties.in(SslConfigs.SSL_KEYSTORE_CERTIFICATE_CHAIN_CONFIG)); + map.from(this::getKeyStoreKey).to(properties.in(SslConfigs.SSL_KEYSTORE_KEY_CONFIG)); + map.from(this::getKeyStoreLocation) + .as(this::resourceToPath) + .to(properties.in(SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG)); + map.from(this::getKeyStorePassword).to(properties.in(SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG)); + map.from(this::getKeyStoreType).to(properties.in(SslConfigs.SSL_KEYSTORE_TYPE_CONFIG)); + map.from(this::getTrustStoreCertificates) + .to(properties.in(SslConfigs.SSL_TRUSTSTORE_CERTIFICATES_CONFIG)); + map.from(this::getTrustStoreLocation) + .as(this::resourceToPath) + .to(properties.in(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG)); + map.from(this::getTrustStorePassword).to(properties.in(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG)); + map.from(this::getTrustStoreType).to(properties.in(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG)); + map.from(this::getProtocol).to(properties.in(SslConfigs.SSL_PROTOCOL_CONFIG)); + } + return properties; + } + + private void validate() { + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.key-store-key", getKeyStoreKey()); + entries.put("spring.kafka.ssl.key-store-location", getKeyStoreLocation()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.trust-store-certificates", getTrustStoreCertificates()); + entries.put("spring.kafka.ssl.trust-store-location", getTrustStoreLocation()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.key-store-key", getKeyStoreKey()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.key-store-location", getKeyStoreLocation()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.trust-store-certificates", getTrustStoreCertificates()); + }); + MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleNonNullValuesIn((entries) -> { + entries.put("spring.kafka.ssl.bundle", getBundle()); + entries.put("spring.kafka.ssl.trust-store-location", getTrustStoreLocation()); + }); + } + + private String resourceToPath(Resource resource) { + try { + return resource.getFile().getAbsolutePath(); + } catch (IOException ex) { + throw new IllegalStateException("Resource '" + resource + "' must be on a file system", ex); + } + } + + } + + public static class Jaas { + + /** + * Whether to enable JAAS configuration. + */ + private boolean enabled; + + /** + * Login module. + */ + private String loginModule = "com.sun.security.auth.module.Krb5LoginModule"; + + /** + * Additional JAAS options. + */ + private final Map options = new HashMap<>(); + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getLoginModule() { + return this.loginModule; + } + + public void setLoginModule(String loginModule) { + this.loginModule = loginModule; + } + + public Map getOptions() { + return this.options; + } + + public void setOptions(Map options) { + if (options != null) { + this.options.putAll(options); + } + } + + } + + public static class Security { + + /** + * Security protocol used to communicate with brokers. + */ + private String protocol; + + public String getProtocol() { + return this.protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public Map buildProperties() { + Properties properties = new Properties(); + PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); + map.from(this::getProtocol).to(properties.in(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG)); + return properties; + } + + } + + public static class Retry { + + private final Topic topic = new Topic(); + + public Topic getTopic() { + return this.topic; + } + + /** + * Properties for non-blocking, topic-based retries. + */ + public static class Topic { + + /** + * Whether to enable topic-based non-blocking retries. + */ + private boolean enabled; + + /** + * Total number of processing attempts made before sending the message to the + * DLT. + */ + private int attempts = 3; + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getAttempts() { + return this.attempts; + } + + public void setAttempts(int attempts) { + this.attempts = attempts; + } + + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.delay", since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) + public Duration getDelay() { + return getBackoff().getDelay(); + } + + @Deprecated(since = "3.4.0", forRemoval = true) + public void setDelay(Duration delay) { + getBackoff().setDelay(delay); + } + + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.multiplier", + since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) + public double getMultiplier() { + return getBackoff().getMultiplier(); + } + + @Deprecated(since = "3.4.0", forRemoval = true) + public void setMultiplier(double multiplier) { + getBackoff().setMultiplier(multiplier); + } + + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.maxDelay", since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) + public Duration getMaxDelay() { + return getBackoff().getMaxDelay(); + } + + @Deprecated(since = "3.4.0", forRemoval = true) + public void setMaxDelay(Duration maxDelay) { + getBackoff().setMaxDelay(maxDelay); + } + + @DeprecatedConfigurationProperty(replacement = "spring.kafka.retry.topic.backoff.random", since = "3.4.0") + @Deprecated(since = "3.4.0", forRemoval = true) + public boolean isRandomBackOff() { + return getBackoff().isRandom(); + } + + @Deprecated(since = "3.4.0", forRemoval = true) + public void setRandomBackOff(boolean randomBackOff) { + getBackoff().setRandom(randomBackOff); + } + + private final Backoff backoff = new Backoff(); + + public Backoff getBackoff() { + return this.backoff; + } + + public static class Backoff { + + /** + * Canonical backoff period. Used as an initial value in the exponential + * case, and as a minimum value in the uniform case. + */ + private Duration delay = Duration.ofSeconds(1); + + /** + * Multiplier to use for generating the next backoff delay. + */ + private double multiplier = 0.0; + + /** + * Maximum wait between retries. If less than the delay then the default + * of 30 seconds is applied. + */ + private Duration maxDelay = Duration.ZERO; + + /** + * Whether to have the backoff delays. + */ + private boolean random = false; + + public Duration getDelay() { + return this.delay; + } + + public void setDelay(Duration delay) { + this.delay = delay; + } + + public double getMultiplier() { + return this.multiplier; + } + + public void setMultiplier(double multiplier) { + this.multiplier = multiplier; + } + + public Duration getMaxDelay() { + return this.maxDelay; + } + + public void setMaxDelay(Duration maxDelay) { + this.maxDelay = maxDelay; + } + + public boolean isRandom() { + return this.random; + } + + public void setRandom(boolean random) { + this.random = random; + } + + } + + } + + } + + public static class Cleanup { + + /** + * Cleanup the application?s local state directory on startup. + */ + private boolean onStartup = false; + + /** + * Cleanup the application?s local state directory on shutdown. + */ + private boolean onShutdown = false; + + public boolean isOnStartup() { + return this.onStartup; + } + + public void setOnStartup(boolean onStartup) { + this.onStartup = onStartup; + } + + public boolean isOnShutdown() { + return this.onShutdown; + } + + public void setOnShutdown(boolean onShutdown) { + this.onShutdown = onShutdown; + } + + } + + public enum IsolationLevel { + + /** + * Read everything including aborted transactions. + */ + READ_UNCOMMITTED((byte) 0), + + /** + * Read records from committed transactions, in addition to records not part of + * transactions. + */ + READ_COMMITTED((byte) 1); + + private final byte id; + + IsolationLevel(byte id) { + this.id = id; + } + + public byte id() { + return this.id; + } + + } + + @SuppressWarnings("serial") + private static final class Properties extends HashMap { + + java.util.function.Consumer in(String key) { + return (value) -> put(key, value); + } + + Properties with(Ssl ssl, Security security, Map properties, SslBundles sslBundles) { + putAll(ssl.buildProperties(sslBundles)); + putAll(security.buildProperties()); + putAll(properties); + return this; + } + + } + +} \ No newline at end of file diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicator.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicator.java new file mode 100644 index 00000000..2241f3c2 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicator.java @@ -0,0 +1,34 @@ +package org.reactivecommons.async.kafka.health; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.kafka.clients.admin.AdminClient; +import org.reactivecommons.async.starter.config.health.RCHealth; +import org.reactivecommons.async.starter.config.health.RCHealthIndicator; +import reactor.core.publisher.Mono; + +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.DOMAIN; +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.VERSION; + +@Log4j2 +@RequiredArgsConstructor +public class KafkaReactiveHealthIndicator extends RCHealthIndicator { + private final String domain; + private final AdminClient adminClient; + + @Override + public Mono doHealthCheck(RCHealth.RCHealthBuilder builder) { + builder.withDetail(DOMAIN, domain); + return checkKafkaHealth() + .map(clusterId -> builder.up().withDetail(VERSION, clusterId).build()) + .onErrorReturn(builder.down().build()); + } + + private Mono checkKafkaHealth() { + return Mono.fromFuture(adminClient.describeCluster().clusterId() + .toCompletionStage() + .toCompletableFuture()) + .doOnError(e -> log.error("Error checking Kafka health in domain {}", domain, e)); + } + +} diff --git a/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/starter/impl/common/kafka/RCKafkaConfig.java b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/starter/impl/common/kafka/RCKafkaConfig.java new file mode 100644 index 00000000..e0749722 --- /dev/null +++ b/starters/async-kafka-starter/src/main/java/org/reactivecommons/async/starter/impl/common/kafka/RCKafkaConfig.java @@ -0,0 +1,54 @@ +package org.reactivecommons.async.starter.impl.common.kafka; + +import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; +import org.reactivecommons.async.kafka.KafkaBrokerProviderFactory; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.KafkaPropertiesAutoConfig; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomain; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomainProperties; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.ssl.DefaultSslBundleRegistry; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@EnableConfigurationProperties({KafkaPropertiesAutoConfig.class, AsyncKafkaPropsDomainProperties.class}) +@Import({AsyncKafkaPropsDomain.class, KafkaBrokerProviderFactory.class}) +public class RCKafkaConfig { + + @Bean + @ConditionalOnMissingBean(KafkaCustomizations.class) + public KafkaCustomizations defaultKafkaCustomizations() { + return new KafkaCustomizations(); + } + + @Bean + @ConditionalOnMissingBean(KafkaJacksonMessageConverter.class) + public KafkaJacksonMessageConverter kafkaJacksonMessageConverter(ObjectMapperSupplier objectMapperSupplier) { + return new KafkaJacksonMessageConverter(objectMapperSupplier.get()); + } + + @Bean + @ConditionalOnMissingBean(AsyncKafkaPropsDomain.KafkaSecretFiller.class) + public AsyncKafkaPropsDomain.KafkaSecretFiller defaultKafkaSecretFiller() { + return (ignoredDomain, ignoredProps) -> { + }; + } + + @Bean + @ConditionalOnMissingBean(KafkaProperties.class) + public KafkaProperties defaultKafkaProperties(KafkaPropertiesAutoConfig properties, ObjectMapperSupplier supplier) { + return supplier.get().convertValue(properties, KafkaProperties.class); + } + + @Bean + @ConditionalOnMissingBean(SslBundles.class) + public SslBundles defaultSslBundles() { + return new DefaultSslBundleRegistry(); + } +} diff --git a/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactoryTest.java b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactoryTest.java new file mode 100644 index 00000000..f0f4463a --- /dev/null +++ b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderFactoryTest.java @@ -0,0 +1,75 @@ +package org.reactivecommons.async.kafka; + +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.springframework.boot.ssl.SslBundles; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(MockitoExtension.class) +class KafkaBrokerProviderFactoryTest { + private final ReactiveReplyRouter router = new ReactiveReplyRouter(); + @Mock + private KafkaJacksonMessageConverter converter; + @Mock + private MeterRegistry meterRegistry; + @Mock + private CustomReporter errorReporter; + @Mock + private KafkaCustomizations customizations; + @Mock + private SslBundles sslBundles; + + private BrokerProviderFactory providerFactory; + + @BeforeEach + public void setUp() { + providerFactory = new KafkaBrokerProviderFactory(router, converter, meterRegistry, errorReporter, + customizations, sslBundles); + } + + @Test + void shouldReturnBrokerType() { + // Arrange + // Act + String brokerType = providerFactory.getBrokerType(); + // Assert + assertEquals("kafka", brokerType); + } + + @Test + void shouldReturnCreateDiscardProvider() { + // Arrange + AsyncKafkaProps props = new AsyncKafkaProps(); + props.setCheckExistingTopics(false); + // Act + DiscardProvider discardProvider = providerFactory.getDiscardProvider(props); + // Assert + assertThat(discardProvider).isInstanceOf(KafkaDiscardProvider.class); + } + + @Test + void shouldReturnBrokerProvider() { + // Arrange + AsyncKafkaProps props = new AsyncKafkaProps(); + props.setCheckExistingTopics(false); + DiscardProvider discardProvider = providerFactory.getDiscardProvider(props); + // Act + BrokerProvider brokerProvider = providerFactory.getProvider("domain", props, discardProvider); + // Assert + assertThat(brokerProvider).isInstanceOf(KafkaBrokerProvider.class); + } +} diff --git a/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderTest.java b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderTest.java new file mode 100644 index 00000000..0f6f6680 --- /dev/null +++ b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaBrokerProviderTest.java @@ -0,0 +1,147 @@ +package org.reactivecommons.async.kafka; + +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.kafka.communications.ReactiveMessageListener; +import org.reactivecommons.async.kafka.communications.ReactiveMessageSender; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.communications.topology.TopologyCreator; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.reactivecommons.async.kafka.health.KafkaReactiveHealthIndicator; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.health.RCHealth; +import org.springframework.boot.ssl.DefaultSslBundleRegistry; +import org.springframework.boot.ssl.SslBundles; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import javax.sound.midi.Receiver; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class KafkaBrokerProviderTest { + private final AsyncKafkaProps props = new AsyncKafkaProps(); + private final SslBundles sslBundles = new DefaultSslBundleRegistry(); + private final KafkaCustomizations customizations = new KafkaCustomizations(); + @Mock + private ReactiveMessageListener listener; + @Mock + private TopologyCreator creator; + @Mock + private HandlerResolver handlerResolver; + @Mock + private KafkaJacksonMessageConverter messageConverter; + @Mock + private CustomReporter customReporter; + @Mock + private Receiver receiver; + @Mock + private ReactiveReplyRouter router; + @Mock + private MeterRegistry meterRegistry; + @Mock + private ReactiveMessageSender sender; + @Mock + private DiscardNotifier discardNotifier; + @Mock + private KafkaReactiveHealthIndicator healthIndicator; + + + private BrokerProvider brokerProvider; + + + @BeforeEach + public void init() { + props.setAppName("test"); + brokerProvider = new KafkaBrokerProvider(DEFAULT_DOMAIN, + props, + router, + messageConverter, + meterRegistry, + customReporter, + healthIndicator, + listener, + sender, + discardNotifier, + creator, + customizations, + sslBundles); + } + + @Test + void shouldCreateDomainEventBus() { + // Act + DomainEventBus domainBus = brokerProvider.getDomainBus(); + // Assert + assertThat(domainBus).isExactlyInstanceOf(KafkaDomainEventBus.class); + } + + @Test + void shouldCreateDirectAsyncGateway() { + // Act + DirectAsyncGateway domainBus = brokerProvider.getDirectAsyncGateway(); + // Assert + assertThat(domainBus).isExactlyInstanceOf(KafkaDirectAsyncGateway.class); + } + + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) + void shouldListenDomainEvents() { + List mockedListeners = spy(List.of()); + when(mockedListeners.isEmpty()).thenReturn(false); + when(handlerResolver.getEventListeners()).thenReturn(mockedListeners); + when(creator.createTopics(any())).thenReturn(Mono.empty()); + when(listener.getMaxConcurrency()).thenReturn(1); + when(listener.listen(any(String.class), any())).thenReturn(Flux.never()); + // Act + brokerProvider.listenDomainEvents(handlerResolver); + // Assert + verify(listener, times(1)).listen(any(String.class), any()); + } + + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) + void shouldListenNotificationEvents() { + List mockedListeners = spy(List.of()); + when(mockedListeners.isEmpty()).thenReturn(false); + when(handlerResolver.getNotificationListeners()).thenReturn(mockedListeners); + when(creator.createTopics(any())).thenReturn(Mono.empty()); + when(listener.getMaxConcurrency()).thenReturn(1); + when(listener.listen(any(String.class), any())).thenReturn(Flux.never()); + // Act + brokerProvider.listenNotificationEvents(handlerResolver); + // Assert + verify(listener, times(1)).listen(any(String.class), any()); + } + + @Test + void shouldProxyHealthCheck() { + when(healthIndicator.health()).thenReturn(Mono.fromSupplier(() -> RCHealth.builder().up().build())); + // Act + Mono flow = brokerProvider.healthCheck(); + // Assert + StepVerifier.create(flow) + .expectNextMatches(health -> health.getStatus().equals(RCHealth.Status.UP)) + .verifyComplete(); + } +} diff --git a/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaDiscardProviderTest.java b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaDiscardProviderTest.java new file mode 100644 index 00000000..9723e0e5 --- /dev/null +++ b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/KafkaDiscardProviderTest.java @@ -0,0 +1,38 @@ +package org.reactivecommons.async.kafka; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.DLQDiscardNotifier; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.kafka.communications.topology.KafkaCustomizations; +import org.reactivecommons.async.kafka.config.KafkaProperties; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaProps; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.springframework.boot.ssl.SslBundles; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class KafkaDiscardProviderTest { + @Mock + private KafkaJacksonMessageConverter converter; + @Mock + private KafkaCustomizations customizations; + @Mock + private SslBundles sslBundles; + + @Test + void shouldCreateDiscardNotifier() { + // Arrange + AsyncKafkaProps props = new AsyncKafkaProps(); + props.setCheckExistingTopics(false); + props.setConnectionProperties(new KafkaProperties()); + KafkaDiscardProvider discardProvider = new KafkaDiscardProvider(props, converter, customizations, sslBundles); + // Act + DiscardNotifier notifier = discardProvider.get(); + // Assert + assertThat(notifier).isExactlyInstanceOf(DLQDiscardNotifier.class); + } +} diff --git a/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicatorTest.java b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicatorTest.java new file mode 100644 index 00000000..b713a513 --- /dev/null +++ b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/kafka/health/KafkaReactiveHealthIndicatorTest.java @@ -0,0 +1,69 @@ +package org.reactivecommons.async.kafka.health; + +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.admin.DescribeClusterResult; +import org.apache.kafka.common.KafkaFuture; +import org.apache.kafka.common.internals.KafkaFutureImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.starter.config.health.RCHealth; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class KafkaReactiveHealthIndicatorTest { + @Mock + private AdminClient adminClient; + @Mock + private DescribeClusterResult describeClusterResult; + + private KafkaReactiveHealthIndicator indicator; + + @BeforeEach + void setup() { + indicator = new KafkaReactiveHealthIndicator(DEFAULT_DOMAIN, adminClient); + } + + @Test + void shouldBeUp() { + // Arrange + when(adminClient.describeCluster()).thenReturn(describeClusterResult); + when(describeClusterResult.clusterId()).thenReturn(KafkaFuture.completedFuture("cluster123")); + // Act + Mono result = indicator.doHealthCheck(RCHealth.builder()); + // Assert + StepVerifier.create(result) + .assertNext(health -> { + assertEquals(DEFAULT_DOMAIN, health.getDetails().get("domain")); + assertEquals("cluster123", health.getDetails().get("version")); + assertEquals(RCHealth.Status.UP, health.getStatus()); + }) + .verifyComplete(); + } + + @Test + void shouldBeDown() { + // Arrange + when(adminClient.describeCluster()).thenReturn(describeClusterResult); + KafkaFutureImpl future = new KafkaFutureImpl<>(); + future.completeExceptionally(new RuntimeException("simulate error")); + when(describeClusterResult.clusterId()).thenReturn(future); + // Act + Mono result = indicator.doHealthCheck(RCHealth.builder()); + // Assert + StepVerifier.create(result) + .expectNextMatches(health -> { + assertEquals(DEFAULT_DOMAIN, health.getDetails().get("domain")); + assertEquals(RCHealth.Status.DOWN, health.getStatus()); + return true; + }) + .verifyComplete(); + } +} diff --git a/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/starter/impl/common/kafka/KafkaConfigTest.java b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/starter/impl/common/kafka/KafkaConfigTest.java new file mode 100644 index 00000000..25363d28 --- /dev/null +++ b/starters/async-kafka-starter/src/test/java/org/reactivecommons/async/starter/impl/common/kafka/KafkaConfigTest.java @@ -0,0 +1,46 @@ +package org.reactivecommons.async.starter.impl.common.kafka; + + +import org.junit.jupiter.api.Test; +import org.reactivecommons.async.kafka.KafkaBrokerProviderFactory; +import org.reactivecommons.async.kafka.config.props.AsyncKafkaPropsDomain; +import org.reactivecommons.async.kafka.converters.json.KafkaJacksonMessageConverter; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(classes = { + RCKafkaConfig.class, + AsyncKafkaPropsDomain.class, + KafkaBrokerProviderFactory.class, + ReactiveCommonsConfig.class, + ReactiveCommonsListenersConfig.class +}) +class KafkaConfigTest { + @Autowired + private KafkaJacksonMessageConverter converter; + @Autowired + private ConnectionManager manager; + + @Test + void shouldHasConverter() { + // Arrange + // Act + // Assert + assertThat(converter).isNotNull(); + } + + @Test + void shouldHasManager() { + // Arrange + // Act + // Assert + assertThat(manager).isNotNull(); + assertThat(manager.getProviders()).isNotEmpty(); + assertThat(manager.getProviders().get("app").getProps().getAppName()).isEqualTo("async-kafka-starter"); + } +} diff --git a/starters/async-kafka-starter/src/test/resources/application.yaml b/starters/async-kafka-starter/src/test/resources/application.yaml new file mode 100644 index 00000000..a5c0534e --- /dev/null +++ b/starters/async-kafka-starter/src/test/resources/application.yaml @@ -0,0 +1,8 @@ +spring: + application: + name: async-kafka-starter +reactive: + commons: + kafka: + app: + checkExistingTopics: false \ No newline at end of file diff --git a/async/async-rabbit-standalone/async-commons-rabbit-standalone.gradle b/starters/async-rabbit-standalone/async-commons-rabbit-standalone.gradle similarity index 100% rename from async/async-rabbit-standalone/async-commons-rabbit-standalone.gradle rename to starters/async-rabbit-standalone/async-commons-rabbit-standalone.gradle diff --git a/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/DirectAsyncGatewayConfig.java similarity index 75% rename from async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java rename to starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/DirectAsyncGatewayConfig.java index a68a2e90..0beea22f 100644 --- a/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/DirectAsyncGatewayConfig.java +++ b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/DirectAsyncGatewayConfig.java @@ -1,7 +1,7 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.rabbit.standalone.config; import io.micrometer.core.instrument.MeterRegistry; -import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import org.reactivecommons.async.commons.config.BrokerConfig; import org.reactivecommons.async.commons.converters.MessageConverter; import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; @@ -14,7 +14,7 @@ import java.util.Base64; import java.util.UUID; -@AllArgsConstructor +@RequiredArgsConstructor public class DirectAsyncGatewayConfig { private String directMessagesExchangeName; @@ -22,13 +22,17 @@ public class DirectAsyncGatewayConfig { private String appName; - public RabbitDirectAsyncGateway rabbitDirectAsyncGateway(BrokerConfig config, ReactiveReplyRouter router, ReactiveMessageSender rSender, MessageConverter converter, - MeterRegistry meterRegistry) throws Exception { - return new RabbitDirectAsyncGateway(config, router, rSender, directMessagesExchangeName, converter, meterRegistry); + public RabbitDirectAsyncGateway rabbitDirectAsyncGateway(BrokerConfig config, ReactiveReplyRouter router, + ReactiveMessageSender rSender, MessageConverter converter, + MeterRegistry meterRegistry) { + return new RabbitDirectAsyncGateway(config, router, rSender, directMessagesExchangeName, converter, + meterRegistry); } - public ApplicationReplyListener msgListener(ReactiveReplyRouter router, BrokerConfig config, ReactiveMessageListener listener, boolean createTopology) { - final ApplicationReplyListener replyListener = new ApplicationReplyListener(router, listener, generateName(), globalReplyExchangeName, createTopology); + public ApplicationReplyListener msgListener(ReactiveReplyRouter router, BrokerConfig config, + ReactiveMessageListener listener, boolean createTopology) { + final ApplicationReplyListener replyListener = new ApplicationReplyListener(router, listener, generateName(), + globalReplyExchangeName, createTopology); replyListener.startListening(config.getRoutingKey()); return replyListener; } @@ -50,7 +54,7 @@ public String generateName() { .putLong(uuid.getLeastSignificantBits()); // Convert to base64 and remove trailing = return this.appName + encodeToUrlSafeString(bb.array()) - .replaceAll("=", ""); + .replace("=", ""); } public static String encodeToUrlSafeString(byte[] src) { diff --git a/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/EventBusConfig.java similarity index 71% rename from async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java rename to starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/EventBusConfig.java index fe01f0c3..959e19d9 100644 --- a/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/EventBusConfig.java +++ b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/EventBusConfig.java @@ -1,18 +1,17 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.rabbit.standalone.config; +import lombok.RequiredArgsConstructor; import org.reactivecommons.api.domain.DomainEventBus; import org.reactivecommons.async.rabbit.RabbitDomainEventBus; import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; import static reactor.rabbitmq.ExchangeSpecification.exchange; -public class EventBusConfig { - private String domainEventsExchangeName; +@RequiredArgsConstructor +public class EventBusConfig { - public EventBusConfig(String domainEventsExchangeName) { - this.domainEventsExchangeName = domainEventsExchangeName; - } + private final String domainEventsExchangeName; public DomainEventBus domainEventBus(ReactiveMessageSender sender) { sender.getTopologyCreator().declare(exchange(domainEventsExchangeName).durable(true).type("topic")).subscribe(); diff --git a/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitMqConfig.java similarity index 71% rename from async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java rename to starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitMqConfig.java index 0d930391..2ee716e6 100644 --- a/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitMqConfig.java +++ b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitMqConfig.java @@ -1,28 +1,32 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.rabbit.standalone.config; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; +import lombok.RequiredArgsConstructor; import lombok.extern.java.Log; -import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; -import org.reactivecommons.async.rabbit.communications.TopologyCreator; import org.reactivecommons.async.commons.converters.MessageConverter; -import org.reactivecommons.async.rabbit.converters.json.JacksonMessageConverter; import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.communications.TopologyCreator; +import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; import reactor.core.publisher.Mono; -import reactor.rabbitmq.*; +import reactor.rabbitmq.ChannelPool; +import reactor.rabbitmq.ChannelPoolFactory; +import reactor.rabbitmq.ChannelPoolOptions; +import reactor.rabbitmq.RabbitFlux; +import reactor.rabbitmq.Sender; +import reactor.rabbitmq.SenderOptions; import reactor.util.retry.Retry; import java.time.Duration; import java.util.logging.Level; @Log +@RequiredArgsConstructor public class RabbitMqConfig { - private String appName; - - public RabbitMqConfig(String appName) { - this.appName = appName; - } + private final String appName; public ReactiveMessageSender messageSender(ConnectionFactoryProvider provider, MessageConverter converter, RabbitProperties rabbitProperties) { @@ -38,12 +42,6 @@ public ReactiveMessageSender messageSender(ConnectionFactoryProvider provider, M return new ReactiveMessageSender(sender, appName, converter, new TopologyCreator(sender)); } - /*public ReactiveMessageListener messageListener(ConnectionFactoryProvider provider) { - final Mono connection = createSenderConnectionMono(provider.getConnectionFactory(), "listener"); - Receiver receiver = RabbitFlux.createReceiver(new ReceiverOptions().connectionMono(connection)); - return new ReactiveMessageListener(receiver, new TopologyCreator(connection)); - }*/ - public ConnectionFactoryProvider connectionFactory(RabbitProperties properties) { final ConnectionFactory factory = new ConnectionFactory(); factory.setHost(properties.getHost()); @@ -58,14 +56,14 @@ public ConnectionFactoryProvider connectionFactory(RabbitProperties properties) } public MessageConverter messageConverter(ObjectMapperSupplier objectMapperSupplier) { - return new JacksonMessageConverter(objectMapperSupplier.get()); + return new RabbitJacksonMessageConverter(objectMapperSupplier.get()); } Mono createSenderConnectionMono(ConnectionFactory factory, String name) { return Mono.fromCallable(() -> factory.newConnection(name)) - .doOnError(err -> - log.log(Level.SEVERE, "Error creating connection to RabbitMq Broker. Starting retry process...", err) - ) + .doOnError(err -> log.log( + Level.SEVERE, "Error creating connection to RabbitMq Broker. Starting retry process...", err + )) .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(300))) .cache(); } diff --git a/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitProperties.java similarity index 69% rename from async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java rename to starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitProperties.java index 48045b68..93c9a1f3 100644 --- a/async/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java +++ b/starters/async-rabbit-standalone/src/main/java/org/reactivecommons/async/rabbit/standalone/config/RabbitProperties.java @@ -1,4 +1,4 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.rabbit.standalone.config; import lombok.Data; @@ -7,7 +7,7 @@ public class RabbitProperties { private String host = "localhost"; private int port = 5672; private String username = "guest"; - private String password = "guest"; + private String password = "guest"; //NOSONAR private String virtualHost; private Integer channelPoolMaxCacheSize; } diff --git a/async/async-rabbit-starter/async-commons-rabbit-starter.gradle b/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle similarity index 81% rename from async/async-rabbit-starter/async-commons-rabbit-starter.gradle rename to starters/async-rabbit-starter/async-commons-rabbit-starter.gradle index 1f5b77ae..c4b4025f 100644 --- a/async/async-rabbit-starter/async-commons-rabbit-starter.gradle +++ b/starters/async-rabbit-starter/async-commons-rabbit-starter.gradle @@ -5,8 +5,10 @@ ext { dependencies { api project(':async-rabbit') + api project(':async-commons-starter') compileOnly 'org.springframework.boot:spring-boot-starter' compileOnly 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProvider.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProvider.java new file mode 100644 index 00000000..a5c626fe --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProvider.java @@ -0,0 +1,164 @@ +package org.reactivecommons.async.rabbit; + +import io.micrometer.core.instrument.MeterRegistry; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.rabbit.health.RabbitReactiveHealthIndicator; +import org.reactivecommons.async.rabbit.listeners.ApplicationCommandListener; +import org.reactivecommons.async.rabbit.listeners.ApplicationEventListener; +import org.reactivecommons.async.rabbit.listeners.ApplicationNotificationListener; +import org.reactivecommons.async.rabbit.listeners.ApplicationQueryListener; +import org.reactivecommons.async.rabbit.listeners.ApplicationReplyListener; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.health.RCHealth; +import reactor.core.publisher.Mono; + +import static reactor.rabbitmq.ExchangeSpecification.exchange; + +@Log +@Getter +@RequiredArgsConstructor +public class RabbitMQBrokerProvider implements BrokerProvider { + private final String domain; + private final AsyncProps props; + private final BrokerConfig config; + private final ReactiveReplyRouter router; + private final RabbitJacksonMessageConverter converter; + private final MeterRegistry meterRegistry; + private final CustomReporter errorReporter; + private final RabbitReactiveHealthIndicator healthIndicator; + private final ReactiveMessageListener receiver; + private final ReactiveMessageSender sender; + private final DiscardNotifier discardNotifier; + + @Override + public DomainEventBus getDomainBus() { + final String exchangeName = props.getBrokerConfigProps().getDomainEventsExchangeName(); + if (props.getCreateTopology()) { + sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("topic")).subscribe(); + } + return new RabbitDomainEventBus(sender, exchangeName, config); + } + + @Override + public DirectAsyncGateway getDirectAsyncGateway() { + String exchangeName = props.getBrokerConfigProps().getDirectMessagesExchangeName(); + if (props.getCreateTopology()) { + sender.getTopologyCreator().declare(exchange(exchangeName).durable(true).type("direct")).subscribe(); + } + listenReplies(); + return new RabbitDirectAsyncGateway(config, router, sender, exchangeName, converter, meterRegistry); + } + + @Override + public void listenDomainEvents(HandlerResolver resolver) { + if (!props.getDomain().isIgnoreThisListener()) { + final ApplicationEventListener listener = new ApplicationEventListener(receiver, + props.getBrokerConfigProps().getEventsQueue(), + props.getBrokerConfigProps().getDomainEventsExchangeName(), + resolver, + converter, + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getMaxRetries(), + props.getRetryDelay(), + props.getDomain().getEvents().getMaxLengthBytes(), + discardNotifier, + errorReporter, + props.getAppName()); + listener.startListener(); + } + } + + @Override + public void listenNotificationEvents(HandlerResolver resolver) { + if (resolver.hasNotificationListeners()) { + final ApplicationNotificationListener listener = new ApplicationNotificationListener( + receiver, + props.getBrokerConfigProps().getDomainEventsExchangeName(), + props.getBrokerConfigProps().getNotificationsQueue(), + props.getCreateTopology(), + resolver, + converter, + discardNotifier, + errorReporter); + listener.startListener(); + } + } + + @Override + public void listenCommands(HandlerResolver resolver) { + if (resolver.hasCommandHandlers()) { + ApplicationCommandListener commandListener = new ApplicationCommandListener( + receiver, + props.getBrokerConfigProps().getCommandsQueue(), + resolver, + props.getDirect().getExchange(), + converter, + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getDelayedCommands(), + props.getMaxRetries(), + props.getRetryDelay(), + props.getDirect().getMaxLengthBytes(), + discardNotifier, + errorReporter); + + commandListener.startListener(); + } + } + + @Override + public void listenQueries(HandlerResolver resolver) { + if (resolver.hasQueryHandlers()) { + final ApplicationQueryListener listener = new ApplicationQueryListener( + receiver, + props.getBrokerConfigProps().getQueriesQueue(), + resolver, + sender, + props.getBrokerConfigProps().getDirectMessagesExchangeName(), + converter, + props.getBrokerConfigProps().getGlobalReplyExchangeName(), + props.getWithDLQRetry(), + props.getCreateTopology(), + props.getMaxRetries(), + props.getRetryDelay(), + props.getGlobal().getMaxLengthBytes(), + props.getDirect().isDiscardTimeoutQueries(), + discardNotifier, + errorReporter); + + listener.startListener(); + } + } + + @Override + public void listenReplies() { + if (props.isListenReplies()) { + final ApplicationReplyListener replyListener = new ApplicationReplyListener(router, + receiver, + props.getBrokerConfigProps().getReplyQueue(), + props.getBrokerConfigProps().getGlobalReplyExchangeName(), + props.getCreateTopology() + ); + replyListener.startListening(config.getRoutingKey()); + } + } + + @Override + public Mono healthCheck() { + return healthIndicator.health(); + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactory.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactory.java new file mode 100644 index 00000000..59a2fa3b --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactory.java @@ -0,0 +1,59 @@ +package org.reactivecommons.async.rabbit; + +import io.micrometer.core.instrument.MeterRegistry; +import lombok.RequiredArgsConstructor; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.rabbit.discard.RabbitMQDiscardProviderFactory; +import org.reactivecommons.async.rabbit.health.RabbitReactiveHealthIndicator; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; +import org.springframework.stereotype.Service; + +@Service("rabbitmq") +@RequiredArgsConstructor +public class RabbitMQBrokerProviderFactory implements BrokerProviderFactory { + private final BrokerConfig config; + private final ReactiveReplyRouter router; + private final RabbitJacksonMessageConverter converter; + private final MeterRegistry meterRegistry; + private final CustomReporter errorReporter; + private final RabbitMQDiscardProviderFactory discardProvider; + + @Override + public String getBrokerType() { + return "rabbitmq"; + } + + @Override + public DiscardProvider getDiscardProvider(AsyncProps props) { + return discardProvider.build(props, config, converter); + } + + @Override + public BrokerProvider getProvider(String domain, AsyncProps props, DiscardProvider discardProvider) { + RabbitProperties properties = props.getConnectionProperties(); + ConnectionFactoryProvider provider = RabbitMQSetupUtils.connectionFactoryProvider(properties); + RabbitReactiveHealthIndicator healthIndicator = + new RabbitReactiveHealthIndicator(domain, provider.getConnectionFactory()); + ReactiveMessageSender sender = RabbitMQSetupUtils.createMessageSender(provider, props, converter); + ReactiveMessageListener listener = RabbitMQSetupUtils.createMessageListener(provider, props); + DiscardNotifier discardNotifier; + if (props.isUseDiscardNotifierPerDomain()) { + discardNotifier = RabbitMQSetupUtils.createDiscardNotifier(sender, props, config, converter); + } else { + discardNotifier = discardProvider.get(); + } + return new RabbitMQBrokerProvider(domain, props, config, router, converter, meterRegistry, errorReporter, + healthIndicator, listener, sender, discardNotifier); + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQSetupUtils.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQSetupUtils.java new file mode 100644 index 00000000..40142e83 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/RabbitMQSetupUtils.java @@ -0,0 +1,248 @@ +package org.reactivecommons.async.rabbit; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.java.Log; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.commons.DLQDiscardNotifier; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.communications.TopologyCreator; +import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.spring.RabbitPropertiesBase; +import org.springframework.boot.context.properties.PropertyMapper; +import reactor.core.publisher.Mono; +import reactor.rabbitmq.ChannelPool; +import reactor.rabbitmq.ChannelPoolFactory; +import reactor.rabbitmq.ChannelPoolOptions; +import reactor.rabbitmq.RabbitFlux; +import reactor.rabbitmq.Receiver; +import reactor.rabbitmq.ReceiverOptions; +import reactor.rabbitmq.Sender; +import reactor.rabbitmq.SenderOptions; +import reactor.rabbitmq.Utils; +import reactor.util.retry.Retry; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.time.Duration; +import java.util.Arrays; +import java.util.logging.Level; + +@Log +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class RabbitMQSetupUtils { + private static final String LISTENER_TYPE = "listener"; + private static final String SENDER_TYPE = "sender"; + private static final String DEFAULT_PROTOCOL; + public static final int START_INTERVAL = 300; + public static final int MAX_BACKOFF_INTERVAL = 3000; + + static { + String protocol = "TLSv1.1"; + try { + String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols(); + for (String prot : protocols) { + if ("TLSv1.2".equals(prot)) { + protocol = "TLSv1.2"; + break; + } + } + } catch (NoSuchAlgorithmException e) { + // nothing + } + DEFAULT_PROTOCOL = protocol; + } + + @SneakyThrows + public static ConnectionFactoryProvider connectionFactoryProvider(RabbitProperties properties) { + final ConnectionFactory factory = new ConnectionFactory(); + PropertyMapper map = PropertyMapper.get(); + map.from(properties::determineHost).whenNonNull().to(factory::setHost); + map.from(properties::determinePort).to(factory::setPort); + map.from(properties::determineUsername).whenNonNull().to(factory::setUsername); + map.from(properties::determinePassword).whenNonNull().to(factory::setPassword); + map.from(properties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost); + factory.useNio(); + setUpSSL(factory, properties); + return () -> factory; + } + + + public static ReactiveMessageSender createMessageSender(ConnectionFactoryProvider provider, + AsyncProps props, + MessageConverter converter) { + final Sender sender = RabbitFlux.createSender(reactiveCommonsSenderOptions(props.getAppName(), provider, + props.getConnectionProperties())); + return new ReactiveMessageSender(sender, props.getAppName(), converter, new TopologyCreator(sender)); + } + + public static ReactiveMessageListener createMessageListener(ConnectionFactoryProvider provider, AsyncProps props) { + final Mono connection = + createConnectionMono(provider.getConnectionFactory(), props.getAppName(), LISTENER_TYPE); + final Receiver receiver = RabbitFlux.createReceiver(new ReceiverOptions().connectionMono(connection)); + final Sender sender = RabbitFlux.createSender(new SenderOptions().connectionMono(connection)); + + return new ReactiveMessageListener(receiver, + new TopologyCreator(sender), + props.getFlux().getMaxConcurrency(), + props.getPrefetchCount()); + } + + public static TopologyCreator createTopologyCreator(AsyncProps props) { + ConnectionFactoryProvider provider = connectionFactoryProvider(props.getConnectionProperties()); + final Mono connection = createConnectionMono(provider.getConnectionFactory(), + props.getAppName(), LISTENER_TYPE); + final Sender sender = RabbitFlux.createSender(new SenderOptions().connectionMono(connection)); + return new TopologyCreator(sender); + } + + public static DiscardNotifier createDiscardNotifier(ReactiveMessageSender sender, AsyncProps props, + BrokerConfig brokerConfig, MessageConverter converter) { + DomainEventBus appDomainEventBus = new RabbitDomainEventBus(sender, + props.getBrokerConfigProps().getDomainEventsExchangeName(), brokerConfig); + return new DLQDiscardNotifier(appDomainEventBus, converter); + } + + private static SenderOptions reactiveCommonsSenderOptions(String appName, ConnectionFactoryProvider provider, + RabbitProperties rabbitProperties) { + final Mono senderConnection = createConnectionMono(provider.getConnectionFactory(), appName, + SENDER_TYPE); + final ChannelPoolOptions channelPoolOptions = new ChannelPoolOptions(); + final PropertyMapper map = PropertyMapper.get(); + + map.from(rabbitProperties.getCache().getChannel()::getSize).whenNonNull() + .to(channelPoolOptions::maxCacheSize); + + final ChannelPool channelPool = ChannelPoolFactory.createChannelPool( + senderConnection, + channelPoolOptions + ); + + return new SenderOptions() + .channelPool(channelPool) + .resourceManagementChannelMono(channelPool.getChannelMono() + .transform(Utils::cache)); + } + + private static Mono createConnectionMono(ConnectionFactory factory, String connectionPrefix, + String connectionType) { + return Mono.fromCallable(() -> factory.newConnection(connectionPrefix + " " + connectionType)) + .doOnError(err -> + log.log(Level.SEVERE, "Error creating connection to RabbitMQ Broker in host '" + + factory.getHost() + "'. Starting retry process...", err) + ) + .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(START_INTERVAL)) + .maxBackoff(Duration.ofMillis(MAX_BACKOFF_INTERVAL))) + .cache(); + } + + // SSL based on RabbitConnectionFactoryBean + // https://github.com/spring-projects/spring-amqp/blob/main/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitConnectionFactoryBean.java + + private static void setUpSSL(ConnectionFactory factory, RabbitProperties properties) + throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException, + CertificateException, IOException { + var ssl = properties.getSsl(); + if (ssl != null && ssl.isEnabled()) { + var keyManagers = configureKeyManagers(ssl); + var trustManagers = configureTrustManagers(ssl); + var secureRandom = SecureRandom.getInstanceStrong(); + + if (log.isLoggable(Level.FINE)) { + log.fine("Initializing SSLContext with KM: " + Arrays.toString(keyManagers) + + ", TM: " + Arrays.toString(trustManagers) + ", random: " + secureRandom); + } + var context = createSSLContext(ssl); + context.init(keyManagers, trustManagers, secureRandom); + factory.useSslProtocol(context); + + logDetails(trustManagers); + + if (ssl.isVerifyHostname()) { + factory.enableHostnameVerification(); + } + } + } + + private static KeyManager[] configureKeyManagers(RabbitPropertiesBase.Ssl ssl) throws KeyStoreException, + IOException, + NoSuchAlgorithmException, + CertificateException, UnrecoverableKeyException { + KeyManager[] keyManagers = null; + if (ssl.getKeyStore() != null) { + var ks = KeyStore.getInstance(ssl.getKeyStoreType()); + char[] keyPassphrase = null; + if (ssl.getKeyStorePassword() != null) { + keyPassphrase = ssl.getKeyStorePassword().toCharArray(); + } + try (var inputStream = new FileInputStream(ssl.getKeyStore())) { + ks.load(inputStream, keyPassphrase); + } + var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, keyPassphrase); + keyManagers = kmf.getKeyManagers(); + } + return keyManagers; + } + + private static TrustManager[] configureTrustManagers(RabbitPropertiesBase.Ssl ssl) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + KeyStore tks = null; + if (ssl.getTrustStore() != null) { + tks = KeyStore.getInstance(ssl.getTrustStoreType()); + char[] trustPassphrase = null; + if (ssl.getTrustStorePassword() != null) { + trustPassphrase = ssl.getTrustStorePassword().toCharArray(); + } + try (InputStream inputStream = new FileInputStream(ssl.getTrustStore())) { + tks.load(inputStream, trustPassphrase); + } + } + + var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(tks); + return tmf.getTrustManagers(); + } + + private static SSLContext createSSLContext(RabbitPropertiesBase.Ssl ssl) throws NoSuchAlgorithmException { + return SSLContext.getInstance(ssl.getAlgorithm() != null ? ssl.getAlgorithm() : DEFAULT_PROTOCOL); + } + + private static void logDetails(TrustManager[] managers) { + var found = false; + for (var trustManager : managers) { + if (trustManager instanceof X509TrustManager) { + found = true; + var x509TrustManager = (X509TrustManager) trustManager; + log.info("Loaded " + x509TrustManager.getAcceptedIssuers().length + " accepted issuers for rabbitmq"); + } + } + if (!found) { + log.warning("No X509TrustManager found in the truststore."); + } + } + +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java new file mode 100644 index 00000000..8706565a --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java @@ -0,0 +1,6 @@ +package org.reactivecommons.async.rabbit.config; + +import org.reactivecommons.async.rabbit.config.spring.RabbitPropertiesBase; + +public class RabbitProperties extends RabbitPropertiesBase { +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesAutoConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesAutoConfig.java new file mode 100644 index 00000000..399b6c20 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/RabbitPropertiesAutoConfig.java @@ -0,0 +1,8 @@ +package org.reactivecommons.async.rabbit.config; + +import org.reactivecommons.async.rabbit.config.spring.RabbitPropertiesBase; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "spring.rabbitmq") +public class RabbitPropertiesAutoConfig extends RabbitPropertiesBase { +} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java similarity index 81% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java index f9d2ea29..3ed6a8b7 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncProps.java @@ -5,8 +5,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.experimental.SuperBuilder; import org.reactivecommons.async.commons.config.IBrokerConfigProps; import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.starter.props.GenericAsyncProps; import org.springframework.boot.context.properties.NestedConfigurationProperty; @@ -14,11 +16,8 @@ @Setter @AllArgsConstructor @NoArgsConstructor -@Builder -public class AsyncProps { - private String appName; - private String secret; - +@SuperBuilder +public class AsyncProps extends GenericAsyncProps { @NestedConfigurationProperty @Builder.Default private FluxProps flux = new FluxProps(); @@ -64,4 +63,13 @@ public class AsyncProps { @Builder.Default private Boolean createTopology = true; // auto delete queues will always be created and bound + @Builder.Default + private boolean useDiscardNotifierPerDomain = false; + + @Builder.Default + private boolean enabled = true; + + @Builder.Default + private String brokerType = "rabbitmq"; + } diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java new file mode 100644 index 00000000..d72aaff8 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncPropsDomain.java @@ -0,0 +1,40 @@ +package org.reactivecommons.async.rabbit.config.props; + +import lombok.Getter; +import lombok.Setter; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomain; +import org.springframework.beans.factory.annotation.Value; + +import java.lang.reflect.Constructor; + +@Getter +@Setter +public class AsyncPropsDomain extends GenericAsyncPropsDomain { + public AsyncPropsDomain(@Value("${spring.application.name}") String defaultAppName, + RabbitProperties defaultRabbitProperties, + AsyncRabbitPropsDomainProperties configured, + RabbitSecretFiller secretFiller) { + super(defaultAppName, defaultRabbitProperties, configured, secretFiller, AsyncProps.class, + RabbitProperties.class); + } + + @SuppressWarnings("unchecked") + public static AsyncPropsDomainBuilder builder() { + return GenericAsyncPropsDomain.builder(RabbitProperties.class, + AsyncRabbitPropsDomainProperties.class, + (Constructor) AsyncPropsDomain.class.getDeclaredConstructors()[0]); + } + + @Override + protected void fillCustoms(AsyncProps asyncProps) { + if (asyncProps.getBrokerConfigProps() == null) { + asyncProps.setBrokerConfigProps(new BrokerConfigProps(asyncProps)); + } + } + + public interface RabbitSecretFiller extends SecretFiller { + } + +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncRabbitPropsDomainProperties.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncRabbitPropsDomainProperties.java new file mode 100644 index 00000000..44fe7f0a --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/AsyncRabbitPropsDomainProperties.java @@ -0,0 +1,22 @@ +package org.reactivecommons.async.rabbit.config.props; + +import lombok.NoArgsConstructor; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.starter.props.GenericAsyncPropsDomainProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.Map; + +@NoArgsConstructor +@ConfigurationProperties(prefix = "app.async") +public class AsyncRabbitPropsDomainProperties extends GenericAsyncPropsDomainProperties { + + public AsyncRabbitPropsDomainProperties(Map m) { + super(m); + } + + public static AsyncPropsDomainPropertiesBuilder builder() { + return GenericAsyncPropsDomainProperties.builder(AsyncRabbitPropsDomainProperties.class); + } +} diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java similarity index 97% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java index 735e4e2e..32ffd8c7 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/BrokerConfigProps.java @@ -68,9 +68,8 @@ private String resolveTemporaryQueue(AtomicReference property, String su final String replyName = generateNameFrom(getAppName(), suffix); if (property.compareAndSet(null, replyName)) { return replyName; - } else { - return property.get(); } + return property.get(); } return name; } diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DirectProps.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/DomainProps.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/EventsProps.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/FluxProps.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java similarity index 100% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/props/GlobalProps.java diff --git a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/spring/RabbitPropertiesBase.java similarity index 56% rename from async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java rename to starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/spring/RabbitPropertiesBase.java index 1ed9934e..19d0c341 100644 --- a/async/async-rabbit-starter-eda/src/main/java/org/reactivecommons/async/rabbit/config/RabbitProperties.java +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/config/spring/RabbitPropertiesBase.java @@ -1,6 +1,7 @@ -package org.reactivecommons.async.rabbit.config; +package org.reactivecommons.async.rabbit.config.spring; -import org.springframework.boot.context.properties.ConfigurationProperties; +import lombok.Getter; +import lombok.Setter; import org.springframework.boot.convert.DurationUnit; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -9,89 +10,106 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -@ConfigurationProperties(prefix = "spring.rabbitmq") -public class RabbitProperties { +public class RabbitPropertiesBase { /** * RabbitMQ host. */ + @Setter + @Getter private String host = "localhost"; /** * RabbitMQ port. */ + @Setter + @Getter private int port = 5672; /** * Login user to authenticate to the communications. */ + @Setter + @Getter private String username = "guest"; /** * Login to authenticate against the communications. */ - private String password = "guest"; + @Setter + @Getter + private String password = "guest"; //NOSONAR /** * SSL configuration. */ + @Getter private final Ssl ssl = new Ssl(); /** * Virtual host to use when connecting to the communications. */ + @Getter private String virtualHost; /** * Comma-separated list of addresses to which the client should connect. */ + @Getter private String addresses; /** * Requested heartbeat timeout; zero for none. If a duration suffix is not specified, * seconds will be used. */ + @Getter + @Setter @DurationUnit(ChronoUnit.SECONDS) private Duration requestedHeartbeat; /** * Whether to enable publisher confirms. */ + @Setter + @Getter private boolean publisherConfirms; /** * Whether to enable publisher returns. */ + @Setter + @Getter private boolean publisherReturns; /** * Connection timeout. Set it to zero to wait forever. */ + @Getter + @Setter private Duration connectionTimeout; /** * Cache configuration. */ + @Getter private final Cache cache = new Cache(); /** * Listener container configuration. */ + @Getter private final Listener listener = new Listener(); + @Getter private final Template template = new Template(); private List

parsedAddresses; - public String getHost() { - return this.host; - } - /** * Returns the host from the first address, or the configured host if no addresses * have been set. + * * @return the host * @see #setAddresses(String) * @see #getHost() @@ -103,17 +121,10 @@ public String determineHost() { return this.parsedAddresses.get(0).host; } - public void setHost(String host) { - this.host = host; - } - - public int getPort() { - return this.port; - } - /** * Returns the port from the first address, or the configured port if no addresses * have been set. + * * @return the port * @see #setAddresses(String) * @see #getPort() @@ -126,17 +137,10 @@ public int determinePort() { return address.port; } - public void setPort(int port) { - this.port = port; - } - - public String getAddresses() { - return this.addresses; - } - /** * Returns the comma-separated addresses or a single address ({@code host:port}) * created from the configured host and port if no addresses have been set. + * * @return the addresses */ public String determineAddresses() { @@ -156,20 +160,17 @@ public void setAddresses(String addresses) { } private List
parseAddresses(String addresses) { - List
parsedAddresses = new ArrayList<>(); + List
parsedAddressesLocal = new ArrayList<>(); for (String address : StringUtils.commaDelimitedListToStringArray(addresses)) { - parsedAddresses.add(new Address(address)); + parsedAddressesLocal.add(new Address(address)); } - return parsedAddresses; - } - - public String getUsername() { - return this.username; + return parsedAddressesLocal; } /** * If addresses have been set and the first address has a username it is returned. * Otherwise returns the result of calling {@code getUsername()}. + * * @return the username * @see #setAddresses(String) * @see #getUsername() @@ -182,17 +183,10 @@ public String determineUsername() { return (address.username != null) ? address.username : this.username; } - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return this.password; - } - /** * If addresses have been set and the first address has a password it is returned. * Otherwise returns the result of calling {@code getPassword()}. + * * @return the password or {@code null} * @see #setAddresses(String) * @see #getPassword() @@ -205,21 +199,10 @@ public String determinePassword() { return (address.password != null) ? address.password : getPassword(); } - public void setPassword(String password) { - this.password = password; - } - - public Ssl getSsl() { - return this.ssl; - } - - public String getVirtualHost() { - return this.virtualHost; - } - /** * If addresses have been set and the first address has a virtual host it is returned. * Otherwise returns the result of calling {@code getVirtualHost()}. + * * @return the virtual host or {@code null} * @see #setAddresses(String) * @see #getVirtualHost() @@ -236,50 +219,8 @@ public void setVirtualHost(String virtualHost) { this.virtualHost = "".equals(virtualHost) ? "/" : virtualHost; } - public Duration getRequestedHeartbeat() { - return this.requestedHeartbeat; - } - - public void setRequestedHeartbeat(Duration requestedHeartbeat) { - this.requestedHeartbeat = requestedHeartbeat; - } - - public boolean isPublisherConfirms() { - return this.publisherConfirms; - } - - public void setPublisherConfirms(boolean publisherConfirms) { - this.publisherConfirms = publisherConfirms; - } - - public boolean isPublisherReturns() { - return this.publisherReturns; - } - - public void setPublisherReturns(boolean publisherReturns) { - this.publisherReturns = publisherReturns; - } - - public Duration getConnectionTimeout() { - return this.connectionTimeout; - } - - public void setConnectionTimeout(Duration connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - public Cache getCache() { - return this.cache; - } - - public Listener getListener() { - return this.listener; - } - - public Template getTemplate() { - return this.template; - } - + @Getter + @Setter public static class Ssl { /** @@ -332,102 +273,17 @@ public static class Ssl { */ private boolean verifyHostname = true; - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getKeyStore() { - return this.keyStore; - } - - public void setKeyStore(String keyStore) { - this.keyStore = keyStore; - } - - public String getKeyStoreType() { - return this.keyStoreType; - } - - public void setKeyStoreType(String keyStoreType) { - this.keyStoreType = keyStoreType; - } - - public String getKeyStorePassword() { - return this.keyStorePassword; - } - - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = keyStorePassword; - } - - public String getTrustStore() { - return this.trustStore; - } - - public void setTrustStore(String trustStore) { - this.trustStore = trustStore; - } - - public String getTrustStoreType() { - return this.trustStoreType; - } - - public void setTrustStoreType(String trustStoreType) { - this.trustStoreType = trustStoreType; - } - - public String getTrustStorePassword() { - return this.trustStorePassword; - } - - public void setTrustStorePassword(String trustStorePassword) { - this.trustStorePassword = trustStorePassword; - } - - public String getAlgorithm() { - return this.algorithm; - } - - public void setAlgorithm(String sslAlgorithm) { - this.algorithm = sslAlgorithm; - } - - public boolean isValidateServerCertificate() { - return this.validateServerCertificate; - } - - public void setValidateServerCertificate(boolean validateServerCertificate) { - this.validateServerCertificate = validateServerCertificate; - } - - public boolean getVerifyHostname() { - return this.verifyHostname; - } - - public void setVerifyHostname(boolean verifyHostname) { - this.verifyHostname = verifyHostname; - } - } + @Getter public static class Cache { - private final Cache.Channel channel = new Cache.Channel(); - - private final Cache.Connection connection = new Cache.Connection(); + private final Channel channel = new Channel(); - public Cache.Channel getChannel() { - return this.channel; - } - - public Cache.Connection getConnection() { - return this.connection; - } + private final Connection connection = new Connection(); + @Getter + @Setter public static class Channel { /** @@ -442,24 +298,10 @@ public static class Channel { */ private Duration checkoutTimeout; - public Integer getSize() { - return this.size; - } - - public void setSize(Integer size) { - this.size = size; - } - - public Duration getCheckoutTimeout() { - return this.checkoutTimeout; - } - - public void setCheckoutTimeout(Duration checkoutTimeout) { - this.checkoutTimeout = checkoutTimeout; - } - } + @Getter + @Setter public static class Connection { /** @@ -472,22 +314,6 @@ public static class Connection { */ private Integer size; - public String getMode() { - return this.mode; - } - - public void setMode(String mode) { - this.mode = mode; - } - - public Integer getSize() { - return this.size; - } - - public void setSize(Integer size) { - this.size = size; - } - } } @@ -507,6 +333,9 @@ public enum ContainerType { } + + @Getter + @Setter public static class Listener { /** @@ -518,24 +347,10 @@ public static class Listener { private final DirectContainer direct = new DirectContainer(); - public ContainerType getType() { - return this.type; - } - - public void setType(ContainerType containerType) { - this.type = containerType; - } - - public SimpleContainer getSimple() { - return this.simple; - } - - public DirectContainer getDirect() { - return this.direct; - } - } + @Getter + @Setter public abstract static class AmqpContainer { /** @@ -569,57 +384,16 @@ public abstract static class AmqpContainer { */ private final ListenerRetry retry = new ListenerRetry(); - public boolean isAutoStartup() { - return this.autoStartup; - } - - public void setAutoStartup(boolean autoStartup) { - this.autoStartup = autoStartup; - } - - public Integer getAcknowledgeMode() { - return this.acknowledgeMode; - } - - public void setAcknowledgeMode(Integer acknowledgeMode) { - this.acknowledgeMode = acknowledgeMode; - } - - public Integer getPrefetch() { - return this.prefetch; - } - - public void setPrefetch(Integer prefetch) { - this.prefetch = prefetch; - } - - public Boolean getDefaultRequeueRejected() { - return this.defaultRequeueRejected; - } - - public void setDefaultRequeueRejected(Boolean defaultRequeueRejected) { - this.defaultRequeueRejected = defaultRequeueRejected; - } - - public Duration getIdleEventInterval() { - return this.idleEventInterval; - } - - public void setIdleEventInterval(Duration idleEventInterval) { - this.idleEventInterval = idleEventInterval; - } public abstract boolean isMissingQueuesFatal(); - public ListenerRetry getRetry() { - return this.retry; - } - } /** * Configuration properties for {@code SimpleMessageListenerContainer}. */ + @Getter + @Setter public static class SimpleContainer extends AmqpContainer { /** @@ -645,44 +419,13 @@ public static class SimpleContainer extends AmqpContainer { */ private boolean missingQueuesFatal = true; - public Integer getConcurrency() { - return this.concurrency; - } - - public void setConcurrency(Integer concurrency) { - this.concurrency = concurrency; - } - - public Integer getMaxConcurrency() { - return this.maxConcurrency; - } - - public void setMaxConcurrency(Integer maxConcurrency) { - this.maxConcurrency = maxConcurrency; - } - - public Integer getTransactionSize() { - return this.transactionSize; - } - - public void setTransactionSize(Integer transactionSize) { - this.transactionSize = transactionSize; - } - - @Override - public boolean isMissingQueuesFatal() { - return this.missingQueuesFatal; - } - - public void setMissingQueuesFatal(boolean missingQueuesFatal) { - this.missingQueuesFatal = missingQueuesFatal; - } - } /** * Configuration properties for {@code DirectMessageListenerContainer}. */ + @Getter + @Setter public static class DirectContainer extends AmqpContainer { /** @@ -696,25 +439,10 @@ public static class DirectContainer extends AmqpContainer { */ private boolean missingQueuesFatal = false; - public Integer getConsumersPerQueue() { - return this.consumersPerQueue; - } - - public void setConsumersPerQueue(Integer consumersPerQueue) { - this.consumersPerQueue = consumersPerQueue; - } - - @Override - public boolean isMissingQueuesFatal() { - return this.missingQueuesFatal; - } - - public void setMissingQueuesFatal(boolean missingQueuesFatal) { - this.missingQueuesFatal = missingQueuesFatal; - } - } + @Getter + @Setter public static class Template { private final Retry retry = new Retry(); @@ -750,60 +478,10 @@ public static class Template { */ private String queue; - public Retry getRetry() { - return this.retry; - } - - public Boolean getMandatory() { - return this.mandatory; - } - - public void setMandatory(Boolean mandatory) { - this.mandatory = mandatory; - } - - public Duration getReceiveTimeout() { - return this.receiveTimeout; - } - - public void setReceiveTimeout(Duration receiveTimeout) { - this.receiveTimeout = receiveTimeout; - } - - public Duration getReplyTimeout() { - return this.replyTimeout; - } - - public void setReplyTimeout(Duration replyTimeout) { - this.replyTimeout = replyTimeout; - } - - public String getExchange() { - return this.exchange; - } - - public void setExchange(String exchange) { - this.exchange = exchange; - } - - public String getRoutingKey() { - return this.routingKey; - } - - public void setRoutingKey(String routingKey) { - this.routingKey = routingKey; - } - - public String getQueue() { - return this.queue; - } - - public void setQueue(String queue) { - this.queue = queue; - } - } + @Getter + @Setter public static class Retry { /** @@ -831,63 +509,16 @@ public static class Retry { */ private Duration maxInterval = Duration.ofMillis(10000); - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public int getMaxAttempts() { - return this.maxAttempts; - } - - public void setMaxAttempts(int maxAttempts) { - this.maxAttempts = maxAttempts; - } - - public Duration getInitialInterval() { - return this.initialInterval; - } - - public void setInitialInterval(Duration initialInterval) { - this.initialInterval = initialInterval; - } - - public double getMultiplier() { - return this.multiplier; - } - - public void setMultiplier(double multiplier) { - this.multiplier = multiplier; - } - - public Duration getMaxInterval() { - return this.maxInterval; - } - - public void setMaxInterval(Duration maxInterval) { - this.maxInterval = maxInterval; - } - } + @Getter + @Setter public static class ListenerRetry extends Retry { /** * Whether retries are stateless or stateful. */ private boolean stateless = true; - - public boolean isStateless() { - return this.stateless; - } - - public void setStateless(boolean stateless) { - this.stateless = stateless; - } - } private static final class Address { @@ -955,10 +586,9 @@ private void parseHostAndPort(String input) { if (portIndex == -1) { this.host = input; this.port = DEFAULT_PORT; - } - else { + } else { this.host = input.substring(0, portIndex); - this.port = Integer.valueOf(input.substring(portIndex + 1)); + this.port = Integer.parseInt(input.substring(portIndex + 1)); } } diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/discard/RabbitMQDiscardProviderConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/discard/RabbitMQDiscardProviderConfig.java new file mode 100644 index 00000000..e64136d9 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/discard/RabbitMQDiscardProviderConfig.java @@ -0,0 +1,15 @@ +package org.reactivecommons.async.rabbit.discard; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RabbitMQDiscardProviderConfig { + + @Bean + @ConditionalOnMissingBean(RabbitMQDiscardProviderFactory.class) + public RabbitMQDiscardProviderFactory defaultRabbitMQDiscardProviderFactory() { + return RabbitMQDiscardProviderImpl::new; + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/discard/RabbitMQDiscardProviderFactory.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/discard/RabbitMQDiscardProviderFactory.java new file mode 100644 index 00000000..62bf9fb3 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/discard/RabbitMQDiscardProviderFactory.java @@ -0,0 +1,10 @@ +package org.reactivecommons.async.rabbit.discard; + +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.starter.broker.DiscardProvider; + +public interface RabbitMQDiscardProviderFactory { + DiscardProvider build(AsyncProps props, BrokerConfig config, MessageConverter converter); +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/discard/RabbitMQDiscardProviderImpl.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/discard/RabbitMQDiscardProviderImpl.java new file mode 100644 index 00000000..c7ba46d7 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/discard/RabbitMQDiscardProviderImpl.java @@ -0,0 +1,35 @@ +package org.reactivecommons.async.rabbit.discard; + +import lombok.RequiredArgsConstructor; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.converters.MessageConverter; +import org.reactivecommons.async.rabbit.RabbitMQSetupUtils; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.starter.broker.DiscardProvider; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@RequiredArgsConstructor +public class RabbitMQDiscardProviderImpl implements DiscardProvider { + private final AsyncProps props; + private final BrokerConfig config; + private final MessageConverter converter; + private final Map discardNotifier = new ConcurrentHashMap<>(); + + @Override + public DiscardNotifier get() { + return discardNotifier.computeIfAbsent(true, this::buildDiscardNotifier); + } + + private DiscardNotifier buildDiscardNotifier(boolean ignored) { + RabbitProperties properties = props.getConnectionProperties(); + ConnectionFactoryProvider provider = RabbitMQSetupUtils.connectionFactoryProvider(properties); + ReactiveMessageSender sender = RabbitMQSetupUtils.createMessageSender(provider, props, converter); + return RabbitMQSetupUtils.createDiscardNotifier(sender, props, config, converter); + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitMQHealthException.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitMQHealthException.java new file mode 100644 index 00000000..a0255f4a --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitMQHealthException.java @@ -0,0 +1,7 @@ +package org.reactivecommons.async.rabbit.health; + +public class RabbitMQHealthException extends RuntimeException { + public RabbitMQHealthException(Throwable throwable) { + super(throwable); + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java new file mode 100644 index 00000000..a80d5f1a --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicator.java @@ -0,0 +1,53 @@ +package org.reactivecommons.async.rabbit.health; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import lombok.SneakyThrows; +import lombok.extern.log4j.Log4j2; +import org.reactivecommons.async.starter.config.health.RCHealth; +import org.reactivecommons.async.starter.config.health.RCHealthIndicator; +import reactor.core.publisher.Mono; + +import java.net.SocketException; + +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.DOMAIN; +import static org.reactivecommons.async.starter.config.health.ReactiveCommonsHealthIndicator.VERSION; + +@Log4j2 +public class RabbitReactiveHealthIndicator extends RCHealthIndicator { + private final String domain; + private final ConnectionFactory connectionFactory; + + public RabbitReactiveHealthIndicator(String domain, ConnectionFactory connectionFactory) { + this.domain = domain; + this.connectionFactory = connectionFactory.clone(); + this.connectionFactory.useBlockingIo(); + } + + @Override + public Mono doHealthCheck(RCHealth.RCHealthBuilder builder) { + builder.withDetail(DOMAIN, domain); + return Mono.fromCallable(() -> getRawVersion(connectionFactory)) + .map(status -> builder.up().withDetail(VERSION, status).build()); + } + + @SneakyThrows + private String getRawVersion(ConnectionFactory factory) { + Connection connection = null; + try { + connection = factory.newConnection(); + return connection.getServerProperties().get(VERSION).toString(); + } catch (SocketException e) { + log.warn("Identified error", e); + throw new RabbitMQHealthException(e); + } finally { + if (connection != null) { + try { + connection.close(); + } catch (Exception e) { + log.error("Error closing health connection", e); + } + } + } + } +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/common/rabbit/RabbitMQConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/common/rabbit/RabbitMQConfig.java new file mode 100644 index 00000000..40a127f4 --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/common/rabbit/RabbitMQConfig.java @@ -0,0 +1,46 @@ +package org.reactivecommons.async.starter.impl.common.rabbit; + +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import org.reactivecommons.async.commons.converters.json.ObjectMapperSupplier; +import org.reactivecommons.async.rabbit.RabbitMQBrokerProviderFactory; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.RabbitPropertiesAutoConfig; +import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; +import org.reactivecommons.async.rabbit.config.props.AsyncRabbitPropsDomainProperties; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.rabbit.discard.RabbitMQDiscardProviderConfig; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Log +@Configuration +@RequiredArgsConstructor +@EnableConfigurationProperties({RabbitPropertiesAutoConfig.class, AsyncRabbitPropsDomainProperties.class}) +@Import({AsyncPropsDomain.class, RabbitMQBrokerProviderFactory.class, RabbitMQDiscardProviderConfig.class}) +public class RabbitMQConfig { + + @Bean + @ConditionalOnMissingBean(RabbitJacksonMessageConverter.class) + public RabbitJacksonMessageConverter messageConverter(ObjectMapperSupplier objectMapperSupplier) { + return new RabbitJacksonMessageConverter(objectMapperSupplier.get()); + } + + @Bean + @ConditionalOnMissingBean(AsyncPropsDomain.RabbitSecretFiller.class) + public AsyncPropsDomain.RabbitSecretFiller defaultRabbitSecretFiller() { + return (ignoredDomain, ignoredProps) -> { + }; + } + + @Bean + @ConditionalOnMissingBean(RabbitProperties.class) + public RabbitProperties defaultRabbitProperties(RabbitPropertiesAutoConfig properties, + ObjectMapperSupplier supplier) { + return supplier.get().convertValue(properties, RabbitProperties.class); + } + +} diff --git a/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/listener/rabbit/RabbitMQListenerOnlyConfig.java b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/listener/rabbit/RabbitMQListenerOnlyConfig.java new file mode 100644 index 00000000..cebe207e --- /dev/null +++ b/starters/async-rabbit-starter/src/main/java/org/reactivecommons/async/starter/impl/listener/rabbit/RabbitMQListenerOnlyConfig.java @@ -0,0 +1,29 @@ +package org.reactivecommons.async.starter.impl.listener.rabbit; + +import org.reactivecommons.async.api.DynamicRegistry; +import org.reactivecommons.async.commons.config.IBrokerConfigProps; +import org.reactivecommons.async.rabbit.DynamicRegistryImp; +import org.reactivecommons.async.rabbit.RabbitMQSetupUtils; +import org.reactivecommons.async.rabbit.communications.TopologyCreator; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; +import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; +import org.reactivecommons.async.starter.config.DomainHandlers; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@Configuration +public class RabbitMQListenerOnlyConfig { + + @Bean + @ConditionalOnMissingBean(DynamicRegistry.class) + public DynamicRegistry dynamicRegistry(AsyncPropsDomain asyncPropsDomain, DomainHandlers handlers) { + AsyncProps props = asyncPropsDomain.getProps(DEFAULT_DOMAIN); + TopologyCreator topologyCreator = RabbitMQSetupUtils.createTopologyCreator(props); + IBrokerConfigProps brokerConfigProps = new BrokerConfigProps(asyncPropsDomain.getProps(DEFAULT_DOMAIN)); + return new DynamicRegistryImp(handlers.get(DEFAULT_DOMAIN), topologyCreator, brokerConfigProps); + } +} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactoryTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactoryTest.java new file mode 100644 index 00000000..28448222 --- /dev/null +++ b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderFactoryTest.java @@ -0,0 +1,77 @@ +package org.reactivecommons.async.rabbit; + +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.config.IBrokerConfigProps; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.rabbit.discard.RabbitMQDiscardProviderFactory; +import org.reactivecommons.async.rabbit.discard.RabbitMQDiscardProviderImpl; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.broker.BrokerProviderFactory; +import org.reactivecommons.async.starter.broker.DiscardProvider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(MockitoExtension.class) +class RabbitMQBrokerProviderFactoryTest { + private final BrokerConfig config = new BrokerConfig(); + private final ReactiveReplyRouter router = new ReactiveReplyRouter(); + @Mock + private RabbitJacksonMessageConverter converter; + @Mock + private MeterRegistry meterRegistry; + @Mock + private CustomReporter errorReporter; + + private BrokerProviderFactory providerFactory; + + @BeforeEach + public void setUp() { + providerFactory = new RabbitMQBrokerProviderFactory(config, router, converter, meterRegistry, errorReporter, + RabbitMQDiscardProviderImpl::new); + } + + @Test + void shouldReturnBrokerType() { + // Arrange + // Act + String brokerType = providerFactory.getBrokerType(); + // Assert + assertEquals("rabbitmq", brokerType); + } + + @Test + void shouldReturnCreateDiscardProvider() { + // Arrange + AsyncProps props = new AsyncProps(); + // Act + DiscardProvider discardProvider = providerFactory.getDiscardProvider(props); + // Assert + assertThat(discardProvider).isInstanceOf(RabbitMQDiscardProviderImpl.class); + } + + @Test + void shouldReturnBrokerProvider() { + // Arrange + AsyncProps props = new AsyncProps(); + props.setConnectionProperties(new RabbitProperties()); + IBrokerConfigProps brokerConfigProps = new BrokerConfigProps(props); + props.setBrokerConfigProps(brokerConfigProps); + DiscardProvider discardProvider = providerFactory.getDiscardProvider(props); + // Act + BrokerProvider brokerProvider = providerFactory.getProvider("domain", props, discardProvider); + // Assert + assertThat(brokerProvider).isInstanceOf(RabbitMQBrokerProvider.class); + } +} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderTest.java new file mode 100644 index 00000000..eecccfd0 --- /dev/null +++ b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQBrokerProviderTest.java @@ -0,0 +1,207 @@ +package org.reactivecommons.async.rabbit; + +import com.rabbitmq.client.AMQP; +import io.micrometer.core.instrument.MeterRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.api.domain.DomainEventBus; +import org.reactivecommons.async.api.DirectAsyncGateway; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.HandlerResolver; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.config.IBrokerConfigProps; +import org.reactivecommons.async.commons.ext.CustomReporter; +import org.reactivecommons.async.commons.reply.ReactiveReplyRouter; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageListener; +import org.reactivecommons.async.rabbit.communications.ReactiveMessageSender; +import org.reactivecommons.async.rabbit.communications.TopologyCreator; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.rabbit.health.RabbitReactiveHealthIndicator; +import org.reactivecommons.async.starter.broker.BrokerProvider; +import org.reactivecommons.async.starter.config.health.RCHealth; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.rabbitmq.BindingSpecification; +import reactor.rabbitmq.ExchangeSpecification; +import reactor.rabbitmq.QueueSpecification; +import reactor.rabbitmq.Receiver; +import reactor.test.StepVerifier; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class RabbitMQBrokerProviderTest { + private final AsyncProps props = new AsyncProps(); + private final BrokerConfig brokerConfig = new BrokerConfig(); + @Mock + private ReactiveMessageListener listener; + @Mock + private TopologyCreator creator; + @Mock + private HandlerResolver handlerResolver; + @Mock + private RabbitJacksonMessageConverter messageConverter; + @Mock + private CustomReporter customReporter; + @Mock + private Receiver receiver; + @Mock + private ReactiveReplyRouter router; + @Mock + private MeterRegistry meterRegistry; + @Mock + private ReactiveMessageSender sender; + @Mock + private DiscardNotifier discardNotifier; + @Mock + private RabbitReactiveHealthIndicator healthIndicator; + + + private BrokerProvider brokerProvider; + + + @BeforeEach + public void init() { + IBrokerConfigProps configProps = new BrokerConfigProps(props); + props.setBrokerConfigProps(configProps); + props.setAppName("test"); + brokerProvider = new RabbitMQBrokerProvider(DEFAULT_DOMAIN, + props, + brokerConfig, + router, + messageConverter, + meterRegistry, + customReporter, + healthIndicator, + listener, + sender, + discardNotifier); + } + + @Test + void shouldCreateDomainEventBus() { + when(sender.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + // Act + DomainEventBus domainBus = brokerProvider.getDomainBus(); + // Assert + assertThat(domainBus).isExactlyInstanceOf(RabbitDomainEventBus.class); + } + + @Test + void shouldCreateDirectAsyncGateway() { + when(sender.getTopologyCreator()).thenReturn(creator); + when(listener.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + when(creator.bind(any(BindingSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); + when(creator.declare(any(QueueSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); + when(listener.getReceiver()).thenReturn(receiver); + when(receiver.consumeAutoAck(any(String.class))).thenReturn(Flux.never()); + // Act + DirectAsyncGateway domainBus = brokerProvider.getDirectAsyncGateway(); + // Assert + assertThat(domainBus).isExactlyInstanceOf(RabbitDirectAsyncGateway.class); + } + + @Test + void shouldListenDomainEvents() { + when(listener.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + when(creator.declareQueue(any(String.class), any())).thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); + when(listener.getReceiver()).thenReturn(receiver); + when(listener.getMaxConcurrency()).thenReturn(1); + when(receiver.consumeManualAck(any(String.class), any())).thenReturn(Flux.never()); + // Act + brokerProvider.listenDomainEvents(handlerResolver); + // Assert + verify(receiver, times(1)).consumeManualAck(any(String.class), any()); + } + + @Test + @SuppressWarnings({"rawtypes", "unchecked"}) + void shouldListenNotificationEvents() { + when(handlerResolver.hasNotificationListeners()).thenReturn(true); + when(listener.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + when(creator.declare(any(QueueSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); + when(listener.getReceiver()).thenReturn(receiver); + when(listener.getMaxConcurrency()).thenReturn(1); + when(receiver.consumeManualAck(any(String.class), any())).thenReturn(Flux.never()); + List mockedListeners = spy(List.of()); + when(handlerResolver.getNotificationListeners()).thenReturn(mockedListeners); + // Act + brokerProvider.listenNotificationEvents(handlerResolver); + // Assert + verify(receiver, times(1)).consumeManualAck(any(String.class), any()); + } + + @Test + void shouldListenCommands() { + when(handlerResolver.hasCommandHandlers()).thenReturn(true); + when(listener.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + when(creator.declareQueue(any(String.class), any())) + .thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); + when(creator.bind(any(BindingSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); + when(listener.getReceiver()).thenReturn(receiver); + when(listener.getMaxConcurrency()).thenReturn(1); + when(receiver.consumeManualAck(any(String.class), any())).thenReturn(Flux.never()); + // Act + brokerProvider.listenCommands(handlerResolver); + // Assert + verify(receiver, times(1)).consumeManualAck(any(String.class), any()); + } + + @Test + void shouldListenQueries() { + when(handlerResolver.hasQueryHandlers()).thenReturn(true); + when(listener.getTopologyCreator()).thenReturn(creator); + when(creator.declare(any(ExchangeSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Exchange.DeclareOk.class))); + when(creator.declareQueue(any(String.class), any())) + .thenReturn(Mono.just(mock(AMQP.Queue.DeclareOk.class))); + when(creator.bind(any(BindingSpecification.class))) + .thenReturn(Mono.just(mock(AMQP.Queue.BindOk.class))); + when(listener.getReceiver()).thenReturn(receiver); + when(listener.getMaxConcurrency()).thenReturn(1); + when(receiver.consumeManualAck(any(String.class), any())).thenReturn(Flux.never()); + // Act + brokerProvider.listenQueries(handlerResolver); + // Assert + verify(receiver, times(1)).consumeManualAck(any(String.class), any()); + } + + @Test + void shouldProxyHealthCheck() { + when(healthIndicator.health()).thenReturn(Mono.fromSupplier(() -> RCHealth.builder().up().build())); + // Act + Mono flow = brokerProvider.healthCheck(); + // Assert + StepVerifier.create(flow) + .expectNextMatches(health -> health.getStatus().equals(RCHealth.Status.UP)) + .verifyComplete(); + } +} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQDiscardProviderImplTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQDiscardProviderImplTest.java new file mode 100644 index 00000000..c2ce4338 --- /dev/null +++ b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/RabbitMQDiscardProviderImplTest.java @@ -0,0 +1,38 @@ +package org.reactivecommons.async.rabbit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.commons.DLQDiscardNotifier; +import org.reactivecommons.async.commons.DiscardNotifier; +import org.reactivecommons.async.commons.config.BrokerConfig; +import org.reactivecommons.async.commons.config.IBrokerConfigProps; +import org.reactivecommons.async.rabbit.config.RabbitProperties; +import org.reactivecommons.async.rabbit.config.props.AsyncProps; +import org.reactivecommons.async.rabbit.config.props.BrokerConfigProps; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.rabbit.discard.RabbitMQDiscardProviderImpl; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class RabbitMQDiscardProviderImplTest { + @Mock + private RabbitJacksonMessageConverter converter; + + @Test + void shouldCreateDiscardNotifier() { + // Arrange + AsyncProps props = new AsyncProps(); + props.setConnectionProperties(new RabbitProperties()); + IBrokerConfigProps brokerConfigProps = new BrokerConfigProps(props); + props.setBrokerConfigProps(brokerConfigProps); + BrokerConfig brokerConfig = new BrokerConfig(); + RabbitMQDiscardProviderImpl discardProvider = new RabbitMQDiscardProviderImpl(props, brokerConfig, converter); + // Act + DiscardNotifier notifier = discardProvider.get(); + // Assert + assertThat(notifier).isExactlyInstanceOf(DLQDiscardNotifier.class); + } +} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java new file mode 100644 index 00000000..3ebf2e22 --- /dev/null +++ b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/rabbit/health/RabbitReactiveHealthIndicatorTest.java @@ -0,0 +1,102 @@ +package org.reactivecommons.async.rabbit.health; + +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.reactivecommons.async.starter.config.health.RCHealth; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.io.IOException; +import java.net.SocketException; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.TimeoutException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; +import static org.reactivecommons.async.api.HandlerRegistry.DEFAULT_DOMAIN; + +@ExtendWith(MockitoExtension.class) +class RabbitReactiveHealthIndicatorTest { + @Mock + private ConnectionFactory factory; + @Mock + private Connection connection; + + private RabbitReactiveHealthIndicator indicator; + + @BeforeEach + void setup() { + when(factory.clone()).thenReturn(factory); + indicator = new RabbitReactiveHealthIndicator(DEFAULT_DOMAIN, factory); + } + + @Test + void shouldBeUp() throws IOException, TimeoutException { + // Arrange + Map properties = new TreeMap<>(); + properties.put("version", "1.2.3"); + when(factory.newConnection()).thenReturn(connection); + when(connection.getServerProperties()).thenReturn(properties); + // Act + Mono result = indicator.doHealthCheck(RCHealth.builder()); + // Assert + StepVerifier.create(result) + .assertNext(health -> { + assertEquals(DEFAULT_DOMAIN, health.getDetails().get("domain")); + assertEquals("1.2.3", health.getDetails().get("version")); + assertEquals(RCHealth.Status.UP, health.getStatus()); + }) + .verifyComplete(); + } + + @Test + void shouldBeUpAndIgnoreCloseError() throws IOException, TimeoutException { + // Arrange + Map properties = new TreeMap<>(); + properties.put("version", "1.2.3"); + when(factory.newConnection()).thenReturn(connection); + when(connection.getServerProperties()).thenReturn(properties); + doThrow(new IOException("Error closing connection")).when(connection).close(); + // Act + Mono result = indicator.doHealthCheck(RCHealth.builder()); + // Assert + StepVerifier.create(result) + .assertNext(health -> { + assertEquals(DEFAULT_DOMAIN, health.getDetails().get("domain")); + assertEquals("1.2.3", health.getDetails().get("version")); + assertEquals(RCHealth.Status.UP, health.getStatus()); + }) + .verifyComplete(); + } + + @Test + void shouldBeDown() throws IOException, TimeoutException { + // Arrange + when(factory.newConnection()).thenThrow(new TimeoutException("Connection timeout")); + // Act + Mono result = indicator.doHealthCheck(RCHealth.builder()); + // Assert + StepVerifier.create(result) + .expectError(TimeoutException.class) + .verify(); + } + + @Test + void shouldBeDownWhenSocketException() throws IOException, TimeoutException { + // Arrange + when(factory.newConnection()).thenThrow(new SocketException("Connection timeout")); + // Act + Mono result = indicator.doHealthCheck(RCHealth.builder()); + // Assert + StepVerifier.create(result) + .expectError(RuntimeException.class) + .verify(); + } +} diff --git a/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/starter/impl/common/rabbit/RabbitMQConfigTest.java b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/starter/impl/common/rabbit/RabbitMQConfigTest.java new file mode 100644 index 00000000..29b0f364 --- /dev/null +++ b/starters/async-rabbit-starter/src/test/java/org/reactivecommons/async/starter/impl/common/rabbit/RabbitMQConfigTest.java @@ -0,0 +1,45 @@ +package org.reactivecommons.async.starter.impl.common.rabbit; + +import org.junit.jupiter.api.Test; +import org.reactivecommons.async.rabbit.RabbitMQBrokerProviderFactory; +import org.reactivecommons.async.rabbit.config.props.AsyncPropsDomain; +import org.reactivecommons.async.rabbit.converters.json.RabbitJacksonMessageConverter; +import org.reactivecommons.async.starter.config.ConnectionManager; +import org.reactivecommons.async.starter.config.ReactiveCommonsConfig; +import org.reactivecommons.async.starter.config.ReactiveCommonsListenersConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(classes = { + RabbitMQConfig.class, + AsyncPropsDomain.class, + RabbitMQBrokerProviderFactory.class, + ReactiveCommonsConfig.class, + ReactiveCommonsListenersConfig.class +}) +class RabbitMQConfigTest { + @Autowired + private RabbitJacksonMessageConverter converter; + @Autowired + private ConnectionManager manager; + + @Test + void shouldHasConverter() { + // Arrange + // Act + // Assert + assertThat(converter).isNotNull(); + } + + @Test + void shouldHasManager() { + // Arrange + // Act + // Assert + assertThat(manager).isNotNull(); + assertThat(manager.getProviders()).isNotEmpty(); + assertThat(manager.getProviders().get("app").getProps().getAppName()).isEqualTo("test-app"); + } +} diff --git a/async/async-rabbit-starter-eda/src/test/resources/application.properties b/starters/async-rabbit-starter/src/test/resources/application.properties similarity index 100% rename from async/async-rabbit-starter-eda/src/test/resources/application.properties rename to starters/async-rabbit-starter/src/test/resources/application.properties