mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat: @discordjs/brokers (#8548)
This commit is contained in:
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -12,6 +12,7 @@ body:
|
|||||||
label: Which package is this bug report for?
|
label: Which package is this bug report for?
|
||||||
options:
|
options:
|
||||||
- discord.js
|
- discord.js
|
||||||
|
- brokers
|
||||||
- builders
|
- builders
|
||||||
- collection
|
- collection
|
||||||
- rest
|
- rest
|
||||||
|
|||||||
3
.github/labeler.yml
vendored
3
.github/labeler.yml
vendored
@@ -5,6 +5,9 @@ apps:website:
|
|||||||
- apps/website/*
|
- apps/website/*
|
||||||
- apps/website/**/*
|
- apps/website/**/*
|
||||||
|
|
||||||
|
packages:brokers:
|
||||||
|
- packages/brokers/*
|
||||||
|
- packages/brokers/**/*
|
||||||
packages:builders:
|
packages:builders:
|
||||||
- packages/builders/*
|
- packages/builders/*
|
||||||
- packages/builders/**/*
|
- packages/builders/**/*
|
||||||
|
|||||||
2
.github/labels.yml
vendored
2
.github/labels.yml
vendored
@@ -50,6 +50,8 @@
|
|||||||
color: e4e669
|
color: e4e669
|
||||||
- name: need repro
|
- name: need repro
|
||||||
color: c66037
|
color: c66037
|
||||||
|
- name: packages:brokers
|
||||||
|
color: fbca04
|
||||||
- name: packages:builders
|
- name: packages:builders
|
||||||
color: fbca04
|
color: fbca04
|
||||||
- name: packages:collection
|
- name: packages:collection
|
||||||
|
|||||||
2
.github/workflows/documentation.yml
vendored
2
.github/workflows/documentation.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
|||||||
max-parallel: 1
|
max-parallel: 1
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
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
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/npm-auto-deprecate.yml
vendored
2
.github/workflows/npm-auto-deprecate.yml
vendored
@@ -22,6 +22,6 @@ jobs:
|
|||||||
run: yarn --immutable
|
run: yarn --immutable
|
||||||
|
|
||||||
- name: Deprecate versions
|
- 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:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/publish-dev.yml
vendored
2
.github/workflows/publish-dev.yml
vendored
@@ -10,6 +10,8 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
- package: '@discordjs/brokers'
|
||||||
|
folder: 'brokers'
|
||||||
- package: '@discordjs/builders'
|
- package: '@discordjs/builders'
|
||||||
folder: 'builders'
|
folder: 'builders'
|
||||||
- package: '@discordjs/collection'
|
- package: '@discordjs/collection'
|
||||||
|
|||||||
@@ -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 =
|
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.";
|
"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.";
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ runs:
|
|||||||
files: ./apps/website/coverage/cobertura-coverage.xml
|
files: ./apps/website/coverage/cobertura-coverage.xml
|
||||||
flags: website
|
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
|
- name: Upload Builders Coverage
|
||||||
uses: codecov/codecov-action@v3
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
5
packages/brokers/.cliff-jumperrc.json
Normal file
5
packages/brokers/.cliff-jumperrc.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "brokers",
|
||||||
|
"org": "discordjs",
|
||||||
|
"packagePath": "packages/brokers"
|
||||||
|
}
|
||||||
3
packages/brokers/.eslintrc.json
Normal file
3
packages/brokers/.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../.eslintrc.json"
|
||||||
|
}
|
||||||
27
packages/brokers/.gitignore
vendored
Normal file
27
packages/brokers/.gitignore
vendored
Normal file
@@ -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
|
||||||
1
packages/brokers/.lintstagedrc.js
Normal file
1
packages/brokers/.lintstagedrc.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = require('../../.lintstagedrc.json');
|
||||||
8
packages/brokers/.prettierignore
Normal file
8
packages/brokers/.prettierignore
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Autogenerated
|
||||||
|
CHANGELOG.md
|
||||||
|
.turbo
|
||||||
|
dist/
|
||||||
|
docs/**/*
|
||||||
|
!docs/index.yml
|
||||||
|
!docs/README.md
|
||||||
|
coverage/
|
||||||
1
packages/brokers/.prettierrc.js
Normal file
1
packages/brokers/.prettierrc.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = require('../../.prettierrc.json');
|
||||||
191
packages/brokers/LICENSE
Normal file
191
packages/brokers/LICENSE
Normal file
@@ -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.
|
||||||
120
packages/brokers/README.md
Normal file
120
packages/brokers/README.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<div align="center">
|
||||||
|
<br />
|
||||||
|
<p>
|
||||||
|
<a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" /></a>
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
<p>
|
||||||
|
<a href="https://discord.gg/djs"><img src="https://img.shields.io/discord/222078108977594368?color=5865F2&logo=discord&logoColor=white" alt="Discord server" /></a>
|
||||||
|
<a href="https://www.npmjs.com/package/@discordjs/brokers"><img src="https://img.shields.io/npm/v/@discordjs/brokers.svg?maxAge=3600" alt="npm version" /></a>
|
||||||
|
<a href="https://www.npmjs.com/package/@discordjs/brokers"><img src="https://img.shields.io/npm/dt/@discordjs/brokers.svg?maxAge=3600" alt="npm downloads" /></a>
|
||||||
|
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Build status" /></a>
|
||||||
|
<a href="https://codecov.io/gh/discordjs/discord.js" ><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2&flag=brokers" alt="Code coverage" /></a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"><img src="https://raw.githubusercontent.com/discordjs/discord.js/main/.github/powered-by-vercel.svg" alt="Vercel" /></a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## 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
|
||||||
18
packages/brokers/__tests__/index.test.ts
Normal file
18
packages/brokers/__tests__/index.test.ts
Normal file
@@ -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');
|
||||||
|
});
|
||||||
3
packages/brokers/api-extractor.json
Normal file
3
packages/brokers/api-extractor.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../api-extractor.json"
|
||||||
|
}
|
||||||
63
packages/brokers/cliff.toml
Normal file
63
packages/brokers/cliff.toml
Normal file
@@ -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"
|
||||||
1
packages/brokers/docs/README.md
Normal file
1
packages/brokers/docs/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
## [View the documentation here.](https://discord.js.org/#/docs/brokers)
|
||||||
1
packages/brokers/docs/index.json
Normal file
1
packages/brokers/docs/index.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[{ "name": "General", "files": [{ "name": "Welcome", "id": "welcome", "path": "../../README.md" }] }]
|
||||||
82
packages/brokers/package.json
Normal file
82
packages/brokers/package.json
Normal file
@@ -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 <icrawltogo@gmail.com>",
|
||||||
|
"Amish Shah <amishshah.2k@gmail.com>",
|
||||||
|
"SpaceEEC <spaceeec@yahoo.com>",
|
||||||
|
"Vlad Frangu <kingdgrizzle@gmail.com>",
|
||||||
|
"Aura Roman <kyradiscord@gmail.com>",
|
||||||
|
"DD <didinele.dev@gmail.com>"
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
16
packages/brokers/scripts/xcleangroup.lua
Normal file
16
packages/brokers/scripts/xcleangroup.lua
Normal file
@@ -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
|
||||||
86
packages/brokers/src/brokers/Broker.ts
Normal file
86
packages/brokers/src/brokers/Broker.ts
Normal file
@@ -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<BaseBrokerOptions> = {
|
||||||
|
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<string, any>,
|
||||||
|
TResponses extends Record<keyof TRecord, any> | undefined = undefined,
|
||||||
|
> = {
|
||||||
|
[TKey in keyof TRecord]: [
|
||||||
|
event: TResponses extends Record<keyof TRecord, any>
|
||||||
|
? { ack(): Promise<void>; reply(data: TResponses[TKey]): Promise<void> }
|
||||||
|
: { ack(): Promise<void> } & { data: TRecord[TKey] },
|
||||||
|
];
|
||||||
|
} & { [K: string]: any };
|
||||||
|
|
||||||
|
export interface IBaseBroker<TEvents extends Record<string, any>> {
|
||||||
|
/**
|
||||||
|
* Subscribes to the given events, grouping them by the given group name
|
||||||
|
*/
|
||||||
|
subscribe(group: string, events: (keyof TEvents)[]): Promise<void>;
|
||||||
|
/**
|
||||||
|
* 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<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPubSubBroker<TEvents extends Record<string, any>>
|
||||||
|
extends IBaseBroker<TEvents>,
|
||||||
|
AsyncEventEmitter<ToEventMap<TEvents>> {
|
||||||
|
/**
|
||||||
|
* Publishes an event
|
||||||
|
*/
|
||||||
|
publish<T extends keyof TEvents>(event: T, data: TEvents[T]): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRPCBroker<TEvents extends Record<string, any>, TResponses extends Record<keyof TEvents, any>>
|
||||||
|
extends IBaseBroker<TEvents>,
|
||||||
|
AsyncEventEmitter<ToEventMap<TEvents, TResponses>> {
|
||||||
|
/**
|
||||||
|
* Makes an RPC call
|
||||||
|
*/
|
||||||
|
call<T extends keyof TEvents>(event: T, data: TEvents[T], timeoutDuration?: number): Promise<TResponses[T]>;
|
||||||
|
}
|
||||||
172
packages/brokers/src/brokers/redis/BaseRedis.ts
Normal file
172
packages/brokers/src/brokers/redis/BaseRedis.ts
Normal file
@@ -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<TEvents extends Record<string, any>>
|
||||||
|
extends AsyncEventEmitter<ToEventMap<TEvents>>
|
||||||
|
implements IBaseBroker<TEvents>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 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<RedisBrokerOptions>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Events this broker has subscribed to
|
||||||
|
*/
|
||||||
|
protected readonly subscribedEvents = new Set<string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<void> {
|
||||||
|
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<void> {
|
||||||
|
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<void> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
58
packages/brokers/src/brokers/redis/PubSubRedis.ts
Normal file
58
packages/brokers/src/brokers/redis/PubSubRedis.ts
Normal file
@@ -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<TEvents extends Record<string, any>>
|
||||||
|
extends BaseRedisBroker<TEvents>
|
||||||
|
implements IPubSubBroker<TEvents>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritDoc IPubSubBroker.publish}
|
||||||
|
*/
|
||||||
|
public async publish<T extends keyof TEvents>(event: T, data: TEvents[T]): Promise<void> {
|
||||||
|
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<void>; data: unknown } = {
|
||||||
|
data,
|
||||||
|
ack: async () => {
|
||||||
|
await this.options.redisClient.xack(event, group, id);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.emit(event, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
130
packages/brokers/src/brokers/redis/RPCRedis.ts
Normal file
130
packages/brokers/src/brokers/redis/RPCRedis.ts
Normal file
@@ -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<Omit<RPCRedisBrokerOptions, 'redisClient'>> = {
|
||||||
|
...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<TEvents extends Record<string, any>, TResponses extends Record<keyof TEvents, any>>
|
||||||
|
extends BaseRedisBroker<TEvents>
|
||||||
|
implements IRPCBroker<TEvents, TResponses>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Options this broker is using
|
||||||
|
*/
|
||||||
|
protected override readonly options: Required<RPCRedisBrokerOptions>;
|
||||||
|
|
||||||
|
protected readonly promises = new Map<string, InternalPromise>();
|
||||||
|
|
||||||
|
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<T extends keyof TEvents>(
|
||||||
|
event: T,
|
||||||
|
data: TEvents[T],
|
||||||
|
timeoutDuration: number = this.options.timeout,
|
||||||
|
): Promise<TResponses[T]> {
|
||||||
|
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<TResponses[T]>((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<void>; data: unknown; reply(data: unknown): Promise<void> } = {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
packages/brokers/src/index.ts
Normal file
5
packages/brokers/src/index.ts
Normal file
@@ -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';
|
||||||
20
packages/brokers/tsconfig.eslint.json
Normal file
20
packages/brokers/tsconfig.eslint.json
Normal file
@@ -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": []
|
||||||
|
}
|
||||||
4
packages/brokers/tsconfig.json
Normal file
4
packages/brokers/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
3
packages/brokers/tsup.config.js
Normal file
3
packages/brokers/tsup.config.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { createTsupConfig } from '../../tsup.config.js';
|
||||||
|
|
||||||
|
export default createTsupConfig();
|
||||||
186
yarn.lock
186
yarn.lock
@@ -2028,6 +2028,28 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
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":
|
"@discordjs/builders@workspace:^, @discordjs/builders@workspace:packages/builders":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@discordjs/builders@workspace:packages/builders"
|
resolution: "@discordjs/builders@workspace:packages/builders"
|
||||||
@@ -2649,6 +2671,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@istanbuljs/load-nyc-config@npm:^1.0.0":
|
||||||
version: 1.1.0
|
version: 1.1.0
|
||||||
resolution: "@istanbuljs/load-nyc-config@npm:1.1.0"
|
resolution: "@istanbuljs/load-nyc-config@npm:1.1.0"
|
||||||
@@ -3138,7 +3167,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 7.32.1
|
||||||
resolution: "@microsoft/api-extractor@npm:7.32.1"
|
resolution: "@microsoft/api-extractor@npm:7.32.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3198,6 +3227,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@next/env@npm:12.3.1":
|
||||||
version: 12.3.1
|
version: 12.3.1
|
||||||
resolution: "@next/env@npm:12.3.1"
|
resolution: "@next/env@npm:12.3.1"
|
||||||
@@ -4293,6 +4329,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@types/node@npm:^8.0.0":
|
||||||
version: 8.10.66
|
version: 8.10.66
|
||||||
resolution: "@types/node@npm:8.10.66"
|
resolution: "@types/node@npm:8.10.66"
|
||||||
@@ -5069,6 +5112,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@vitest/coverage-c8@npm:^0.24.1":
|
||||||
version: 0.24.1
|
version: 0.24.1
|
||||||
resolution: "@vitest/coverage-c8@npm:0.24.1"
|
resolution: "@vitest/coverage-c8@npm:0.24.1"
|
||||||
@@ -5329,9 +5382,9 @@ __metadata:
|
|||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0":
|
"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.1.0":
|
||||||
version: 6.1.1
|
version: 6.2.0
|
||||||
resolution: "ansi-styles@npm:6.1.1"
|
resolution: "ansi-styles@npm:6.2.0"
|
||||||
checksum: f2b1ed658ead23caf77effe7b875960cacd70d1ebe47c830e191358b242d688cf52a28d55ef9b19d102f792e8c1dec34bd865db264f1c7f4f63dd3a5fa84677e
|
checksum: ce35204dc87f418440e8a95569c23e235715d7089e512f88254fb5fcedc18cdcfd6cd36852182388586eba21a9246b67a9ce4f1687864b06017407d8fda11a10
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -6815,6 +6868,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"cmdk@npm:^0.1.20":
|
||||||
version: 0.1.20
|
version: 0.1.20
|
||||||
resolution: "cmdk@npm:0.1.20"
|
resolution: "cmdk@npm:0.1.20"
|
||||||
@@ -7961,6 +8021,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"depd@npm:2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "depd@npm:2.0.0"
|
resolution: "depd@npm:2.0.0"
|
||||||
@@ -8347,9 +8414,9 @@ __metadata:
|
|||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"electron-to-chromium@npm:^1.4.251":
|
"electron-to-chromium@npm:^1.4.251":
|
||||||
version: 1.4.276
|
version: 1.4.277
|
||||||
resolution: "electron-to-chromium@npm:1.4.276"
|
resolution: "electron-to-chromium@npm:1.4.277"
|
||||||
checksum: 9cd4448f68a37e598ad7ce193c982e8dd428c5e67e09cf780c0e13f23d689f7aec9b33718a61ea522b51ec2a8c835a0c25bbb0214ac62627fe7e4771a8650600
|
checksum: 05002adf87dbaa6beb3895b7d64a1ee9442d46d5765582c71fc47c9aa8a4e710ada9939ce33bcd92da7c6fe61099462e036890b2edd21200c7f694309b31ea08
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -11791,6 +11858,23 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"ip@npm:^2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "ip@npm:2.0.0"
|
resolution: "ip@npm:2.0.0"
|
||||||
@@ -13401,6 +13485,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"lodash.get@npm:^4.4.2":
|
||||||
version: 4.4.2
|
version: 4.4.2
|
||||||
resolution: "lodash.get@npm:4.4.2"
|
resolution: "lodash.get@npm:4.4.2"
|
||||||
@@ -13408,6 +13499,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"lodash.isequal@npm:^4.5.0":
|
||||||
version: 4.5.0
|
version: 4.5.0
|
||||||
resolution: "lodash.isequal@npm:4.5.0"
|
resolution: "lodash.isequal@npm:4.5.0"
|
||||||
@@ -15128,8 +15226,8 @@ __metadata:
|
|||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"node-gyp@npm:latest":
|
"node-gyp@npm:latest":
|
||||||
version: 9.2.0
|
version: 9.3.0
|
||||||
resolution: "node-gyp@npm:9.2.0"
|
resolution: "node-gyp@npm:9.3.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
env-paths: ^2.2.0
|
env-paths: ^2.2.0
|
||||||
glob: ^7.1.4
|
glob: ^7.1.4
|
||||||
@@ -15143,7 +15241,7 @@ __metadata:
|
|||||||
which: ^2.0.2
|
which: ^2.0.2
|
||||||
bin:
|
bin:
|
||||||
node-gyp: bin/node-gyp.js
|
node-gyp: bin/node-gyp.js
|
||||||
checksum: 91f0589eabbd37f0d4e3fe9918f1f9e25afc707f6e107f0133be19c5aac62e731d92abdc2b106258665a4487b18cc2878d3fcd3dc2c6cffd68da1cb2a5ccf450
|
checksum: 589ddd3ed967724ef425f9624bfa47cf73022640ab3eba6d556e92cdc4ddef33b63fce3a467c93b995a3f61df92eafd3c3d1e8dbe4a2c00c383334487dea99c3
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@@ -16813,6 +16911,22 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"reduce-extract@npm:^1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "reduce-extract@npm:1.0.0"
|
resolution: "reduce-extract@npm:1.0.0"
|
||||||
@@ -18288,6 +18402,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"statuses@npm:2.0.1":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "statuses@npm:2.0.1"
|
resolution: "statuses@npm:2.0.1"
|
||||||
@@ -19035,6 +19156,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"tinypool@npm:^0.3.0":
|
||||||
version: 0.3.0
|
version: 0.3.0
|
||||||
resolution: "tinypool@npm:0.3.0"
|
resolution: "tinypool@npm:0.3.0"
|
||||||
@@ -20487,7 +20615,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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
|
version: 3.1.7
|
||||||
resolution: "vite@npm:3.1.7"
|
resolution: "vite@npm:3.1.7"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -20519,6 +20647,42 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"vitest@npm:0.24.1, vitest@npm:^0.24.1":
|
||||||
version: 0.24.1
|
version: 0.24.1
|
||||||
resolution: "vitest@npm:0.24.1"
|
resolution: "vitest@npm:0.24.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user