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?
|
||||
options:
|
||||
- discord.js
|
||||
- brokers
|
||||
- builders
|
||||
- collection
|
||||
- rest
|
||||
|
||||
3
.github/labeler.yml
vendored
3
.github/labeler.yml
vendored
@@ -5,6 +5,9 @@ apps:website:
|
||||
- apps/website/*
|
||||
- apps/website/**/*
|
||||
|
||||
packages:brokers:
|
||||
- packages/brokers/*
|
||||
- packages/brokers/**/*
|
||||
packages:builders:
|
||||
- packages/builders/*
|
||||
- packages/builders/**/*
|
||||
|
||||
2
.github/labels.yml
vendored
2
.github/labels.yml
vendored
@@ -50,6 +50,8 @@
|
||||
color: e4e669
|
||||
- name: need repro
|
||||
color: c66037
|
||||
- name: packages:brokers
|
||||
color: fbca04
|
||||
- name: packages:builders
|
||||
color: fbca04
|
||||
- name: packages:collection
|
||||
|
||||
2
.github/workflows/documentation.yml
vendored
2
.github/workflows/documentation.yml
vendored
@@ -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 }}
|
||||
|
||||
2
.github/workflows/npm-auto-deprecate.yml
vendored
2
.github/workflows/npm-auto-deprecate.yml
vendored
@@ -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 }}
|
||||
|
||||
2
.github/workflows/publish-dev.yml
vendored
2
.github/workflows/publish-dev.yml
vendored
@@ -10,6 +10,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- package: '@discordjs/brokers'
|
||||
folder: 'brokers'
|
||||
- package: '@discordjs/builders'
|
||||
folder: 'builders'
|
||||
- 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 =
|
||||
"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
|
||||
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:
|
||||
|
||||
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
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user