mqtt-plus 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dst-stage1/mqtt-plus-auth.js +7 -3
- package/dst-stage1/mqtt-plus-base.js +7 -1
- package/dst-stage1/mqtt-plus-codec.d.ts +2 -2
- package/dst-stage1/mqtt-plus-codec.js +8 -8
- package/dst-stage1/mqtt-plus-encode.js +1 -1
- package/dst-stage1/mqtt-plus-event.js +14 -13
- package/dst-stage1/mqtt-plus-msg.d.ts +21 -6
- package/dst-stage1/mqtt-plus-msg.js +86 -36
- package/dst-stage1/mqtt-plus-options.d.ts +1 -0
- package/dst-stage1/mqtt-plus-options.js +1 -0
- package/dst-stage1/mqtt-plus-service.d.ts +0 -2
- package/dst-stage1/mqtt-plus-service.js +20 -53
- package/dst-stage1/mqtt-plus-sink.d.ts +2 -2
- package/dst-stage1/mqtt-plus-sink.js +99 -56
- package/dst-stage1/mqtt-plus-source.d.ts +1 -2
- package/dst-stage1/mqtt-plus-source.js +91 -56
- package/dst-stage1/mqtt-plus-trace.d.ts +2 -2
- package/dst-stage1/mqtt-plus-trace.js +5 -8
- package/dst-stage1/mqtt-plus-util.d.ts +23 -2
- package/dst-stage1/mqtt-plus-util.js +144 -35
- package/dst-stage1/mqtt-plus-version.d.ts +4 -0
- package/dst-stage1/mqtt-plus-version.js +42 -0
- package/dst-stage1/mqtt-plus.d.ts +1 -0
- package/dst-stage1/tsc.tsbuildinfo +1 -1
- package/dst-stage2/mqtt-plus.cjs.js +440 -241
- package/dst-stage2/mqtt-plus.esm.js +440 -241
- package/dst-stage2/mqtt-plus.umd.js +11 -11
- package/etc/vite.mts +5 -1
- package/package.json +1 -1
- package/src/mqtt-plus-auth.ts +7 -3
- package/src/mqtt-plus-base.ts +8 -2
- package/src/mqtt-plus-codec.ts +7 -7
- package/src/mqtt-plus-encode.ts +1 -1
- package/src/mqtt-plus-event.ts +15 -14
- package/src/mqtt-plus-msg.ts +134 -65
- package/src/mqtt-plus-options.ts +10 -8
- package/src/mqtt-plus-service.ts +26 -60
- package/src/mqtt-plus-sink.ts +120 -59
- package/src/mqtt-plus-source.ts +106 -62
- package/src/mqtt-plus-trace.ts +6 -9
- package/src/mqtt-plus-util.ts +166 -38
- package/src/mqtt-plus-version.ts +48 -0
- package/src/mqtt-plus.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
ChangeLog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
1.3.0 (2026-02-07)
|
|
6
|
+
------------------
|
|
7
|
+
|
|
8
|
+
- IMPROVEMENT: add credit-based flow control to sink/source facility
|
|
9
|
+
- IMPROVEMENT: make "buffer" and "stream" fields always mutual-exlusive
|
|
10
|
+
- IMPROVEMENT: provide version field in protocol messages
|
|
11
|
+
|
|
12
|
+
1.2.1 (2026-02-07)
|
|
13
|
+
------------------
|
|
14
|
+
|
|
15
|
+
- REFACTOR: use a reference counting subscription class
|
|
16
|
+
- CLEANUP: improve internal validation logic
|
|
17
|
+
- CLEANUP: various code cleanups
|
|
18
|
+
|
|
5
19
|
1.2.0 (2026-02-06)
|
|
6
20
|
------------------
|
|
7
21
|
|
|
@@ -41,9 +41,9 @@ export class AuthTrait extends MetaTrait {
|
|
|
41
41
|
if (credential.length === 0)
|
|
42
42
|
throw new Error("credential must not be empty");
|
|
43
43
|
/* use a derived key with minimum length of 32 for JWT HS256 */
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
this._credential = pbkdf2.deriveKey(sha256.SHA256,
|
|
44
|
+
const pass = new TextEncoder().encode(credential);
|
|
45
|
+
const salt = new TextEncoder().encode("mqtt-plus");
|
|
46
|
+
this._credential = pbkdf2.deriveKey(sha256.SHA256, pass, salt, 600000, 32);
|
|
47
47
|
}
|
|
48
48
|
/* issue client-side token on server-side */
|
|
49
49
|
async issue(payload) {
|
|
@@ -93,6 +93,10 @@ export class AuthTrait extends MetaTrait {
|
|
|
93
93
|
continue;
|
|
94
94
|
if (payload.id && payload.id !== clientId)
|
|
95
95
|
continue;
|
|
96
|
+
if (!Array.isArray(payload.roles))
|
|
97
|
+
continue;
|
|
98
|
+
if (payload.roles.length > 64)
|
|
99
|
+
continue;
|
|
96
100
|
for (const role of roles) {
|
|
97
101
|
if (payload.roles.includes(role)) {
|
|
98
102
|
authenticated = true;
|
|
@@ -37,8 +37,14 @@ export class BaseTrait extends TraceTrait {
|
|
|
37
37
|
get(_target, prop, _receiver) {
|
|
38
38
|
if (prop === "isFakeProxy")
|
|
39
39
|
return true;
|
|
40
|
-
else
|
|
40
|
+
else if (typeof prop === "string" && ["on", "off", "once"].includes(prop))
|
|
41
41
|
return () => { };
|
|
42
|
+
else
|
|
43
|
+
return () => {
|
|
44
|
+
throw new Error(`Underlying MQTT operation "${String(prop)}" called ` +
|
|
45
|
+
"on a null MQTT client -- only MQTT+ \"emit({ ..., dry: true })\" " +
|
|
46
|
+
"is supported in this special operation mode");
|
|
47
|
+
};
|
|
42
48
|
}
|
|
43
49
|
});
|
|
44
50
|
}
|
|
@@ -7,10 +7,10 @@ export declare class JSONX {
|
|
|
7
7
|
static parse(json: string): unknown;
|
|
8
8
|
}
|
|
9
9
|
declare class Codec {
|
|
10
|
-
private
|
|
10
|
+
private format;
|
|
11
11
|
private types;
|
|
12
12
|
private tags;
|
|
13
|
-
constructor(
|
|
13
|
+
constructor(format: "cbor" | "json");
|
|
14
14
|
encode(data: unknown): Uint8Array | string;
|
|
15
15
|
decode(data: Uint8Array | string): unknown;
|
|
16
16
|
}
|
|
@@ -46,8 +46,8 @@ export class JSONX {
|
|
|
46
46
|
}
|
|
47
47
|
/* the encoder/decoder abstraction */
|
|
48
48
|
class Codec {
|
|
49
|
-
constructor(
|
|
50
|
-
this.
|
|
49
|
+
constructor(format) {
|
|
50
|
+
this.format = format;
|
|
51
51
|
this.types = new CBOR.TypeEncoderMap();
|
|
52
52
|
this.tags = new Map();
|
|
53
53
|
/* support direct encoding/decoding of Buffer */
|
|
@@ -61,7 +61,7 @@ class Codec {
|
|
|
61
61
|
}
|
|
62
62
|
encode(data) {
|
|
63
63
|
let result;
|
|
64
|
-
if (this.
|
|
64
|
+
if (this.format === "cbor") {
|
|
65
65
|
try {
|
|
66
66
|
result = CBOR.encode(data, { types: this.types });
|
|
67
67
|
}
|
|
@@ -69,7 +69,7 @@ class Codec {
|
|
|
69
69
|
throw new Error("failed to encode CBOR format", { cause: ex });
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
else if (this.
|
|
72
|
+
else if (this.format === "json") {
|
|
73
73
|
try {
|
|
74
74
|
result = JSONX.stringify(data);
|
|
75
75
|
}
|
|
@@ -78,12 +78,12 @@ class Codec {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
else
|
|
81
|
-
throw new Error(`invalid format "${this.
|
|
81
|
+
throw new Error(`invalid format "${this.format}"`);
|
|
82
82
|
return result;
|
|
83
83
|
}
|
|
84
84
|
decode(data) {
|
|
85
85
|
let result;
|
|
86
|
-
if (this.
|
|
86
|
+
if (this.format === "cbor") {
|
|
87
87
|
if (!(data instanceof Uint8Array))
|
|
88
88
|
throw new Error("failed to decode CBOR format (data type is not Uint8Array)");
|
|
89
89
|
try {
|
|
@@ -93,7 +93,7 @@ class Codec {
|
|
|
93
93
|
throw new Error("failed to decode CBOR format", { cause: ex });
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
else if (this.
|
|
96
|
+
else if (this.format === "json") {
|
|
97
97
|
if (typeof data !== "string")
|
|
98
98
|
throw new Error("failed to decode JSON format (data type is not string)");
|
|
99
99
|
try {
|
|
@@ -104,7 +104,7 @@ class Codec {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
else
|
|
107
|
-
throw new Error(`invalid format "${this.
|
|
107
|
+
throw new Error(`invalid format "${this.format}"`);
|
|
108
108
|
return result;
|
|
109
109
|
}
|
|
110
110
|
}
|
|
@@ -45,7 +45,7 @@ export class EncodeTrait extends CodecTrait {
|
|
|
45
45
|
}
|
|
46
46
|
buf2arr(data, cons) {
|
|
47
47
|
let arr;
|
|
48
|
-
if (
|
|
48
|
+
if (cons === Buffer)
|
|
49
49
|
arr = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
50
50
|
else if (cons === Uint8Array)
|
|
51
51
|
arr = data;
|
|
@@ -25,7 +25,7 @@ import { nanoid } from "nanoid";
|
|
|
25
25
|
/* internal requirements */
|
|
26
26
|
import { EventEmission } from "./mqtt-plus-msg";
|
|
27
27
|
import { AuthTrait } from "./mqtt-plus-auth";
|
|
28
|
-
/* Event
|
|
28
|
+
/* Event Emission Trait */
|
|
29
29
|
export class EventTrait extends AuthTrait {
|
|
30
30
|
constructor() {
|
|
31
31
|
super(...arguments);
|
|
@@ -56,35 +56,36 @@ export class EventTrait extends AuthTrait {
|
|
|
56
56
|
if (this.events.has(name))
|
|
57
57
|
throw new Error(`event: event "${name}" already registered`);
|
|
58
58
|
/* generate the corresponding MQTT topics for broadcast and direct use */
|
|
59
|
-
const
|
|
60
|
-
const topicB = this.options.topicMake(
|
|
61
|
-
const topicD = this.options.topicMake(
|
|
59
|
+
const topicS = share ? `$share/${share}/${name}` : name;
|
|
60
|
+
const topicB = this.options.topicMake(topicS, "event-emission");
|
|
61
|
+
const topicD = this.options.topicMake(name, "event-emission", this.options.id);
|
|
62
|
+
/* remember the registration */
|
|
63
|
+
this.events.set(name, {
|
|
64
|
+
callback: callback,
|
|
65
|
+
auth
|
|
66
|
+
});
|
|
62
67
|
/* subscribe to MQTT topics */
|
|
63
68
|
await Promise.all([
|
|
64
69
|
this._subscribeTopic(topicB, { qos: 0, ...options }),
|
|
65
70
|
this._subscribeTopic(topicD, { qos: 0, ...options })
|
|
66
71
|
]).catch((err) => {
|
|
72
|
+
this.events.delete(name);
|
|
67
73
|
this._unsubscribeTopic(topicB).catch(() => { });
|
|
68
74
|
this._unsubscribeTopic(topicD).catch(() => { });
|
|
69
75
|
throw err;
|
|
70
76
|
});
|
|
71
|
-
/* remember the registration */
|
|
72
|
-
this.events.set(name, {
|
|
73
|
-
callback: callback,
|
|
74
|
-
auth
|
|
75
|
-
});
|
|
76
77
|
/* provide a registration for subsequent destruction */
|
|
77
78
|
const registration = {
|
|
78
79
|
destroy: async () => {
|
|
79
80
|
if (!this.events.has(name))
|
|
80
81
|
throw new Error(`destroy: event "${name}" not registered`);
|
|
81
|
-
|
|
82
|
-
return Promise.all([
|
|
82
|
+
await Promise.all([
|
|
83
83
|
this._unsubscribeTopic(topicB),
|
|
84
84
|
this._unsubscribeTopic(topicD)
|
|
85
85
|
]).then(() => { }).catch((err) => {
|
|
86
86
|
this.error(err, `destroy: failed to unsubscribe from topics for event "${name}"`);
|
|
87
87
|
});
|
|
88
|
+
this.events.delete(name);
|
|
88
89
|
}
|
|
89
90
|
};
|
|
90
91
|
return registration;
|
|
@@ -142,14 +143,14 @@ export class EventTrait extends AuthTrait {
|
|
|
142
143
|
if (topicMatch.name !== name)
|
|
143
144
|
throw new Error(`event name mismatch between topic "${topicMatch.name}" and payload "${name}"`);
|
|
144
145
|
const handler = this.events.get(name);
|
|
146
|
+
if (handler === undefined)
|
|
147
|
+
throw new Error(`handler for event "${name}" not found`);
|
|
145
148
|
const params = parsed.params ?? [];
|
|
146
149
|
const info = { sender: parsed.sender ?? "" };
|
|
147
150
|
if (parsed.receiver)
|
|
148
151
|
info.receiver = parsed.receiver;
|
|
149
152
|
if (parsed.meta)
|
|
150
153
|
info.meta = parsed.meta;
|
|
151
|
-
if (handler === undefined)
|
|
152
|
-
throw new Error(`handler for event "${name}" not found`);
|
|
153
154
|
if (handler.auth)
|
|
154
155
|
info.authenticated = await this.authenticated(parsed.sender, parsed.auth, handler.auth);
|
|
155
156
|
Promise.resolve().then(() => {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { APISchema } from "./mqtt-plus-api";
|
|
2
2
|
import { EncodeTrait } from "./mqtt-plus-encode";
|
|
3
|
-
type MessageType = "event-emission" | "service-call-request" | "service-call-response" | "sink-push-request" | "sink-push-response" | "sink-push-chunk" | "source-fetch-request" | "source-fetch-response" | "source-fetch-chunk";
|
|
3
|
+
type MessageType = "event-emission" | "service-call-request" | "service-call-response" | "sink-push-request" | "sink-push-response" | "sink-push-chunk" | "sink-push-credit" | "source-fetch-request" | "source-fetch-response" | "source-fetch-chunk" | "source-fetch-credit";
|
|
4
4
|
declare class Base {
|
|
5
5
|
type: MessageType;
|
|
6
6
|
id: string;
|
|
7
7
|
sender?: string | undefined;
|
|
8
8
|
receiver?: string | undefined;
|
|
9
|
+
version: string;
|
|
9
10
|
constructor(type: MessageType, id: string, sender?: string | undefined, receiver?: string | undefined);
|
|
10
11
|
}
|
|
11
12
|
export declare class EventEmission extends Base {
|
|
@@ -39,7 +40,8 @@ export declare class SinkPushResponse extends Base {
|
|
|
39
40
|
error?: string | undefined;
|
|
40
41
|
auth?: string[] | undefined;
|
|
41
42
|
meta?: Record<string, any> | undefined;
|
|
42
|
-
|
|
43
|
+
credit?: number | undefined;
|
|
44
|
+
constructor(id: string, name: string, error?: string | undefined, sender?: string, receiver?: string, auth?: string[] | undefined, meta?: Record<string, any> | undefined, credit?: number | undefined);
|
|
43
45
|
}
|
|
44
46
|
export declare class SinkPushChunk extends Base {
|
|
45
47
|
name: string;
|
|
@@ -48,12 +50,18 @@ export declare class SinkPushChunk extends Base {
|
|
|
48
50
|
final?: boolean | undefined;
|
|
49
51
|
constructor(id: string, name: string, chunk?: Uint8Array | undefined, error?: string | undefined, final?: boolean | undefined, sender?: string, receiver?: string);
|
|
50
52
|
}
|
|
53
|
+
export declare class SinkPushCredit extends Base {
|
|
54
|
+
name: string;
|
|
55
|
+
credit: number;
|
|
56
|
+
constructor(id: string, name: string, credit: number, sender?: string, receiver?: string);
|
|
57
|
+
}
|
|
51
58
|
export declare class SourceFetchRequest extends Base {
|
|
52
59
|
name: string;
|
|
53
60
|
params?: any[] | undefined;
|
|
54
61
|
auth?: string[] | undefined;
|
|
55
62
|
meta?: Record<string, any> | undefined;
|
|
56
|
-
|
|
63
|
+
credit?: number | undefined;
|
|
64
|
+
constructor(id: string, name: string, params?: any[] | undefined, sender?: string, receiver?: string, auth?: string[] | undefined, meta?: Record<string, any> | undefined, credit?: number | undefined);
|
|
57
65
|
}
|
|
58
66
|
export declare class SourceFetchResponse extends Base {
|
|
59
67
|
name: string;
|
|
@@ -69,17 +77,24 @@ export declare class SourceFetchChunk extends Base {
|
|
|
69
77
|
final?: boolean | undefined;
|
|
70
78
|
constructor(id: string, name: string, chunk?: Uint8Array | undefined, error?: string | undefined, final?: boolean | undefined, sender?: string, receiver?: string);
|
|
71
79
|
}
|
|
80
|
+
export declare class SourceFetchCredit extends Base {
|
|
81
|
+
name: string;
|
|
82
|
+
credit: number;
|
|
83
|
+
constructor(id: string, name: string, credit: number, sender?: string, receiver?: string);
|
|
84
|
+
}
|
|
72
85
|
declare class Msg {
|
|
73
86
|
makeEventEmission(id: string, name: string, params?: any[], sender?: string, receiver?: string, auth?: string[], meta?: Record<string, any>): EventEmission;
|
|
74
87
|
makeServiceCallRequest(id: string, name: string, params?: any[], sender?: string, receiver?: string, auth?: string[], meta?: Record<string, any>): ServiceCallRequest;
|
|
75
88
|
makeServiceCallResponse(id: string, result?: any, error?: string, sender?: string, receiver?: string): ServiceCallResponse;
|
|
76
89
|
makeSinkPushRequest(id: string, name: string, params?: any[], sender?: string, receiver?: string, auth?: string[], meta?: Record<string, any>): SinkPushRequest;
|
|
77
|
-
makeSinkPushResponse(id: string, name: string, error?: string, sender?: string, receiver?: string, auth?: string[], meta?: Record<string, any
|
|
90
|
+
makeSinkPushResponse(id: string, name: string, error?: string, sender?: string, receiver?: string, auth?: string[], meta?: Record<string, any>, credit?: number): SinkPushResponse;
|
|
78
91
|
makeSinkPushChunk(id: string, name: string, chunk?: Uint8Array, error?: string, final?: boolean, sender?: string, receiver?: string): SinkPushChunk;
|
|
79
|
-
|
|
92
|
+
makeSinkPushCredit(id: string, name: string, credit: number, sender?: string, receiver?: string): SinkPushCredit;
|
|
93
|
+
makeSourceFetchRequest(id: string, name: string, params?: any[], sender?: string, receiver?: string, auth?: string[], meta?: Record<string, any>, credit?: number): SourceFetchRequest;
|
|
80
94
|
makeSourceFetchResponse(id: string, name: string, error?: string, sender?: string, receiver?: string, auth?: string[], meta?: Record<string, any>): SourceFetchResponse;
|
|
81
95
|
makeSourceFetchChunk(id: string, name: string, chunk?: Uint8Array, error?: string, final?: boolean, sender?: string, receiver?: string): SourceFetchChunk;
|
|
82
|
-
|
|
96
|
+
makeSourceFetchCredit(id: string, name: string, credit: number, sender?: string, receiver?: string): SourceFetchCredit;
|
|
97
|
+
parse(obj: any): EventEmission | ServiceCallRequest | ServiceCallResponse | SinkPushRequest | SinkPushResponse | SinkPushChunk | SinkPushCredit | SourceFetchRequest | SourceFetchResponse | SourceFetchChunk | SourceFetchCredit;
|
|
83
98
|
}
|
|
84
99
|
export declare class MsgTrait<T extends APISchema = APISchema> extends EncodeTrait<T> {
|
|
85
100
|
protected msg: Msg;
|
|
@@ -24,10 +24,11 @@
|
|
|
24
24
|
/* external requirements */
|
|
25
25
|
import * as v from "valibot";
|
|
26
26
|
import { EncodeTrait } from "./mqtt-plus-encode";
|
|
27
|
+
import { version, VERSION, versionToNum } from "./mqtt-plus-version";
|
|
27
28
|
/* meta validation schema (non-array plain object) */
|
|
28
29
|
const MetaSchema = v.pipe(v.record(v.string(), v.unknown()), v.check((data) => !Array.isArray(data)));
|
|
29
|
-
/* reusable
|
|
30
|
-
const
|
|
30
|
+
/* reusable auth validation schema (max 8 tokens, max 8192 chars each) */
|
|
31
|
+
const AuthSchema = v.pipe(v.array(v.pipe(v.string(), v.maxLength(8192))), v.maxLength(8));
|
|
31
32
|
/* base class */
|
|
32
33
|
class Base {
|
|
33
34
|
constructor(type, id, sender, receiver) {
|
|
@@ -35,9 +36,11 @@ class Base {
|
|
|
35
36
|
this.id = id;
|
|
36
37
|
this.sender = sender;
|
|
37
38
|
this.receiver = receiver;
|
|
39
|
+
this.version = `MQTT+/${VERSION}`;
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
const BaseSchema = {
|
|
43
|
+
version: v.pipe(v.string(), v.regex(/^MQTT\+\/\d+\.\d+$/)),
|
|
41
44
|
type: v.string(),
|
|
42
45
|
id: v.string(),
|
|
43
46
|
sender: v.optional(v.string()),
|
|
@@ -57,8 +60,8 @@ const EventEmissionSchema = v.strictObject({
|
|
|
57
60
|
...BaseSchema,
|
|
58
61
|
type: v.literal("event-emission"),
|
|
59
62
|
name: v.string(),
|
|
60
|
-
params: v.optional(v.array(v.unknown())),
|
|
61
|
-
auth: v.optional(
|
|
63
|
+
params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
|
|
64
|
+
auth: v.optional(AuthSchema),
|
|
62
65
|
meta: v.optional(MetaSchema)
|
|
63
66
|
});
|
|
64
67
|
/* service request */
|
|
@@ -75,8 +78,8 @@ const ServiceCallRequestSchema = v.strictObject({
|
|
|
75
78
|
...BaseSchema,
|
|
76
79
|
type: v.literal("service-call-request"),
|
|
77
80
|
name: v.string(),
|
|
78
|
-
params: v.optional(v.array(v.unknown())),
|
|
79
|
-
auth: v.optional(
|
|
81
|
+
params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
|
|
82
|
+
auth: v.optional(AuthSchema),
|
|
80
83
|
meta: v.optional(MetaSchema)
|
|
81
84
|
});
|
|
82
85
|
/* service response */
|
|
@@ -107,18 +110,19 @@ const SinkPushRequestSchema = v.strictObject({
|
|
|
107
110
|
...BaseSchema,
|
|
108
111
|
type: v.literal("sink-push-request"),
|
|
109
112
|
name: v.string(),
|
|
110
|
-
params: v.optional(v.array(v.unknown())),
|
|
111
|
-
auth: v.optional(
|
|
113
|
+
params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
|
|
114
|
+
auth: v.optional(AuthSchema),
|
|
112
115
|
meta: v.optional(MetaSchema)
|
|
113
116
|
});
|
|
114
117
|
/* sink push response (ack/nak) */
|
|
115
118
|
export class SinkPushResponse extends Base {
|
|
116
|
-
constructor(id, name, error, sender, receiver, auth, meta) {
|
|
119
|
+
constructor(id, name, error, sender, receiver, auth, meta, credit) {
|
|
117
120
|
super("sink-push-response", id, sender, receiver);
|
|
118
121
|
this.name = name;
|
|
119
122
|
this.error = error;
|
|
120
123
|
this.auth = auth;
|
|
121
124
|
this.meta = meta;
|
|
125
|
+
this.credit = credit;
|
|
122
126
|
}
|
|
123
127
|
}
|
|
124
128
|
const SinkPushResponseSchema = v.strictObject({
|
|
@@ -126,8 +130,9 @@ const SinkPushResponseSchema = v.strictObject({
|
|
|
126
130
|
type: v.literal("sink-push-response"),
|
|
127
131
|
name: v.string(),
|
|
128
132
|
error: v.optional(v.string()),
|
|
129
|
-
auth: v.optional(
|
|
130
|
-
meta: v.optional(MetaSchema)
|
|
133
|
+
auth: v.optional(AuthSchema),
|
|
134
|
+
meta: v.optional(MetaSchema),
|
|
135
|
+
credit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0)))
|
|
131
136
|
});
|
|
132
137
|
/* sink push chunk (actual data transfer) */
|
|
133
138
|
export class SinkPushChunk extends Base {
|
|
@@ -143,27 +148,43 @@ const SinkPushChunkSchema = v.strictObject({
|
|
|
143
148
|
...BaseSchema,
|
|
144
149
|
type: v.literal("sink-push-chunk"),
|
|
145
150
|
name: v.string(),
|
|
146
|
-
chunk: v.optional(
|
|
151
|
+
chunk: v.optional(v.instance(Uint8Array)),
|
|
147
152
|
error: v.optional(v.string()),
|
|
148
153
|
final: v.optional(v.boolean())
|
|
149
154
|
});
|
|
155
|
+
/* sink push credit (credit replenishment for push flow control) */
|
|
156
|
+
export class SinkPushCredit extends Base {
|
|
157
|
+
constructor(id, name, credit, sender, receiver) {
|
|
158
|
+
super("sink-push-credit", id, sender, receiver);
|
|
159
|
+
this.name = name;
|
|
160
|
+
this.credit = credit;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const SinkPushCreditSchema = v.strictObject({
|
|
164
|
+
...BaseSchema,
|
|
165
|
+
type: v.literal("sink-push-credit"),
|
|
166
|
+
name: v.string(),
|
|
167
|
+
credit: v.pipe(v.number(), v.integer(), v.minValue(1))
|
|
168
|
+
});
|
|
150
169
|
/* source fetch request */
|
|
151
170
|
export class SourceFetchRequest extends Base {
|
|
152
|
-
constructor(id, name, params, sender, receiver, auth, meta) {
|
|
171
|
+
constructor(id, name, params, sender, receiver, auth, meta, credit) {
|
|
153
172
|
super("source-fetch-request", id, sender, receiver);
|
|
154
173
|
this.name = name;
|
|
155
174
|
this.params = params;
|
|
156
175
|
this.auth = auth;
|
|
157
176
|
this.meta = meta;
|
|
177
|
+
this.credit = credit;
|
|
158
178
|
}
|
|
159
179
|
}
|
|
160
180
|
const SourceFetchRequestSchema = v.strictObject({
|
|
161
181
|
...BaseSchema,
|
|
162
182
|
type: v.literal("source-fetch-request"),
|
|
163
183
|
name: v.string(),
|
|
164
|
-
params: v.optional(v.array(v.unknown())),
|
|
165
|
-
auth: v.optional(
|
|
166
|
-
meta: v.optional(MetaSchema)
|
|
184
|
+
params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
|
|
185
|
+
auth: v.optional(AuthSchema),
|
|
186
|
+
meta: v.optional(MetaSchema),
|
|
187
|
+
credit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(0)))
|
|
167
188
|
});
|
|
168
189
|
/* source fetch response (ack/nak) */
|
|
169
190
|
export class SourceFetchResponse extends Base {
|
|
@@ -180,7 +201,7 @@ const SourceFetchResponseSchema = v.strictObject({
|
|
|
180
201
|
type: v.literal("source-fetch-response"),
|
|
181
202
|
name: v.string(),
|
|
182
203
|
error: v.optional(v.string()),
|
|
183
|
-
auth: v.optional(
|
|
204
|
+
auth: v.optional(AuthSchema),
|
|
184
205
|
meta: v.optional(MetaSchema)
|
|
185
206
|
});
|
|
186
207
|
/* source fetch chunk (actual data transfer) */
|
|
@@ -197,55 +218,73 @@ const SourceFetchChunkSchema = v.strictObject({
|
|
|
197
218
|
...BaseSchema,
|
|
198
219
|
type: v.literal("source-fetch-chunk"),
|
|
199
220
|
name: v.string(),
|
|
200
|
-
chunk: v.optional(
|
|
221
|
+
chunk: v.optional(v.instance(Uint8Array)),
|
|
201
222
|
error: v.optional(v.string()),
|
|
202
223
|
final: v.optional(v.boolean())
|
|
203
224
|
});
|
|
225
|
+
/* source fetch credit (credit replenishment for fetch flow control) */
|
|
226
|
+
export class SourceFetchCredit extends Base {
|
|
227
|
+
constructor(id, name, credit, sender, receiver) {
|
|
228
|
+
super("source-fetch-credit", id, sender, receiver);
|
|
229
|
+
this.name = name;
|
|
230
|
+
this.credit = credit;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const SourceFetchCreditSchema = v.strictObject({
|
|
234
|
+
...BaseSchema,
|
|
235
|
+
type: v.literal("source-fetch-credit"),
|
|
236
|
+
name: v.string(),
|
|
237
|
+
credit: v.pipe(v.number(), v.integer(), v.minValue(1))
|
|
238
|
+
});
|
|
204
239
|
/* utility class */
|
|
205
240
|
class Msg {
|
|
206
|
-
/*
|
|
241
|
+
/* factories for creating objects */
|
|
207
242
|
makeEventEmission(id, name, params, sender, receiver, auth, meta) {
|
|
208
243
|
return new EventEmission(id, name, params, sender, receiver, auth, meta);
|
|
209
244
|
}
|
|
210
|
-
/* factory for service request */
|
|
211
245
|
makeServiceCallRequest(id, name, params, sender, receiver, auth, meta) {
|
|
212
246
|
return new ServiceCallRequest(id, name, params, sender, receiver, auth, meta);
|
|
213
247
|
}
|
|
214
|
-
/* factory for service response */
|
|
215
248
|
makeServiceCallResponse(id, result, error, sender, receiver) {
|
|
216
249
|
return new ServiceCallResponse(id, result, error, sender, receiver);
|
|
217
250
|
}
|
|
218
|
-
/* factory for sink push request */
|
|
219
251
|
makeSinkPushRequest(id, name, params, sender, receiver, auth, meta) {
|
|
220
252
|
return new SinkPushRequest(id, name, params, sender, receiver, auth, meta);
|
|
221
253
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return new SinkPushResponse(id, name, error, sender, receiver, auth, meta);
|
|
254
|
+
makeSinkPushResponse(id, name, error, sender, receiver, auth, meta, credit) {
|
|
255
|
+
return new SinkPushResponse(id, name, error, sender, receiver, auth, meta, credit);
|
|
225
256
|
}
|
|
226
|
-
/* factory for sink push chunk */
|
|
227
257
|
makeSinkPushChunk(id, name, chunk, error, final, sender, receiver) {
|
|
228
258
|
return new SinkPushChunk(id, name, chunk, error, final, sender, receiver);
|
|
229
259
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
260
|
+
makeSinkPushCredit(id, name, credit, sender, receiver) {
|
|
261
|
+
return new SinkPushCredit(id, name, credit, sender, receiver);
|
|
262
|
+
}
|
|
263
|
+
makeSourceFetchRequest(id, name, params, sender, receiver, auth, meta, credit) {
|
|
264
|
+
return new SourceFetchRequest(id, name, params, sender, receiver, auth, meta, credit);
|
|
233
265
|
}
|
|
234
|
-
/* factory for source fetch response */
|
|
235
266
|
makeSourceFetchResponse(id, name, error, sender, receiver, auth, meta) {
|
|
236
267
|
return new SourceFetchResponse(id, name, error, sender, receiver, auth, meta);
|
|
237
268
|
}
|
|
238
|
-
/* factory for source fetch chunk */
|
|
239
269
|
makeSourceFetchChunk(id, name, chunk, error, final, sender, receiver) {
|
|
240
270
|
return new SourceFetchChunk(id, name, chunk, error, final, sender, receiver);
|
|
241
271
|
}
|
|
272
|
+
makeSourceFetchCredit(id, name, credit, sender, receiver) {
|
|
273
|
+
return new SourceFetchCredit(id, name, credit, sender, receiver);
|
|
274
|
+
}
|
|
242
275
|
/* parse any object into typed object */
|
|
243
276
|
parse(obj) {
|
|
277
|
+
/* sanity check input */
|
|
244
278
|
if (typeof obj !== "object" || obj === null)
|
|
245
279
|
throw new Error("invalid argument: not an object");
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
280
|
+
/* sanity check version */
|
|
281
|
+
if (typeof obj.version !== "string")
|
|
282
|
+
throw new Error("invalid object: missing or invalid \"version\" field");
|
|
283
|
+
const m = obj.version.match(/^MQTT\+\/(\d+\.\d+)$/);
|
|
284
|
+
const V = m !== null ? versionToNum(m[1]) : 0.0;
|
|
285
|
+
if (V !== version)
|
|
286
|
+
throw new Error(`protocol version mismatch (expected version ${VERSION}, got version ${obj.version})`);
|
|
287
|
+
/* helper function for Valibot-based validation */
|
|
249
288
|
const parseObject = (obj, name, schema) => {
|
|
250
289
|
const res = v.safeParse(schema, obj);
|
|
251
290
|
if (!res.success) {
|
|
@@ -254,6 +293,9 @@ class Msg {
|
|
|
254
293
|
}
|
|
255
294
|
return res.output;
|
|
256
295
|
};
|
|
296
|
+
/* dispatch according to type indication by field */
|
|
297
|
+
if (typeof obj.type !== "string")
|
|
298
|
+
throw new Error("invalid object: missing or invalid \"type\" field");
|
|
257
299
|
if (obj.type === "event-emission") {
|
|
258
300
|
const out = parseObject(obj, "EventEmission", EventEmissionSchema);
|
|
259
301
|
return this.makeEventEmission(out.id, out.name, out.params, out.sender, out.receiver, out.auth, out.meta);
|
|
@@ -272,15 +314,19 @@ class Msg {
|
|
|
272
314
|
}
|
|
273
315
|
else if (obj.type === "sink-push-response") {
|
|
274
316
|
const out = parseObject(obj, "SinkPushResponse", SinkPushResponseSchema);
|
|
275
|
-
return this.makeSinkPushResponse(out.id, out.name, out.error, out.sender, out.receiver, out.auth, out.meta);
|
|
317
|
+
return this.makeSinkPushResponse(out.id, out.name, out.error, out.sender, out.receiver, out.auth, out.meta, out.credit);
|
|
276
318
|
}
|
|
277
319
|
else if (obj.type === "sink-push-chunk") {
|
|
278
320
|
const out = parseObject(obj, "SinkPushChunk", SinkPushChunkSchema);
|
|
279
321
|
return this.makeSinkPushChunk(out.id, out.name, out.chunk, out.error, out.final, out.sender, out.receiver);
|
|
280
322
|
}
|
|
323
|
+
else if (obj.type === "sink-push-credit") {
|
|
324
|
+
const out = parseObject(obj, "SinkPushCredit", SinkPushCreditSchema);
|
|
325
|
+
return this.makeSinkPushCredit(out.id, out.name, out.credit, out.sender, out.receiver);
|
|
326
|
+
}
|
|
281
327
|
else if (obj.type === "source-fetch-request") {
|
|
282
328
|
const out = parseObject(obj, "SourceFetchRequest", SourceFetchRequestSchema);
|
|
283
|
-
return this.makeSourceFetchRequest(out.id, out.name, out.params, out.sender, out.receiver, out.auth, out.meta);
|
|
329
|
+
return this.makeSourceFetchRequest(out.id, out.name, out.params, out.sender, out.receiver, out.auth, out.meta, out.credit);
|
|
284
330
|
}
|
|
285
331
|
else if (obj.type === "source-fetch-response") {
|
|
286
332
|
const out = parseObject(obj, "SourceFetchResponse", SourceFetchResponseSchema);
|
|
@@ -290,6 +336,10 @@ class Msg {
|
|
|
290
336
|
const out = parseObject(obj, "SourceFetchChunk", SourceFetchChunkSchema);
|
|
291
337
|
return this.makeSourceFetchChunk(out.id, out.name, out.chunk, out.error, out.final, out.sender, out.receiver);
|
|
292
338
|
}
|
|
339
|
+
else if (obj.type === "source-fetch-credit") {
|
|
340
|
+
const out = parseObject(obj, "SourceFetchCredit", SourceFetchCreditSchema);
|
|
341
|
+
return this.makeSourceFetchCredit(out.id, out.name, out.credit, out.sender, out.receiver);
|
|
342
|
+
}
|
|
293
343
|
else
|
|
294
344
|
throw new Error("invalid object: not of any known type");
|
|
295
345
|
}
|
|
@@ -23,7 +23,5 @@ export declare class ServiceTrait<T extends APISchema = APISchema> extends Event
|
|
|
23
23
|
options?: IClientPublishOptions;
|
|
24
24
|
meta?: Record<string, any>;
|
|
25
25
|
}): Promise<ReturnType<T[K]>>;
|
|
26
|
-
private callSubscribe;
|
|
27
|
-
private callUnsubscribe;
|
|
28
26
|
protected _dispatchMessage(topic: string, parsed: any): Promise<void>;
|
|
29
27
|
}
|