首次提交

This commit is contained in:
2026-07-13 12:06:16 +08:00
commit e3c810b1a6
1690 changed files with 228324 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
name: ci
on:
push:
branches:
- main
pull_request:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x, '*']
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Install
run: |
npm install
- name: Check licenses
run: |
npm run license-checker
- name: Run tests
run: |
npm run test:ci
- name: Coveralls Parallel
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.github_token }}
parallel: true
coverage:
needs: test
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) Aedes Contributors
Copyright (c) 2015-2020 Matteo Collina, http://matteocollina.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+304
View File
@@ -0,0 +1,304 @@
# aedes-persistence
![](https://github.com/moscajs/aedes-persistence/workflows/ci/badge.svg)
[![Dependencies Status](https://david-dm.org/moscajs/aedes-persistence/status.svg)](https://david-dm.org/moscajs/aedes-persistence)
[![devDependencies Status](https://david-dm.org/moscajs/aedes-persistence/dev-status.svg)](https://david-dm.org/moscajs/aedes-persistence?type=dev)
<br/>
[![Known Vulnerabilities](https://snyk.io/test/github/moscajs/aedes-persistence/badge.svg)](https://snyk.io/test/github/moscajs/aedes-persistence)
[![Coverage Status](https://coveralls.io/repos/moscajs/aedes-persistence/badge.svg?branch=master&service=github)](https://coveralls.io/github/moscajs/aedes-persistence?branch=master)
[![NPM version](https://img.shields.io/npm/v/aedes-persistence.svg?style=flat)](https://www.npmjs.com/package/aedes-persistence)
[![NPM downloads](https://img.shields.io/npm/dm/aedes-persistence.svg?style=flat)](https://www.npmjs.com/package/aedes-persistence)
The spec for an [Aedes](http://npm.im/aedes) persistence, with abstract
tests and a fast in-memory implementation.
* [Install](#install)
* [API](#api)
* [Implement another persistence](#implement)
* [License](#license)
<a name="install"></a>
## Install
To install aedes-persistence, simply use npm:
```
npm install aedes-persistence --save
```
<a name="api"></a>
## API
* <a href="#constructor"><code><b>persistence()</b></code></a>
* <a href="#storeRetained"><code>instance.<b>storeRetained()</b></code></a>
* <a href="#createRetainedStream"><code>instance.<b>createRetainedStream()</b></code></a>
* <a href="#createRetainedStreamCombi"><code>instance.<b>createRetainedStreamCombi()</b></code></a>
* <a href="#addSubscriptions"><code>instance.<b>addSubscriptions()</b></code></a>
* <a href="#removeSubscriptions"><code>instance.<b>removeSubscriptions()</b></code></a>
* <a href="#subscriptionsByClient"><code>instance.<b>subscriptionsByClient()</b></code></a>
* <a href="#countOffline"><code>instance.<b>countOffline()</b></code></a>
* <a href="#subscriptionsByTopic"><code>instance.<b>subscriptionsByTopic()</b></code></a>
* <a href="#cleanSubscriptions"><code>instance.<b>cleanSubscriptions()</b></code></a>
* <a href="#outgoingEnqueue"><code>instance.<b>outgoingEnqueue()</b></code></a>
* <a href="#outgoingEnqueueCombi"><code>instance.<b>outgoingEnqueueCombi()</b></code></a>
* <a href="#outgoingUpdate"><code>instance.<b>outgoingUpdate()</b></code></a>
* <a href="#outgoingClearMessageId"><code>instance.<b>outgoingClearMessageId()</b></code></a>
* <a href="#outgoingStream"><code>instance.<b>outgoingStream()</b></code></a>
* <a href="#incomingStorePacket"><code>instance.<b>incomingStorePacket()</b></code></a>
* <a href="#incomingGetPacket"><code>instance.<b>incomingGetPacket()</b></code></a>
* <a href="#incomingDelPacket"><code>instance.<b>incomingDelPacket()</b></code></a>
* <a href="#putWill"><code>instance.<b>putWill()</b></code></a>
* <a href="#getWill"><code>instance.<b>getWill()</b></code></a>
* <a href="#delWill"><code>instance.<b>delWill()</b></code></a>
* <a href="#streamWill"><code>instance.<b>streamWill()</b></code></a>
* <a href="#getClientList"><code>instance.<b>getClientList()</b></code></a>
* <a href="#destroy"><code>instance.<b>destroy()</b></code></a>
-------------------------------------------------------
<a name="constructor"></a>
### persistence([opts])
Creates a new instance of a persistence, that is already ready to
operate. The default implementation is in-memory only.
-------------------------------------------------------
<a name="storeRetained"></a>
### instance.storeRetained(packet, callback(err))
Store a retained message, calls the callback when it was saved.
-------------------------------------------------------
<a name="createRetainedStream"></a>
### instance.createRetainedStream(pattern)
Return a stream that will load all retained messages matching the given
pattern (according to the MQTT spec) asynchronously. Deprecated.
-------------------------------------------------------
<a name="createRetainedStreamCombi"></a>
### instance.createRetainedStreamCombi(patterns)
Return a stream that will load all retained messages matching given
patterns (according to the MQTT spec) asynchronously.
-------------------------------------------------------
<a name="addSubscriptions"></a>
### instance.addSubscriptions(client, subscriptions, callback(err, client))
Add the given offline subscriptions for the given
[Client](https://github.com/moscajs/aedes#client). The client __must__
have connected with `clean: false`, as this is not checked here.
This is called when a client issue a SUBSCRIBE packet.
`subscriptions` is in the same format of the `subscribe` property in the
[SUBSCRIBE](https://github.com/mqttjs/mqtt-packet#subscribe) packet:
```js
[{
topic: 'hello/world',
qos: 1,
}, {
topic: 'hello/#',
qos: 2,
}]
```
-------------------------------------------------------
<a name="removeSubscriptions"></a>
### instance.removeSubscriptions(client, subscriptions, callback(err, client))
The inverse of [`addSubscriptions`](#addSubscriptions) but subscriptions is an array of topic names.
-------------------------------------------------------
<a name="subscriptionsByClient"></a>
### instance.subscriptionsByClient(client, callback(err, subscriptions, client))
Returns all the offline subscriptions for the given client. Called when
a client with `clean: false` connects to restore its subscriptions.
`subscriptions` is in the same format of the `subscribe` property in the
[SUBSCRIBE](https://github.com/mqttjs/mqtt-packet#subscribe) packet:
```js
[{
topic: 'hello/world',
qos: 1,
}, {
topic: 'hello/#',
qos: 2,
}]
```
-------------------------------------------------------
<a name="countOffline"></a>
### instance.countOffline(cb(err, numOfSubscriptions, numOfClients))
Returns the number of offline subscriptions and the number of offline
clients.
-------------------------------------------------------
<a name="subscriptionsByTopic"></a>
### instance.subscriptionsByTopic(pattern, callback(err, subscriptions))
Returns all the offline subscriptions matching the given pattern. Called when
a PUBLISH with `qos: 1` or `qos: 2` is received.
The subscriptions are in the format:
```js
{
clientId: client.id,
topic: sub.topic,
qos: sub.qos
}
```
-------------------------------------------------------
<a name="cleanSubscriptions"></a>
### instance.cleanSubscriptions(client, callback(err, client))
Removes all offline subscriptions for a given client.
-------------------------------------------------------
<a name="outgoingEnqueue"></a>
### instance.outgoingEnqueue(subscription, packet, callback(err))
Enqueue a potentially offline delivery. `subscription` is one of the
objects returned by [`subscriptionsByTopic`](#subscriptionsByTopic). Deprecated.
-------------------------------------------------------
<a name="outgoingEnqueueCombi"></a>
### instance.outgoingEnqueueCombi(subscriptions, packet, callback(err))
Enqueue a potentially offline delivery. `subscriptions` is the whole subscriptions
objects returned by [`subscriptionsByTopic`](#subscriptionsByTopic).
-------------------------------------------------------
<a name="outgoingUpdate"></a>
### instance.outgoingUpdate(client, packet, callback(err))
Called before a (potentially) offline packet is delivered, the caller
should update the `packet.messageId` before updating.
-------------------------------------------------------
<a name="outgoingClearMessageId"></a>
### instance.outgoingClearMessageId(client, packet, callback(err, packet))
Removes a packet with the given `messageId` (passing a PUBACK is ok)
from the persistence. Passes back original packet to the callback.
-------------------------------------------------------
<a name="outgoingStream"></a>
### instance.outgoingStream(client)
Return a stream that will load all offline messages for the given client asynchronously.
-------------------------------------------------------
<a name="incomingStorePacket"></a>
### instance.incomingStorePacket(client, packet, cb(err, packet))
Store an incoming packet for the given client. Used for QoS 2.
-------------------------------------------------------
<a name="incomingGetPacket"></a>
### instance.incomingGetPacket(client, packet, cb(err, packet))
Retrieve an incoming packet with the same `messageId` for the given client. Used for QoS 2.
-------------------------------------------------------
<a name="incomingDelPacket"></a>
### instance.incomingDelPacket(client, packet, cb(err, packet))
Deletes incoming packet with the same `messageId` for the given client. Used for QoS 2.
-------------------------------------------------------
<a name="putWill"></a>
### instance.putWill(client, packet, cb(err))
Stores the will of a client. Used to support multi-broker environments
and to not lose wills in case of a crash.
-------------------------------------------------------
<a name="getWill"></a>
### instance.getWill(client, packet, cb(err))
Retrieves the will of a client. Used to support multi-broker environments
and to not lose wills in case of a crash.
-------------------------------------------------------
<a name="delWill"></a>
### instance.delWill(client, packet, cb(err))
Removes the will of a client. Used to support multi-broker environments
and to not lose wills in case of a crash.
-------------------------------------------------------
<a name="streamWill"></a>
### instance.streamWill(brokers)
Streams all the wills for the given brokers. The brokers are in the
format:
```js
{
mybroker: {
brokerId: 'mybroker'
}
}
```
-------------------------------------------------------
<a name="getCLientList"></a>
### instance.getClientList(topic)
Returns a stream which has all the clientIds subscribed to the
specified topic
<a name="destroy"></a>
### instance.destroy(cb(err))
Destroy current persistence. Use callback `cb(err)` to catch errors if any
<a name="implement"></a>
## Implement another persistence
A persistence needs to pass all tests defined in
[./abstract.js](./abstract.js). You can import and use that test suite
in the following manner:
```js
const test = require('tape').test
const myperst = require('./')
const abs = require('aedes-persistence/abstract')
abs({
test: test,
persistence: myperst
})
```
If you require some async stuff before returning, a callback is also
supported:
```js
const test = require('tape').test
const myperst = require('./')
const abs = require('aedes-persistence/abstract')
const clean = require('./clean') // invented module
abs({
test: test,
buildEmitter: require('mymqemitter'), // optional
persistence: function build (cb) {
clean(function (err) {
cb(err, myperst())
})
}
})
```
## Collaborators
* [__Gnought__](https://github.com/gnought)
## License
MIT
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
{
"name": "aedes-persistence",
"version": "9.1.2",
"description": "The spec for an Aedes persistence, with abstract tests and a fast in-memory implementation.",
"main": "persistence.js",
"types": "types/index.d.ts",
"scripts": {
"lint": "standard --verbose | snazzy",
"lint-fix": "standard --fix",
"unit": "tape test.js | faucet",
"test:typescript": "tsd",
"test": "npm run lint && npm run unit && tsd",
"coverage": "nyc --reporter=lcov tape test.js",
"test:ci": "npm run lint && npm run coverage && npm run test:typescript",
"license-checker": "license-checker --production --onlyAllow='MIT;ISC;BSD-3-Clause;BSD-2-Clause'",
"release": "read -p 'GITHUB_TOKEN: ' GITHUB_TOKEN && export GITHUB_TOKEN=$GITHUB_TOKEN && release-it --disable-metrics"
},
"release-it": {
"github": {
"release": true
},
"git": {
"tagName": "v${version}"
},
"hooks": {
"before:init": [
"npm run test"
]
},
"npm": {
"publish": true
}
},
"pre-commit": [
"test"
],
"repository": {
"type": "git",
"url": "git+https://github.com/moscajs/aedes-persistence.git"
},
"keywords": [
"mqtt",
"broker",
"persistence",
"aedes"
],
"author": "Matteo Collina <hello@matteocollina.com>",
"contributors": [
{
"name": "Gnought",
"url": "https://github.com/gnought"
}
],
"license": "MIT",
"bugs": {
"url": "https://github.com/moscajs/aedes-persistence/issues"
},
"homepage": "https://github.com/moscajs/aedes-persistence#readme",
"engines": {
"node": ">=14"
},
"devDependencies": {
"@types/node": "^17.0.29",
"aedes": "^0.46.3",
"faucet": "0.0.1",
"license-checker": "^25.0.1",
"mqemitter": "^4.5.0",
"nyc": "^15.1.0",
"pre-commit": "^1.2.2",
"release-it": "^14.14.2",
"snazzy": "^9.0.0",
"standard": "^17.0.0",
"tape": "^5.5.3",
"tsd": "^0.20.0"
},
"dependencies": {
"aedes-packet": "^3.0.0",
"qlobber": "^7.0.0"
}
}
+335
View File
@@ -0,0 +1,335 @@
const { Readable } = require('stream')
const QlobberSub = require('qlobber/aedes/qlobber-sub')
const { QlobberTrue } = require('qlobber')
const Packet = require('aedes-packet')
const QlobberOpts = {
wildcard_one: '+',
wildcard_some: '#',
separator: '/'
}
const CREATE_ON_EMPTY = true
function * multiIterables (iterables) {
for (const iter of iterables) {
yield * iter
}
}
function * retainedMessagesByPattern (retained, pattern) {
const qlobber = new QlobberTrue(QlobberOpts)
qlobber.add(pattern)
for (const [topic, packet] of retained) {
if (qlobber.test(topic)) {
yield packet
}
}
}
function * willsByBrokers (wills, brokers) {
for (const will of wills.values()) {
if (!brokers[will.brokerId]) {
yield will
}
}
}
function * clientListbyTopic (subscriptions, topic) {
for (const [clientId, topicMap] of subscriptions) {
if (topicMap.has(topic)) {
yield clientId
}
}
}
class MemoryPersistence {
// private class members start with #
#retained
#subscriptions
#outgoing
#incoming
#wills
#clientsCount
#trie
constructor () {
// using Maps for convenience and security (risk on prototype polution)
// Map ( topic -> packet )
this.#retained = new Map()
// Map ( clientId -> Map( topic -> { qos, rh, rap, nl } ))
this.#subscriptions = new Map()
// Map ( clientId > [ packet ] }
this.#outgoing = new Map()
// Map ( clientId -> { packetId -> Packet } )
this.#incoming = new Map()
// Map( clientId -> will )
this.#wills = new Map()
this.#clientsCount = 0
this.#trie = new QlobberSub(QlobberOpts)
}
storeRetained (pkt, cb) {
const packet = Object.assign({}, pkt)
if (packet.payload.length === 0) {
this.#retained.delete(packet.topic)
} else {
this.#retained.set(packet.topic, packet)
}
cb(null)
}
createRetainedStreamCombi (patterns) {
const iterables = patterns.map((p) => {
return retainedMessagesByPattern(this.#retained, p)
})
return Readable.from(multiIterables(iterables))
}
createRetainedStream (pattern) {
return Readable.from(retainedMessagesByPattern(this.#retained, pattern))
}
addSubscriptions (client, subs, cb) {
let stored = this.#subscriptions.get(client.id)
const trie = this.#trie
if (!stored) {
stored = new Map()
this.#subscriptions.set(client.id, stored)
this.#clientsCount++
}
for (const sub of subs) {
const storedSub = stored.get(sub.topic)
if (sub.qos > 0) {
trie.add(sub.topic, {
clientId: client.id,
topic: sub.topic,
qos: sub.qos,
rh: sub.rh,
rap: sub.rap,
nl: sub.nl
})
} else if (storedSub?.qos > 0) {
trie.remove(sub.topic, {
clientId: client.id,
topic: sub.topic
})
}
stored.set(sub.topic, { qos: sub.qos, rh: sub.rh, rap: sub.rap, nl: sub.nl })
}
cb(null, client)
}
removeSubscriptions (client, subs, cb) {
const stored = this.#subscriptions.get(client.id)
const trie = this.#trie
if (stored) {
for (const topic of subs) {
const storedSub = stored.get(topic)
if (storedSub !== undefined) {
if (storedSub.qos > 0) {
trie.remove(topic, { clientId: client.id, topic })
}
stored.delete(topic)
}
}
if (stored.size === 0) {
this.#clientsCount--
this.#subscriptions.delete(client.id)
}
}
cb(null, client)
}
subscriptionsByClient (client, cb) {
let subs = null
const stored = this.#subscriptions.get(client.id)
if (stored) {
subs = []
for (const [topic, storedSub] of stored) {
subs.push({ topic, ...storedSub })
}
}
cb(null, subs, client)
}
countOffline (cb) {
return cb(null, this.#trie.subscriptionsCount, this.#clientsCount)
}
subscriptionsByTopic (pattern, cb) {
cb(null, this.#trie.match(pattern))
}
cleanSubscriptions (client, cb) {
const trie = this.#trie
const stored = this.#subscriptions.get(client.id)
if (stored) {
for (const [topic, storedSub] of stored) {
if (storedSub.qos > 0) {
trie.remove(topic, { clientId: client.id, topic })
}
}
this.#clientsCount--
this.#subscriptions.delete(client.id)
}
cb(null, client)
}
#outgoingEnqueuePerSub (sub, packet) {
const id = sub.clientId
const queue = getMapRef(this.#outgoing, id, [], CREATE_ON_EMPTY)
queue[queue.length] = new Packet(packet)
}
outgoingEnqueue (sub, packet, cb) {
this.#outgoingEnqueuePerSub(sub, packet)
process.nextTick(cb)
}
outgoingEnqueueCombi (subs, packet, cb) {
for (let i = 0; i < subs.length; i++) {
this.#outgoingEnqueuePerSub(subs[i], packet)
}
process.nextTick(cb)
}
outgoingUpdate (client, packet, cb) {
const outgoing = getMapRef(this.#outgoing, client.id, [], CREATE_ON_EMPTY)
let temp
for (let i = 0; i < outgoing.length; i++) {
temp = outgoing[i]
if (temp.brokerId === packet.brokerId) {
if (temp.brokerCounter === packet.brokerCounter) {
temp.messageId = packet.messageId
return cb(null, client, packet)
}
/*
Maximum of messageId (packet identifier) is 65535 and will be rotated,
brokerCounter is to ensure the packet identifier be unique.
The for loop is going to search which packet messageId should be updated
in the #outgoing queue.
If there is a case that brokerCounter is different but messageId is same,
we need to let the loop keep searching
*/
} else if (temp.messageId === packet.messageId) {
outgoing[i] = packet
return cb(null, client, packet)
}
}
cb(new Error('no such packet'), client, packet)
}
outgoingClearMessageId (client, packet, cb) {
const outgoing = getMapRef(this.#outgoing, client.id, [], CREATE_ON_EMPTY)
let temp
for (let i = 0; i < outgoing.length; i++) {
temp = outgoing[i]
if (temp.messageId === packet.messageId) {
outgoing.splice(i, 1)
return cb(null, temp)
}
}
cb()
}
outgoingStream (client) {
// shallow clone the outgoing queue for this client to avoid race conditions
const outgoing = [].concat(getMapRef(this.#outgoing, client.id, []))
return Readable.from(outgoing)
}
incomingStorePacket (client, packet, cb) {
const id = client.id
const store = getMapRef(this.#incoming, id, {}, CREATE_ON_EMPTY)
store[packet.messageId] = new Packet(packet)
store[packet.messageId].messageId = packet.messageId
cb(null)
}
incomingGetPacket (client, packet, cb) {
const id = client.id
const store = getMapRef(this.#incoming, id, {})
let err = null
this.#incoming.set(id, store)
if (!store[packet.messageId]) {
err = new Error('no such packet')
}
cb(err, store[packet.messageId])
}
incomingDelPacket (client, packet, cb) {
const id = client.id
const store = getMapRef(this.#incoming, id, {})
const toDelete = store[packet.messageId]
let err = null
if (!toDelete) {
err = new Error('no such packet')
} else {
delete store[packet.messageId]
}
cb(err)
}
putWill (client, packet, cb) {
packet.brokerId = this.broker.id
packet.clientId = client.id
this.#wills.set(client.id, packet)
cb(null, client)
}
getWill (client, cb) {
cb(null, this.#wills.get(client.id), client)
}
delWill (client, cb) {
const will = this.#wills.get(client.id)
this.#wills.delete(client.id)
cb(null, will, client)
}
streamWill (brokers = {}) {
return Readable.from(willsByBrokers(this.#wills, brokers))
}
getClientList (topic) {
return Readable.from(clientListbyTopic(this.#subscriptions, topic))
}
destroy (cb) {
this.#retained = null
if (cb) {
cb(null)
}
}
}
function getMapRef (map, key, ifEmpty, createOnEmpty = false) {
const value = map.get(key)
if (value === undefined && createOnEmpty) {
map.set(key, ifEmpty)
}
return value || ifEmpty
}
module.exports = () => { return new MemoryPersistence() }
module.exports.Packet = Packet
+10
View File
@@ -0,0 +1,10 @@
'use strict'
const test = require('tape').test
const memory = require('./')
const abs = require('./abstract')
abs({
test,
persistence: memory
})
+283
View File
@@ -0,0 +1,283 @@
import type { Brokers, Client, Subscription } from "aedes";
import type { AedesPacket } from "aedes-packet";
import type { Readable } from "stream";
export type { AedesPacket as Packet } from "aedes-packet";
type ClientId = Client["id"];
type MessageId = AedesPacket["messageId"];
type Topic = Subscription["topic"];
type TopicPattern = Subscription["topic"];
type QoS = Subscription["qos"];
type Incoming = {
// tsc accepts:
// [messageId: MessageId]: AedesPacket;
// Workaround for tsd 0.20:
[messageId: number]: AedesPacket;
};
export interface AedesPersistenceSubscription {
clientId: ClientId;
topic: Topic;
qos?: QoS;
}
export type CallbackError = Error | null | undefined;
export type WillPacket = AedesPacket & { [key: string]: any };
export interface AedesPersistence {
storeRetained: (
packet: AedesPacket,
cb: (error: CallbackError) => void,
) => void;
createRetainedStream: (pattern: TopicPattern) => Readable;
createRetainedStreamCombi: (patterns: TopicPattern[]) => Readable;
addSubscriptions: (
client: Client,
subs: Subscription[],
cb: (error: CallbackError, client: Client) => void,
) => void;
removeSubscriptions: (
client: Client,
subs: Subscription[],
cb: (error: CallbackError, client: Client) => void,
) => void;
subscriptionsByClient: (
client: Client,
cb: (
error: CallbackError,
subs: { topic: Topic; qos: QoS }[],
client: Client,
) => void,
) => void;
countOffline: (
cb: (
error: CallbackError,
subscriptionsCount: number,
clientsCount: number,
) => void,
) => void;
subscriptionsByTopic: (
pattern: TopicPattern,
cb: (error: CallbackError, subs: AedesPersistenceSubscription[]) => void,
) => void;
cleanSubscriptions: (
client: Client,
cb: (error: CallbackError, client: Client) => void,
) => void;
outgoingEnqueue: (
sub: { clientId: ClientId },
packet: AedesPacket,
cb: (error: CallbackError) => void,
) => void;
outgoingEnqueueCombi: (
subs: { clientId: ClientId }[],
packet: AedesPacket,
cb: (error: CallbackError) => void,
) => void;
outgoingUpdate: (
client: Client,
packet: AedesPacket,
cb: (error: CallbackError, client: Client, packet: AedesPacket) => void,
) => void;
outgoingClearMessageId: (
client: Client,
packet: AedesPacket,
cb: (error?: CallbackError, packet?: AedesPacket) => void,
) => void;
outgoingStream: (client: Client) => Readable;
incomingStorePacket: (
client: Client,
packet: AedesPacket,
cb: (error: CallbackError) => void,
) => void;
incomingGetPacket: (
client: Client,
packet: AedesPacket,
cb: (error: CallbackError, packet: AedesPacket) => void,
) => void;
incomingDelPacket: (
client: Client,
packet: AedesPacket,
cb: (error: CallbackError) => void,
) => void;
putWill: (
client: Client,
packet: AedesPacket,
cb: (error: CallbackError, client: Client) => void,
) => void;
getWill: (
client: Client,
cb: (error: CallbackError, will: WillPacket, client: Client) => void,
) => void;
delWill: (
client: Client,
cb: (error: CallbackError, will: WillPacket, client: Client) => void,
) => void;
streamWill: (brokers: Brokers) => Readable;
getClientList: (topic: string) => Readable;
destroy: (cb?: (error: CallbackError) => void) => void;
}
export class AedesMemoryPersistence implements AedesPersistence {
#retained: Map<Topic, AedesPacket>;
#subscriptions: Map<
ClientId,
Map<
Topic,
QoS
>
>;
#clientsCount: number;
#trie: any;
#outgoing: Map<ClientId, AedesPacket[]>;
#incoming: Map<ClientId, Incoming>;
#wills: Map<ClientId, WillPacket>;
constructor();
storeRetained: (
packet: AedesPacket,
cb: (error: CallbackError) => void,
) => void;
createRetainedStream: (pattern: TopicPattern) => Readable;
createRetainedStreamCombi: (patterns: TopicPattern[]) => Readable;
addSubscriptions: (
client: Client,
subs: Subscription[],
cb: (error: CallbackError, client: Client) => void,
) => void;
removeSubscriptions: (
client: Client,
subs: Subscription[],
cb: (error: CallbackError, client: Client) => void,
) => void;
subscriptionsByClient: (
client: Client,
cb: (
error: CallbackError,
subs: { topic: string; qos: QoS }[],
client: Client,
) => void,
) => void;
countOffline: (
cb: (
error: CallbackError,
subscriptionsCount: number,
clientsCount: number,
) => void,
) => void;
subscriptionsByTopic: (
pattern: string,
cb: (error: CallbackError, subs: AedesPersistenceSubscription[]) => void,
) => void;
cleanSubscriptions: (
client: Client,
cb: (error: CallbackError, client: Client) => void,
) => void;
#outgoingEnqueuePerSub: (
sub: { clientId: ClientId },
packet: AedesPacket,
) => void;
outgoingEnqueue: (
sub: { clientId: ClientId },
packet: AedesPacket,
cb: (error: CallbackError) => void,
) => void;
outgoingEnqueueCombi: (
sub: { clientId: ClientId }[],
packet: AedesPacket,
cb: (error: CallbackError) => void,
) => void;
outgoingUpdate: (
client: Client,
packet: AedesPacket,
cb: (error: CallbackError, client: Client, packet: AedesPacket) => void,
) => void;
outgoingClearMessageId: (
client: Client,
packet: AedesPacket,
cb: (error?: CallbackError, packet?: AedesPacket) => void,
) => void;
outgoingStream: (client: Client) => Readable;
incomingStorePacket: (
client: Client,
packet: AedesPacket,
cb: (error: CallbackError) => void,
) => void;
incomingGetPacket: (
client: Client,
packet: AedesPacket,
cb: (error: CallbackError, packet: AedesPacket) => void,
) => void;
incomingDelPacket: (
client: Client,
packet: AedesPacket,
cb: (error: CallbackError) => void,
) => void;
putWill: (
client: Client,
packet: AedesPacket,
cb: (error: CallbackError, client: Client) => void,
) => void;
getWill: (
client: Client,
cb: (error: CallbackError, will: WillPacket, client: Client) => void,
) => void;
delWill: (
client: Client,
cb: (error: CallbackError, will: WillPacket, client: Client) => void,
) => void;
streamWill: (brokers: Brokers) => Readable;
getClientList: (topic: string) => Readable;
destroy: (cb?: (error: CallbackError) => void) => void;
}
export default function aedesMemoryPersistence(): AedesMemoryPersistence;
+168
View File
@@ -0,0 +1,168 @@
import type { Brokers, Client, Subscription } from 'aedes';
import type { AedesPacket } from 'aedes-packet';
import type { QoS } from 'mqtt-packet';
import type { Readable } from 'stream';
import { expectType } from 'tsd';
import aedesMemoryPersistence, {
AedesMemoryPersistence,
AedesPersistenceSubscription,
CallbackError,
WillPacket,
} from '.';
expectType<AedesMemoryPersistence>(aedesMemoryPersistence());
expectType<void>(
aedesMemoryPersistence().storeRetained(
{
brokerId: '',
brokerCounter: 1,
cmd: 'publish',
qos: 0,
dup: false,
retain: false,
topic: 'test',
payload: 'test',
},
(err: CallbackError) => {}
)
);
expectType<void>(
aedesMemoryPersistence().addSubscriptions(
{} as Client,
[] as Subscription[],
(err: CallbackError) => {}
)
);
expectType<void>(
aedesMemoryPersistence().removeSubscriptions(
{} as Client,
[] as Subscription[],
(err: CallbackError) => {}
)
);
expectType<void>(
aedesMemoryPersistence().subscriptionsByClient(
{} as Client,
(
error: CallbackError,
subs: { topic: string; qos: QoS }[],
client: Client
) => {}
)
);
expectType<void>(
aedesMemoryPersistence().countOffline(
(
error: CallbackError,
subscriptionsCount: number,
clientsCount: number
) => {}
)
);
expectType<void>(
aedesMemoryPersistence().subscriptionsByTopic(
'pattern',
(error: CallbackError, subs: AedesPersistenceSubscription[]) => {}
)
);
expectType<void>(
aedesMemoryPersistence().cleanSubscriptions(
{} as Client,
(error: CallbackError, client: Client) => {}
)
);
expectType<void>(
aedesMemoryPersistence().outgoingEnqueue(
{ clientId: '' },
{} as AedesPacket,
(error: CallbackError) => {}
)
);
expectType<void>(
aedesMemoryPersistence().outgoingEnqueueCombi(
[{ clientId: '' }],
{} as AedesPacket,
(error: CallbackError) => {}
)
);
expectType<void>(
aedesMemoryPersistence().outgoingUpdate(
{} as Client,
{} as AedesPacket,
(error: CallbackError, client: Client, packet: AedesPacket) => {}
)
);
expectType<void>(
aedesMemoryPersistence().outgoingClearMessageId(
{} as Client,
{} as AedesPacket,
(error: CallbackError, packet?: AedesPacket) => {}
)
);
expectType<Readable>(aedesMemoryPersistence().outgoingStream({} as Client));
expectType<void>(
aedesMemoryPersistence().incomingStorePacket(
{} as Client,
{} as AedesPacket,
(error: CallbackError) => {}
)
);
expectType<void>(
aedesMemoryPersistence().incomingGetPacket(
{} as Client,
{} as AedesPacket,
(error: CallbackError, packet: AedesPacket) => {}
)
);
expectType<void>(
aedesMemoryPersistence().incomingDelPacket(
{} as Client,
{} as AedesPacket,
(error: CallbackError) => {}
)
);
expectType<void>(
aedesMemoryPersistence().putWill(
{} as Client,
{} as AedesPacket,
(error: CallbackError, client: Client) => {}
)
);
expectType<void>(
aedesMemoryPersistence().getWill(
{} as Client,
(error: CallbackError, will: WillPacket, client: Client) => {}
)
);
expectType<void>(
aedesMemoryPersistence().delWill(
{} as Client,
(error: CallbackError, will: WillPacket, client: Client) => {}
)
);
expectType<Readable>(aedesMemoryPersistence().streamWill({} as Brokers));
expectType<Readable>(aedesMemoryPersistence().getClientList('topic'));
expectType<void>(aedesMemoryPersistence().destroy());
expectType<void>(aedesMemoryPersistence().destroy(() => {}));