diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index f800fbb26..e356e097d 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -12,6 +12,7 @@ body:
label: Which package is this bug report for?
options:
- discord.js
+ - brokers
- builders
- collection
- rest
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 8d5581374..9424ed106 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -5,6 +5,9 @@ apps:website:
- apps/website/*
- apps/website/**/*
+packages:brokers:
+ - packages/brokers/*
+ - packages/brokers/**/*
packages:builders:
- packages/builders/*
- packages/builders/**/*
diff --git a/.github/labels.yml b/.github/labels.yml
index 9ba45fda4..8bc0a7d3b 100644
--- a/.github/labels.yml
+++ b/.github/labels.yml
@@ -50,6 +50,8 @@
color: e4e669
- name: need repro
color: c66037
+- name: packages:brokers
+ color: fbca04
- name: packages:builders
color: fbca04
- name: packages:collection
diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
index 98852fb9a..31c7875b7 100644
--- a/.github/workflows/documentation.yml
+++ b/.github/workflows/documentation.yml
@@ -68,7 +68,7 @@ jobs:
max-parallel: 1
fail-fast: false
matrix:
- package: ['builders', 'collection', 'discord.js', 'proxy', 'rest', 'util', 'voice', 'ws']
+ package: ['brokers', 'builders', 'collection', 'discord.js', 'proxy', 'rest', 'util', 'voice', 'ws']
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
diff --git a/.github/workflows/npm-auto-deprecate.yml b/.github/workflows/npm-auto-deprecate.yml
index 2acc083f7..9774b2f25 100644
--- a/.github/workflows/npm-auto-deprecate.yml
+++ b/.github/workflows/npm-auto-deprecate.yml
@@ -22,6 +22,6 @@ jobs:
run: yarn --immutable
- name: Deprecate versions
- run: 'yarn npm-deprecate --name "*dev*" --package @discordjs/builders @discordjs/collection discord.js @discordjs/proxy @discordjs/rest @discordjs/util @discordjs/voice @discordjs/ws'
+ run: 'yarn npm-deprecate --name "*dev*" --package @discordjs/brokers @discordjs/builders @discordjs/collection discord.js @discordjs/proxy @discordjs/rest @discordjs/util @discordjs/voice @discordjs/ws'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml
index fc0a28db6..ea51cee9b 100644
--- a/.github/workflows/publish-dev.yml
+++ b/.github/workflows/publish-dev.yml
@@ -10,6 +10,8 @@ jobs:
fail-fast: false
matrix:
include:
+ - package: '@discordjs/brokers'
+ folder: 'brokers'
- package: '@discordjs/builders'
folder: 'builders'
- package: '@discordjs/collection'
diff --git a/apps/website/src/util/constants.ts b/apps/website/src/util/constants.ts
index b15deb233..bc85a14f3 100644
--- a/apps/website/src/util/constants.ts
+++ b/apps/website/src/util/constants.ts
@@ -1,4 +1,4 @@
-export const PACKAGES = ['builders', 'collection', 'proxy', 'rest', 'util', 'voice', 'ws'];
+export const PACKAGES = ['brokers', 'builders', 'collection', 'proxy', 'rest', 'util', 'voice', 'ws'];
export const DESCRIPTION =
"discord.js is a powerful node.js module that allows you to interact with the Discord API very easily. It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend.";
diff --git a/packages/actions/src/uploadCoverage/action.yml b/packages/actions/src/uploadCoverage/action.yml
index ec42c5dbc..f45d0e70b 100644
--- a/packages/actions/src/uploadCoverage/action.yml
+++ b/packages/actions/src/uploadCoverage/action.yml
@@ -15,6 +15,12 @@ runs:
files: ./apps/website/coverage/cobertura-coverage.xml
flags: website
+ - name: Upload Brokers Coverage
+ uses: codecov/codecov-action@v3
+ with:
+ files: ./packages/brokers/coverage/cobertura-coverage.xml
+ flags: brokers
+
- name: Upload Builders Coverage
uses: codecov/codecov-action@v3
with:
diff --git a/packages/brokers/.cliff-jumperrc.json b/packages/brokers/.cliff-jumperrc.json
new file mode 100644
index 000000000..819afd40a
--- /dev/null
+++ b/packages/brokers/.cliff-jumperrc.json
@@ -0,0 +1,5 @@
+{
+ "name": "brokers",
+ "org": "discordjs",
+ "packagePath": "packages/brokers"
+}
diff --git a/packages/brokers/.eslintrc.json b/packages/brokers/.eslintrc.json
new file mode 100644
index 000000000..99ef7cec8
--- /dev/null
+++ b/packages/brokers/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../../.eslintrc.json"
+}
diff --git a/packages/brokers/.gitignore b/packages/brokers/.gitignore
new file mode 100644
index 000000000..86b93e929
--- /dev/null
+++ b/packages/brokers/.gitignore
@@ -0,0 +1,27 @@
+# Packages
+node_modules/
+
+# Log files
+logs/
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Env
+.env
+
+# Dist
+dist/
+typings/
+docs/**/*
+!docs/index.json
+!docs/README.md
+
+# Miscellaneous
+.tmp/
+coverage/
+tsconfig.tsbuildinfo
diff --git a/packages/brokers/.lintstagedrc.js b/packages/brokers/.lintstagedrc.js
new file mode 100644
index 000000000..dc17706a5
--- /dev/null
+++ b/packages/brokers/.lintstagedrc.js
@@ -0,0 +1 @@
+module.exports = require('../../.lintstagedrc.json');
diff --git a/packages/brokers/.prettierignore b/packages/brokers/.prettierignore
new file mode 100644
index 000000000..8b94c7d45
--- /dev/null
+++ b/packages/brokers/.prettierignore
@@ -0,0 +1,8 @@
+# Autogenerated
+CHANGELOG.md
+.turbo
+dist/
+docs/**/*
+!docs/index.yml
+!docs/README.md
+coverage/
\ No newline at end of file
diff --git a/packages/brokers/.prettierrc.js b/packages/brokers/.prettierrc.js
new file mode 100644
index 000000000..f004026c7
--- /dev/null
+++ b/packages/brokers/.prettierrc.js
@@ -0,0 +1 @@
+module.exports = require('../../.prettierrc.json');
diff --git a/packages/brokers/LICENSE b/packages/brokers/LICENSE
new file mode 100644
index 000000000..f9786ff8f
--- /dev/null
+++ b/packages/brokers/LICENSE
@@ -0,0 +1,191 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright 2022 Noel Buechler
+ Copyright 2022 Charlotte Cristea
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/brokers/README.md b/packages/brokers/README.md
new file mode 100644
index 000000000..33b7e60fb
--- /dev/null
+++ b/packages/brokers/README.md
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## About
+
+`@discordjs/brokers` is a powerful set of message brokers
+
+## Installation
+
+**Node.js 16.9.0 or newer is required.**
+
+```sh-session
+npm install @discordjs/brokers
+yarn add @discordjs/brokers
+pnpm add @discordjs/brokers
+```
+
+## Example usage
+
+### pub sub
+
+```ts
+// publisher.js
+import { PubSubRedisBroker } from '@discordjs/brokers';
+import Redis from 'ioredis';
+
+const broker = new PubSubRedisBroker({ redisClient: new Redis() });
+
+await broker.publish('test', 'Hello World!');
+await broker.destroy();
+
+// subscriber.js
+import { PubSubRedisBroker } from '@discordjs/brokers';
+import Redis from 'ioredis';
+
+const broker = new PubSubRedisBroker({ redisClient: new Redis() });
+broker.on('test', ({ data, ack }) => {
+ console.log(data);
+ void ack();
+});
+
+await broker.subscribe('subscribers', ['test']);
+```
+
+### RPC
+
+```ts
+// caller.js
+import { RPCRedisBroker } from '@discordjs/brokers';
+import Redis from 'ioredis';
+
+const broker = new RPCRedisBroker({ redisClient: new Redis() });
+
+console.log(await broker.call('testcall', 'Hello World!'));
+await broker.destroy();
+
+// responder.js
+import { RPCRedisBroker } from '@discordjs/brokers';
+import Redis from 'ioredis';
+
+const broker = new RPCRedisBroker({ redisClient: new Redis() });
+broker.on('testcall', ({ data, ack, reply }) => {
+ console.log('responder', data);
+ void ack();
+ void reply(`Echo: ${data}`);
+});
+
+await broker.subscribe('responders', ['testcall']);
+```
+
+## Links
+
+- [Website][website] ([source][website-source])
+- [Documentation][documentation]
+- [Guide][guide] ([source][guide-source])
+ See also the [Update Guide][guide-update], including updated and removed items in the library.
+- [discord.js Discord server][discord]
+- [Discord API Discord server][discord-api]
+- [GitHub][source]
+- [npm][npm]
+- [Related libraries][related-libs]
+
+## Contributing
+
+Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
+[documentation][documentation].
+See [the contribution guide][contributing] if you'd like to submit a PR.
+
+## Help
+
+If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
+nudge in the right direction, please don't hesitate to join our official [discord.js Server][discord].
+
+[website]: https://discord.js.org/
+[website-source]: https://github.com/discordjs/discord.js/tree/main/apps/website
+[documentation]: https://discord.js.org/#/docs/brokers
+[guide]: https://discordjs.guide/
+[guide-source]: https://github.com/discordjs/guide
+[guide-update]: https://discordjs.guide/additional-info/changes-in-v14.html
+[discord]: https://discord.gg/djs
+[discord-api]: https://discord.gg/discord-api
+[source]: https://github.com/discordjs/discord.js/tree/main/packages/brokers
+[npm]: https://www.npmjs.com/package/@discordjs/brokers
+[related-libs]: https://discord.com/developers/docs/topics/community-resources#libraries
+[contributing]: https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md
diff --git a/packages/brokers/__tests__/index.test.ts b/packages/brokers/__tests__/index.test.ts
new file mode 100644
index 000000000..e133c58d9
--- /dev/null
+++ b/packages/brokers/__tests__/index.test.ts
@@ -0,0 +1,18 @@
+import type Redis from 'ioredis';
+import { test, expect, vi } from 'vitest';
+import { PubSubRedisBroker } from '../src/index.js';
+
+const mockRedisClient = {
+ defineCommand: vi.fn(),
+ xadd: vi.fn(),
+ duplicate: vi.fn(() => mockRedisClient),
+} as unknown as Redis;
+
+test('pubsub with custom encoding', async () => {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
+ const encode = vi.fn((data) => data);
+
+ const broker = new PubSubRedisBroker({ redisClient: mockRedisClient, encode });
+ await broker.publish('test', 'test');
+ expect(encode).toHaveBeenCalledWith('test');
+});
diff --git a/packages/brokers/api-extractor.json b/packages/brokers/api-extractor.json
new file mode 100644
index 000000000..bc73f2cc0
--- /dev/null
+++ b/packages/brokers/api-extractor.json
@@ -0,0 +1,3 @@
+{
+ "extends": "../../api-extractor.json"
+}
diff --git a/packages/brokers/cliff.toml b/packages/brokers/cliff.toml
new file mode 100644
index 000000000..56402e3f6
--- /dev/null
+++ b/packages/brokers/cliff.toml
@@ -0,0 +1,63 @@
+[changelog]
+header = """
+# Changelog
+
+All notable changes to this project will be documented in this file.\n
+"""
+body = """
+{% if version %}\
+ # [{{ version | trim_start_matches(pat="v") }}]\
+ {% if previous %}\
+ {% if previous.version %}\
+ (https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
+ {% else %}\
+ (https://github.com/discordjs/discord.js/tree/{{ version }})\
+ {% endif %}\
+ {% endif %} \
+ - ({{ timestamp | date(format="%Y-%m-%d") }})
+{% else %}\
+ # [unreleased]
+{% endif %}\
+{% for group, commits in commits | group_by(attribute="group") %}
+ ## {{ group | upper_first }}
+ {% for commit in commits %}
+ - {% if commit.scope %}\
+ **{{commit.scope}}:** \
+ {% endif %}\
+ {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
+ {% if commit.breaking %}\
+ {% for breakingChange in commit.footers %}\
+ \n{% raw %} {% endraw %}- **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\
+ {% endfor %}\
+ {% endif %}\
+ {% endfor %}
+{% endfor %}\n
+"""
+trim = true
+footer = ""
+
+[git]
+conventional_commits = true
+filter_unconventional = true
+commit_parsers = [
+ { message = "^feat", group = "Features"},
+ { message = "^fix", group = "Bug Fixes"},
+ { message = "^docs", group = "Documentation"},
+ { message = "^perf", group = "Performance"},
+ { message = "^refactor", group = "Refactor"},
+ { message = "^typings", group = "Typings"},
+ { message = "^types", group = "Typings"},
+ { message = ".*deprecated", body = ".*deprecated", group = "Deprecation"},
+ { message = "^revert", skip = true},
+ { message = "^style", group = "Styling"},
+ { message = "^test", group = "Testing"},
+ { message = "^chore", skip = true},
+ { message = "^ci", skip = true},
+ { message = "^build", skip = true},
+ { body = ".*security", group = "Security"},
+]
+filter_commits = true
+tag_pattern = "@discordjs/brokers@[0-9]*"
+ignore_tags = ""
+date_order = true
+sort_commits = "newest"
diff --git a/packages/brokers/docs/README.md b/packages/brokers/docs/README.md
new file mode 100644
index 000000000..88e2e10f7
--- /dev/null
+++ b/packages/brokers/docs/README.md
@@ -0,0 +1 @@
+## [View the documentation here.](https://discord.js.org/#/docs/brokers)
diff --git a/packages/brokers/docs/index.json b/packages/brokers/docs/index.json
new file mode 100644
index 000000000..557341ae9
--- /dev/null
+++ b/packages/brokers/docs/index.json
@@ -0,0 +1 @@
+[{ "name": "General", "files": [{ "name": "Welcome", "id": "welcome", "path": "../../README.md" }] }]
diff --git a/packages/brokers/package.json b/packages/brokers/package.json
new file mode 100644
index 000000000..c6c988600
--- /dev/null
+++ b/packages/brokers/package.json
@@ -0,0 +1,82 @@
+{
+ "name": "@discordjs/brokers",
+ "version": "0.1.0",
+ "description": "Powerful set of message brokers",
+ "scripts": {
+ "test": "vitest run",
+ "build": "tsup",
+ "lint": "prettier --check . && cross-env TIMING=1 eslint src __tests__ --ext .mjs,.js,.ts --format=pretty",
+ "format": "prettier --write . && cross-env TIMING=1 eslint src __tests__ --ext .mjs,.js,.ts --fix --format=pretty",
+ "fmt": "yarn format",
+ "docs": "api-extractor run --local",
+ "prepack": "yarn lint && yarn test && yarn build",
+ "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/brokers/*'",
+ "release": "cliff-jumper"
+ },
+ "main": "./dist/index.js",
+ "module": "./dist/index.mjs",
+ "typings": "./dist/index.d.ts",
+ "exports": {
+ "import": "./dist/index.mjs",
+ "require": "./dist/index.js",
+ "types": "./dist/index.d.ts"
+ },
+ "directories": {
+ "lib": "src",
+ "test": "__tests__"
+ },
+ "files": [
+ "dist"
+ ],
+ "contributors": [
+ "Crawl ",
+ "Amish Shah ",
+ "SpaceEEC ",
+ "Vlad Frangu ",
+ "Aura Roman ",
+ "DD "
+ ],
+ "license": "Apache-2.0",
+ "keywords": [
+ "discord",
+ "api",
+ "message",
+ "brokers",
+ "redis",
+ "discordapp",
+ "discordjs"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/discordjs/discord.js.git"
+ },
+ "bugs": {
+ "url": "https://github.com/discordjs/discord.js/issues"
+ },
+ "homepage": "https://discord.js.org",
+ "dependencies": {
+ "@msgpack/msgpack": "^2.8.0",
+ "@vladfrangu/async_event_emitter": "^2.1.2",
+ "ioredis": "^5.2.3"
+ },
+ "devDependencies": {
+ "@favware/cliff-jumper": "^1.8.8",
+ "@microsoft/api-extractor": "^7.32.1",
+ "@types/node": "^16.11.52",
+ "@vitest/coverage-c8": "^0.22.1",
+ "cross-env": "^7.0.3",
+ "eslint": "^8.25.0",
+ "eslint-config-neon": "^0.1.38",
+ "eslint-formatter-pretty": "^4.1.0",
+ "prettier": "^2.7.1",
+ "tsup": "^6.2.3",
+ "typescript": "^4.8.4",
+ "vitest": "^0.22.1"
+ },
+ "engines": {
+ "node": ">=16.9.0"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/packages/brokers/scripts/xcleangroup.lua b/packages/brokers/scripts/xcleangroup.lua
new file mode 100644
index 000000000..7553a56b5
--- /dev/null
+++ b/packages/brokers/scripts/xcleangroup.lua
@@ -0,0 +1,16 @@
+local info = redis.call('XINFO', 'CONSUMERS', KEYS[1], ARGS[1])
+local empty = true
+
+for k, consumer in pairs(info) do
+ if consumer['idle'] != 0 then
+ empty = false
+ break
+ end
+end
+
+if empty then
+ redis.call('XGROUP', 'DESTROY', KEYS[1], ARGS[1])
+ return true
+end
+
+return false
diff --git a/packages/brokers/src/brokers/Broker.ts b/packages/brokers/src/brokers/Broker.ts
new file mode 100644
index 000000000..40c31f3b2
--- /dev/null
+++ b/packages/brokers/src/brokers/Broker.ts
@@ -0,0 +1,86 @@
+import { Buffer } from 'node:buffer';
+import { randomBytes } from 'node:crypto';
+import { encode, decode } from '@msgpack/msgpack';
+import type { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
+
+/**
+ * Base options for a broker implementation
+ */
+export interface BaseBrokerOptions {
+ /**
+ * How long to block for messages when polling
+ */
+ blockTimeout?: number;
+ /**
+ * Function to use for decoding messages
+ */
+ // eslint-disable-next-line @typescript-eslint/method-signature-style
+ decode?: (data: Buffer) => unknown;
+ /**
+ * Function to use for encoding messages
+ */
+ // eslint-disable-next-line @typescript-eslint/method-signature-style
+ encode?: (data: unknown) => Buffer;
+ /**
+ * Max number of messages to poll at once
+ */
+ maxChunk?: number;
+ /**
+ * Unique consumer name. See: https://redis.io/commands/xreadgroup/
+ */
+ name?: string;
+}
+
+/**
+ * Default broker options
+ */
+export const DefaultBrokerOptions: Required = {
+ name: randomBytes(20).toString('hex'),
+ maxChunk: 10,
+ blockTimeout: 5_000,
+ encode: (data): Buffer => {
+ const encoded = encode(data);
+ return Buffer.from(encoded.buffer, encoded.byteOffset, encoded.byteLength);
+ },
+ decode: (data): unknown => decode(data),
+};
+
+export type ToEventMap<
+ TRecord extends Record,
+ TResponses extends Record | undefined = undefined,
+> = {
+ [TKey in keyof TRecord]: [
+ event: TResponses extends Record
+ ? { ack(): Promise; reply(data: TResponses[TKey]): Promise }
+ : { ack(): Promise } & { data: TRecord[TKey] },
+ ];
+} & { [K: string]: any };
+
+export interface IBaseBroker> {
+ /**
+ * Subscribes to the given events, grouping them by the given group name
+ */
+ subscribe(group: string, events: (keyof TEvents)[]): Promise;
+ /**
+ * Unsubscribes from the given events - it's required to pass the same group name as when subscribing for proper cleanup
+ */
+ unsubscribe(group: string, events: (keyof TEvents)[]): Promise;
+}
+
+export interface IPubSubBroker>
+ extends IBaseBroker,
+ AsyncEventEmitter> {
+ /**
+ * Publishes an event
+ */
+ publish(event: T, data: TEvents[T]): Promise;
+}
+
+export interface IRPCBroker, TResponses extends Record>
+ extends IBaseBroker,
+ AsyncEventEmitter> {
+ /**
+ * Makes an RPC call
+ */
+ call(event: T, data: TEvents[T], timeoutDuration?: number): Promise;
+}
diff --git a/packages/brokers/src/brokers/redis/BaseRedis.ts b/packages/brokers/src/brokers/redis/BaseRedis.ts
new file mode 100644
index 000000000..a075c0a47
--- /dev/null
+++ b/packages/brokers/src/brokers/redis/BaseRedis.ts
@@ -0,0 +1,172 @@
+import type { Buffer } from 'node:buffer';
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
+import type { Redis } from 'ioredis';
+import { ReplyError } from 'ioredis';
+import type { BaseBrokerOptions, IBaseBroker, ToEventMap } from '../Broker.js';
+import { DefaultBrokerOptions } from '../Broker.js';
+
+// For some reason ioredis doesn't have this typed, but it exists
+declare module 'ioredis' {
+ interface Redis {
+ xreadgroupBuffer(...args: (Buffer | string)[]): Promise<[Buffer, [Buffer, Buffer[]][]][] | null>;
+ }
+}
+
+/**
+ * Options specific for a Redis broker
+ */
+export interface RedisBrokerOptions extends BaseBrokerOptions {
+ /**
+ * The Redis client to use
+ */
+ redisClient: Redis;
+}
+
+/**
+ * Helper class with shared Redis logic
+ */
+export abstract class BaseRedisBroker>
+ extends AsyncEventEmitter>
+ implements IBaseBroker
+{
+ /**
+ * Used for Redis queues, see the 3rd argument taken by {@link https://redis.io/commands/xadd | xadd }
+ */
+ public static readonly STREAM_DATA_KEY = 'data';
+
+ /**
+ * Options this broker is using
+ */
+ protected readonly options: Required;
+
+ /**
+ * Events this broker has subscribed to
+ */
+ protected readonly subscribedEvents = new Set();
+
+ /**
+ * Internal copy of the Redis client being used to read incoming payloads
+ */
+ protected readonly streamReadClient: Redis;
+
+ /**
+ * Whether this broker is currently polling events
+ */
+ protected listening = false;
+
+ public constructor(options: RedisBrokerOptions) {
+ super();
+ this.options = { ...DefaultBrokerOptions, ...options };
+ options.redisClient.defineCommand('xcleangroup', {
+ numberOfKeys: 1,
+ lua: readFileSync(resolve(__dirname, '..', '..', '..', 'scripts', 'xcleangroup.lua'), 'utf8'),
+ });
+ this.streamReadClient = options.redisClient.duplicate();
+ }
+
+ /**
+ * {@inheritDoc IBaseBroker.subscribe}
+ */
+ public async subscribe(group: string, events: (keyof TEvents)[]): Promise {
+ await Promise.all(
+ // eslint-disable-next-line consistent-return
+ events.map(async (event) => {
+ this.subscribedEvents.add(event as string);
+ try {
+ return await this.options.redisClient.xgroup('CREATE', event as string, group, 0, 'MKSTREAM');
+ } catch (error) {
+ if (!(error instanceof ReplyError)) {
+ throw error;
+ }
+ }
+ }),
+ );
+ void this.listen(group);
+ }
+
+ /**
+ * {@inheritDoc IBaseBroker.unsubscribe}
+ */
+ public async unsubscribe(group: string, events: (keyof TEvents)[]): Promise {
+ const commands: unknown[][] = Array.from({ length: events.length * 2 });
+ for (let idx = 0; idx < commands.length; idx += 2) {
+ const event = events[idx / 2];
+ commands[idx] = ['xgroup', 'delconsumer', event as string, group, this.options.name];
+ commands[idx + 1] = ['xcleangroup', event as string, group];
+ }
+
+ await this.options.redisClient.pipeline(commands).exec();
+
+ for (const event of events) {
+ this.subscribedEvents.delete(event as string);
+ }
+ }
+
+ /**
+ * Begins polling for events, firing them to {@link BaseRedisBroker.listen}
+ */
+ protected async listen(group: string): Promise {
+ if (this.listening) {
+ return;
+ }
+
+ this.listening = true;
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ while (true) {
+ try {
+ const data = await this.streamReadClient.xreadgroupBuffer(
+ 'GROUP',
+ group,
+ this.options.name,
+ 'COUNT',
+ String(this.options.maxChunk),
+ 'BLOCK',
+ String(this.options.blockTimeout),
+ 'STREAMS',
+ ...this.subscribedEvents,
+ ...Array.from({ length: this.subscribedEvents.size }, () => '>'),
+ );
+
+ if (!data) {
+ continue;
+ }
+
+ for (const [event, info] of data) {
+ for (const [id, packet] of info) {
+ const idx = packet.findIndex((value, idx) => value.toString('utf8') === 'data' && idx % 2 === 0);
+ if (idx < 0) {
+ continue;
+ }
+
+ const data = packet[idx + 1];
+ if (!data) {
+ continue;
+ }
+
+ this.emitEvent(id, group, event.toString('utf8'), this.options.decode(data));
+ }
+ }
+ } catch (error) {
+ this.emit('error', error);
+ break;
+ }
+ }
+
+ this.listening = false;
+ }
+
+ /**
+ * Destroys the broker, closing all connections
+ */
+ public async destroy() {
+ this.streamReadClient.disconnect();
+ this.options.redisClient.disconnect();
+ }
+
+ /**
+ * Handles an incoming Redis event
+ */
+ protected abstract emitEvent(id: Buffer, group: string, event: string, data: unknown): unknown;
+}
diff --git a/packages/brokers/src/brokers/redis/PubSubRedis.ts b/packages/brokers/src/brokers/redis/PubSubRedis.ts
new file mode 100644
index 000000000..79b0b41fc
--- /dev/null
+++ b/packages/brokers/src/brokers/redis/PubSubRedis.ts
@@ -0,0 +1,58 @@
+import type { Buffer } from 'node:buffer';
+import type { IPubSubBroker } from '../Broker.js';
+import { BaseRedisBroker } from './BaseRedis.js';
+
+/**
+ * PubSub broker powered by Redis
+ *
+ * @example
+ * ```ts
+ * // publisher.js
+ * import { PubSubRedisBroker } from '@discordjs/brokers';
+ * import Redis from 'ioredis';
+ *
+ * const broker = new PubSubRedisBroker({ redisClient: new Redis() });
+ *
+ * await broker.publish('test', 'Hello World!');
+ * await broker.destroy();
+ *
+ * // subscriber.js
+ * import { PubSubRedisBroker } from '@discordjs/brokers';
+ * import Redis from 'ioredis';
+ *
+ * const broker = new PubSubRedisBroker({ redisClient: new Redis() });
+ * broker.on('test', ({ data, ack }) => {
+ * console.log(data);
+ * void ack();
+ * });
+ *
+ * await broker.subscribe('subscribers', ['test']);
+ * ```
+ */
+export class PubSubRedisBroker>
+ extends BaseRedisBroker
+ implements IPubSubBroker
+{
+ /**
+ * {@inheritDoc IPubSubBroker.publish}
+ */
+ public async publish(event: T, data: TEvents[T]): Promise {
+ await this.options.redisClient.xadd(
+ event as string,
+ '*',
+ BaseRedisBroker.STREAM_DATA_KEY,
+ this.options.encode(data),
+ );
+ }
+
+ protected emitEvent(id: Buffer, group: string, event: string, data: unknown) {
+ const payload: { ack(): Promise; data: unknown } = {
+ data,
+ ack: async () => {
+ await this.options.redisClient.xack(event, group, id);
+ },
+ };
+
+ this.emit(event, payload);
+ }
+}
diff --git a/packages/brokers/src/brokers/redis/RPCRedis.ts b/packages/brokers/src/brokers/redis/RPCRedis.ts
new file mode 100644
index 000000000..5aed520d9
--- /dev/null
+++ b/packages/brokers/src/brokers/redis/RPCRedis.ts
@@ -0,0 +1,130 @@
+import type { Buffer } from 'node:buffer';
+import { clearTimeout, setTimeout } from 'node:timers';
+import type { IRPCBroker } from '../Broker.js';
+import { DefaultBrokerOptions } from '../Broker.js';
+import type { RedisBrokerOptions } from './BaseRedis.js';
+import { BaseRedisBroker } from './BaseRedis.js';
+
+interface InternalPromise {
+ reject(error: any): void;
+ resolve(data: any): void;
+ timeout: NodeJS.Timeout;
+}
+
+/**
+ * Options specific for an RPC Redis broker
+ */
+export interface RPCRedisBrokerOptions extends RedisBrokerOptions {
+ timeout?: number;
+}
+
+/**
+ * Default values used for the {@link RPCRedisBrokerOptions}
+ */
+export const DefaultRPCRedisBrokerOptions: Required> = {
+ ...DefaultBrokerOptions,
+ timeout: 5_000,
+};
+
+/**
+ * RPC broker powered by Redis
+ *
+ * @example
+ * ```ts
+ * // caller.js
+ * import { RPCRedisBroker } from '@discordjs/brokers';
+ * import Redis from 'ioredis';
+ *
+ * const broker = new RPCRedisBroker({ redisClient: new Redis() });
+ *
+ * console.log(await broker.call('testcall', 'Hello World!'));
+ * await broker.destroy();
+ *
+ * // responder.js
+ * import { RPCRedisBroker } from '@discordjs/brokers';
+ * import Redis from 'ioredis';
+ *
+ * const broker = new RPCRedisBroker({ redisClient: new Redis() });
+ * broker.on('testcall', ({ data, ack, reply }) => {
+ * console.log('responder', data);
+ * void ack();
+ * void reply(`Echo: ${data}`);
+ * });
+ *
+ * await broker.subscribe('responders', ['testcall']);
+ * ```
+ */
+export class RPCRedisBroker, TResponses extends Record>
+ extends BaseRedisBroker
+ implements IRPCBroker
+{
+ /**
+ * Options this broker is using
+ */
+ protected override readonly options: Required;
+
+ protected readonly promises = new Map();
+
+ public constructor(options: RPCRedisBrokerOptions) {
+ super(options);
+ this.options = { ...DefaultRPCRedisBrokerOptions, ...options };
+
+ this.streamReadClient.on('messageBuffer', (channel: Buffer, message: Buffer) => {
+ const [, id] = channel.toString().split(':');
+ if (id && this.promises.has(id)) {
+ // eslint-disable-next-line @typescript-eslint/unbound-method
+ const { resolve, timeout } = this.promises.get(id)!;
+ resolve(this.options.decode(message));
+ clearTimeout(timeout);
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc IRPCBroker.call}
+ */
+ public async call(
+ event: T,
+ data: TEvents[T],
+ timeoutDuration: number = this.options.timeout,
+ ): Promise {
+ const id = await this.options.redisClient.xadd(
+ event as string,
+ '*',
+ BaseRedisBroker.STREAM_DATA_KEY,
+ this.options.encode(data),
+ );
+ // This id! assertion is valid. From redis docs:
+ // "The command returns a Null reply when used with the NOMKSTREAM option and the key doesn't exist."
+ // See: https://redis.io/commands/xadd/
+ const rpcChannel = `${event as string}:${id!}`;
+
+ // Construct the error here for better stack traces
+ const timedOut = new Error(`timed out after ${timeoutDuration}ms`);
+
+ await this.streamReadClient.subscribe(rpcChannel);
+ return new Promise((resolve, reject) => {
+ const timeout = setTimeout(() => reject(timedOut), timeoutDuration).unref();
+
+ this.promises.set(id!, { resolve, reject, timeout });
+ // eslint-disable-next-line promise/prefer-await-to-then
+ }).finally(() => {
+ void this.streamReadClient.unsubscribe(rpcChannel);
+ this.promises.delete(id!);
+ });
+ }
+
+ protected emitEvent(id: Buffer, group: string, event: string, data: unknown) {
+ const payload: { ack(): Promise; data: unknown; reply(data: unknown): Promise } = {
+ data,
+ ack: async () => {
+ await this.options.redisClient.xack(event, group, id);
+ },
+ reply: async (data) => {
+ await this.options.redisClient.publish(`${event}:${id.toString()}`, this.options.encode(data));
+ },
+ };
+
+ this.emit(event, payload);
+ }
+}
diff --git a/packages/brokers/src/index.ts b/packages/brokers/src/index.ts
new file mode 100644
index 000000000..e421e5c46
--- /dev/null
+++ b/packages/brokers/src/index.ts
@@ -0,0 +1,5 @@
+export * from './brokers/redis/BaseRedis.js';
+export * from './brokers/redis/PubSubRedis.js';
+export * from './brokers/redis/RPCRedis.js';
+
+export * from './brokers/Broker.js';
diff --git a/packages/brokers/tsconfig.eslint.json b/packages/brokers/tsconfig.eslint.json
new file mode 100644
index 000000000..d04d4be3a
--- /dev/null
+++ b/packages/brokers/tsconfig.eslint.json
@@ -0,0 +1,20 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "allowJs": true
+ },
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ "**/*.js",
+ "**/*.mjs",
+ "**/*.jsx",
+ "**/*.test.ts",
+ "**/*.test.js",
+ "**/*.test.mjs",
+ "**/*.spec.ts",
+ "**/*.spec.js",
+ "**/*.spec.mjs"
+ ],
+ "exclude": []
+}
diff --git a/packages/brokers/tsconfig.json b/packages/brokers/tsconfig.json
new file mode 100644
index 000000000..fd8b5e417
--- /dev/null
+++ b/packages/brokers/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src/**/*.ts"]
+}
diff --git a/packages/brokers/tsup.config.js b/packages/brokers/tsup.config.js
new file mode 100644
index 000000000..2e679fd0a
--- /dev/null
+++ b/packages/brokers/tsup.config.js
@@ -0,0 +1,3 @@
+import { createTsupConfig } from '../../tsup.config.js';
+
+export default createTsupConfig();
diff --git a/yarn.lock b/yarn.lock
index 85fcbd714..d26b8f365 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2028,6 +2028,28 @@ __metadata:
languageName: unknown
linkType: soft
+"@discordjs/brokers@workspace:packages/brokers":
+ version: 0.0.0-use.local
+ resolution: "@discordjs/brokers@workspace:packages/brokers"
+ dependencies:
+ "@favware/cliff-jumper": ^1.8.8
+ "@microsoft/api-extractor": ^7.32.1
+ "@msgpack/msgpack": ^2.8.0
+ "@types/node": ^16.11.52
+ "@vitest/coverage-c8": ^0.22.1
+ "@vladfrangu/async_event_emitter": ^2.1.2
+ cross-env: ^7.0.3
+ eslint: ^8.25.0
+ eslint-config-neon: ^0.1.38
+ eslint-formatter-pretty: ^4.1.0
+ ioredis: ^5.2.3
+ prettier: ^2.7.1
+ tsup: ^6.2.3
+ typescript: ^4.8.4
+ vitest: ^0.22.1
+ languageName: unknown
+ linkType: soft
+
"@discordjs/builders@workspace:^, @discordjs/builders@workspace:packages/builders":
version: 0.0.0-use.local
resolution: "@discordjs/builders@workspace:packages/builders"
@@ -2649,6 +2671,13 @@ __metadata:
languageName: node
linkType: hard
+"@ioredis/commands@npm:^1.1.1":
+ version: 1.2.0
+ resolution: "@ioredis/commands@npm:1.2.0"
+ checksum: 9b20225ba36ef3e5caf69b3c0720597c3016cc9b1e157f519ea388f621dd9037177f84cfe7e25c4c32dad7dd90c70ff9123cd411f747e053cf292193c9c461e2
+ languageName: node
+ linkType: hard
+
"@istanbuljs/load-nyc-config@npm:^1.0.0":
version: 1.1.0
resolution: "@istanbuljs/load-nyc-config@npm:1.1.0"
@@ -3138,7 +3167,7 @@ __metadata:
languageName: node
linkType: hard
-"@microsoft/api-extractor@npm:^7.32.0":
+"@microsoft/api-extractor@npm:^7.32.0, @microsoft/api-extractor@npm:^7.32.1":
version: 7.32.1
resolution: "@microsoft/api-extractor@npm:7.32.1"
dependencies:
@@ -3198,6 +3227,13 @@ __metadata:
languageName: node
linkType: hard
+"@msgpack/msgpack@npm:^2.8.0":
+ version: 2.8.0
+ resolution: "@msgpack/msgpack@npm:2.8.0"
+ checksum: bead9393f57239007a2fe455df5277cbc03b125f14f310162a652b81471dcf3ab6780eaa24b36e20aa742998910a6840147d08b7267063b8e2de5d40c624360e
+ languageName: node
+ linkType: hard
+
"@next/env@npm:12.3.1":
version: 12.3.1
resolution: "@next/env@npm:12.3.1"
@@ -4293,6 +4329,13 @@ __metadata:
languageName: node
linkType: hard
+"@types/node@npm:^16.11.52":
+ version: 16.11.65
+ resolution: "@types/node@npm:16.11.65"
+ checksum: 81d84cb1e7aa305574cd35acf1a5e47f4a7f52783ba096f4bc511314540dee33a27cbd4fddc8bddd4535f1c87a96a76907157bcde093a25c89e8851d6dd63022
+ languageName: node
+ linkType: hard
+
"@types/node@npm:^8.0.0":
version: 8.10.66
resolution: "@types/node@npm:8.10.66"
@@ -5069,6 +5112,16 @@ __metadata:
languageName: node
linkType: hard
+"@vitest/coverage-c8@npm:^0.22.1":
+ version: 0.22.1
+ resolution: "@vitest/coverage-c8@npm:0.22.1"
+ dependencies:
+ c8: ^7.12.0
+ vitest: 0.22.1
+ checksum: 141c10127a556ff32e43c6d92a468d800d7c62c767feef1a4123e204a07b58456b410efdba720fa3035639903098dc12767602aa7dabadd40e2d60abc0b008f1
+ languageName: node
+ linkType: hard
+
"@vitest/coverage-c8@npm:^0.24.1":
version: 0.24.1
resolution: "@vitest/coverage-c8@npm:0.24.1"
@@ -5329,9 +5382,9 @@ __metadata:
linkType: hard
"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0":
- version: 6.1.1
- resolution: "ansi-styles@npm:6.1.1"
- checksum: f2b1ed658ead23caf77effe7b875960cacd70d1ebe47c830e191358b242d688cf52a28d55ef9b19d102f792e8c1dec34bd865db264f1c7f4f63dd3a5fa84677e
+ version: 6.2.0
+ resolution: "ansi-styles@npm:6.2.0"
+ checksum: ce35204dc87f418440e8a95569c23e235715d7089e512f88254fb5fcedc18cdcfd6cd36852182388586eba21a9246b67a9ce4f1687864b06017407d8fda11a10
languageName: node
linkType: hard
@@ -6815,6 +6868,13 @@ __metadata:
languageName: node
linkType: hard
+"cluster-key-slot@npm:^1.1.0":
+ version: 1.1.1
+ resolution: "cluster-key-slot@npm:1.1.1"
+ checksum: 2fb7390e7950075acb09fc8aad3dc939abb64b139ba1b5f6341efdd0beda8cdc8b508e5f30d943370cf30ea0c13741c579e0846efd007b328bdc1a5a712264da
+ languageName: node
+ linkType: hard
+
"cmdk@npm:^0.1.20":
version: 0.1.20
resolution: "cmdk@npm:0.1.20"
@@ -7961,6 +8021,13 @@ __metadata:
languageName: node
linkType: hard
+"denque@npm:^2.0.1":
+ version: 2.1.0
+ resolution: "denque@npm:2.1.0"
+ checksum: 1d4ae1d05e59ac3a3481e7b478293f4b4c813819342273f3d5b826c7ffa9753c520919ba264f377e09108d24ec6cf0ec0ac729a5686cbb8f32d797126c5dae74
+ languageName: node
+ linkType: hard
+
"depd@npm:2.0.0":
version: 2.0.0
resolution: "depd@npm:2.0.0"
@@ -8347,9 +8414,9 @@ __metadata:
linkType: hard
"electron-to-chromium@npm:^1.4.251":
- version: 1.4.276
- resolution: "electron-to-chromium@npm:1.4.276"
- checksum: 9cd4448f68a37e598ad7ce193c982e8dd428c5e67e09cf780c0e13f23d689f7aec9b33718a61ea522b51ec2a8c835a0c25bbb0214ac62627fe7e4771a8650600
+ version: 1.4.277
+ resolution: "electron-to-chromium@npm:1.4.277"
+ checksum: 05002adf87dbaa6beb3895b7d64a1ee9442d46d5765582c71fc47c9aa8a4e710ada9939ce33bcd92da7c6fe61099462e036890b2edd21200c7f694309b31ea08
languageName: node
linkType: hard
@@ -11791,6 +11858,23 @@ __metadata:
languageName: node
linkType: hard
+"ioredis@npm:^5.2.3":
+ version: 5.2.3
+ resolution: "ioredis@npm:5.2.3"
+ dependencies:
+ "@ioredis/commands": ^1.1.1
+ cluster-key-slot: ^1.1.0
+ debug: ^4.3.4
+ denque: ^2.0.1
+ lodash.defaults: ^4.2.0
+ lodash.isarguments: ^3.1.0
+ redis-errors: ^1.2.0
+ redis-parser: ^3.0.0
+ standard-as-callback: ^2.1.0
+ checksum: 2cb7f0f4217e6774accad3620af1b7114722721c1d1824be2c9f0c2a77ab9629f2e0848d18b1a7208bc37796ae1207cb3e0898fce61900cfe797da0382724ad1
+ languageName: node
+ linkType: hard
+
"ip@npm:^2.0.0":
version: 2.0.0
resolution: "ip@npm:2.0.0"
@@ -13401,6 +13485,13 @@ __metadata:
languageName: node
linkType: hard
+"lodash.defaults@npm:^4.2.0":
+ version: 4.2.0
+ resolution: "lodash.defaults@npm:4.2.0"
+ checksum: 84923258235592c8886e29de5491946ff8c2ae5c82a7ac5cddd2e3cb697e6fbdfbbb6efcca015795c86eec2bb953a5a2ee4016e3735a3f02720428a40efbb8f1
+ languageName: node
+ linkType: hard
+
"lodash.get@npm:^4.4.2":
version: 4.4.2
resolution: "lodash.get@npm:4.4.2"
@@ -13408,6 +13499,13 @@ __metadata:
languageName: node
linkType: hard
+"lodash.isarguments@npm:^3.1.0":
+ version: 3.1.0
+ resolution: "lodash.isarguments@npm:3.1.0"
+ checksum: ae1526f3eb5c61c77944b101b1f655f846ecbedcb9e6b073526eba6890dc0f13f09f72e11ffbf6540b602caee319af9ac363d6cdd6be41f4ee453436f04f13b5
+ languageName: node
+ linkType: hard
+
"lodash.isequal@npm:^4.5.0":
version: 4.5.0
resolution: "lodash.isequal@npm:4.5.0"
@@ -15128,8 +15226,8 @@ __metadata:
linkType: hard
"node-gyp@npm:latest":
- version: 9.2.0
- resolution: "node-gyp@npm:9.2.0"
+ version: 9.3.0
+ resolution: "node-gyp@npm:9.3.0"
dependencies:
env-paths: ^2.2.0
glob: ^7.1.4
@@ -15143,7 +15241,7 @@ __metadata:
which: ^2.0.2
bin:
node-gyp: bin/node-gyp.js
- checksum: 91f0589eabbd37f0d4e3fe9918f1f9e25afc707f6e107f0133be19c5aac62e731d92abdc2b106258665a4487b18cc2878d3fcd3dc2c6cffd68da1cb2a5ccf450
+ checksum: 589ddd3ed967724ef425f9624bfa47cf73022640ab3eba6d556e92cdc4ddef33b63fce3a467c93b995a3f61df92eafd3c3d1e8dbe4a2c00c383334487dea99c3
languageName: node
linkType: hard
@@ -16813,6 +16911,22 @@ __metadata:
languageName: node
linkType: hard
+"redis-errors@npm:^1.0.0, redis-errors@npm:^1.2.0":
+ version: 1.2.0
+ resolution: "redis-errors@npm:1.2.0"
+ checksum: f28ac2692113f6f9c222670735aa58aeae413464fd58ccf3fce3f700cae7262606300840c802c64f2b53f19f65993da24dc918afc277e9e33ac1ff09edb394f4
+ languageName: node
+ linkType: hard
+
+"redis-parser@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "redis-parser@npm:3.0.0"
+ dependencies:
+ redis-errors: ^1.0.0
+ checksum: 89290ae530332f2ae37577647fa18208d10308a1a6ba750b9d9a093e7398f5e5253f19855b64c98757f7129cccce958e4af2573fdc33bad41405f87f1943459a
+ languageName: node
+ linkType: hard
+
"reduce-extract@npm:^1.0.0":
version: 1.0.0
resolution: "reduce-extract@npm:1.0.0"
@@ -18288,6 +18402,13 @@ __metadata:
languageName: node
linkType: hard
+"standard-as-callback@npm:^2.1.0":
+ version: 2.1.0
+ resolution: "standard-as-callback@npm:2.1.0"
+ checksum: 88bec83ee220687c72d94fd86a98d5272c91d37ec64b66d830dbc0d79b62bfa6e47f53b71646011835fc9ce7fae62739545d13124262b53be4fbb3e2ebad551c
+ languageName: node
+ linkType: hard
+
"statuses@npm:2.0.1":
version: 2.0.1
resolution: "statuses@npm:2.0.1"
@@ -19035,6 +19156,13 @@ __metadata:
languageName: node
linkType: hard
+"tinypool@npm:^0.2.4":
+ version: 0.2.4
+ resolution: "tinypool@npm:0.2.4"
+ checksum: f050bd36c89529a2a0d3f9c1fdbba3f317114e3ee6eb5d5ba72c51e887d45ef3ef8d8533fb2ca2eba7189d19d2231712b81b3a75e099248532f5563369929c33
+ languageName: node
+ linkType: hard
+
"tinypool@npm:^0.3.0":
version: 0.3.0
resolution: "tinypool@npm:0.3.0"
@@ -20487,7 +20615,7 @@ __metadata:
languageName: node
linkType: hard
-"vite@npm:^3.0.0, vite@npm:^3.0.9, vite@npm:^3.1.7, vite@npm:~3.1.3":
+"vite@npm:^2.9.12 || ^3.0.0-0, vite@npm:^3.0.0, vite@npm:^3.0.9, vite@npm:^3.1.7, vite@npm:~3.1.3":
version: 3.1.7
resolution: "vite@npm:3.1.7"
dependencies:
@@ -20519,6 +20647,42 @@ __metadata:
languageName: node
linkType: hard
+"vitest@npm:0.22.1, vitest@npm:^0.22.1":
+ version: 0.22.1
+ resolution: "vitest@npm:0.22.1"
+ dependencies:
+ "@types/chai": ^4.3.3
+ "@types/chai-subset": ^1.3.3
+ "@types/node": "*"
+ chai: ^4.3.6
+ debug: ^4.3.4
+ local-pkg: ^0.4.2
+ tinypool: ^0.2.4
+ tinyspy: ^1.0.2
+ vite: ^2.9.12 || ^3.0.0-0
+ peerDependencies:
+ "@edge-runtime/vm": "*"
+ "@vitest/browser": "*"
+ "@vitest/ui": "*"
+ happy-dom: "*"
+ jsdom: "*"
+ peerDependenciesMeta:
+ "@edge-runtime/vm":
+ optional: true
+ "@vitest/browser":
+ optional: true
+ "@vitest/ui":
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+ bin:
+ vitest: vitest.mjs
+ checksum: 7abe50ceb51181e77cd62eb3a07c2da17f13078f09be34cc2e98f1f94a77eba33a56c644d48ae16bb474945ffc1cfc8664b1f4976c3de495c5e474057420c4ca
+ languageName: node
+ linkType: hard
+
"vitest@npm:0.24.1, vitest@npm:^0.24.1":
version: 0.24.1
resolution: "vitest@npm:0.24.1"