mqtt-plus 1.4.13 → 1.4.15

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.
Files changed (92) hide show
  1. package/AGENTS.md +0 -1
  2. package/CHANGELOG.md +18 -0
  3. package/dst-stage1/mqtt-plus-api.js +1 -0
  4. package/dst-stage1/mqtt-plus-api.js.map +1 -0
  5. package/dst-stage1/mqtt-plus-auth.js +1 -0
  6. package/dst-stage1/mqtt-plus-auth.js.map +1 -0
  7. package/dst-stage1/mqtt-plus-base.js +1 -0
  8. package/dst-stage1/mqtt-plus-base.js.map +1 -0
  9. package/dst-stage1/mqtt-plus-codec.js +1 -0
  10. package/dst-stage1/mqtt-plus-codec.js.map +1 -0
  11. package/dst-stage1/mqtt-plus-encode.js +1 -0
  12. package/dst-stage1/mqtt-plus-encode.js.map +1 -0
  13. package/dst-stage1/mqtt-plus-error.js +1 -0
  14. package/dst-stage1/mqtt-plus-error.js.map +1 -0
  15. package/dst-stage1/mqtt-plus-event.js +1 -0
  16. package/dst-stage1/mqtt-plus-event.js.map +1 -0
  17. package/dst-stage1/mqtt-plus-info.js +1 -0
  18. package/dst-stage1/mqtt-plus-info.js.map +1 -0
  19. package/dst-stage1/mqtt-plus-meta.js +1 -0
  20. package/dst-stage1/mqtt-plus-meta.js.map +1 -0
  21. package/dst-stage1/mqtt-plus-msg.js +1 -0
  22. package/dst-stage1/mqtt-plus-msg.js.map +1 -0
  23. package/dst-stage1/mqtt-plus-options.js +1 -0
  24. package/dst-stage1/mqtt-plus-options.js.map +1 -0
  25. package/dst-stage1/mqtt-plus-service.js +1 -0
  26. package/dst-stage1/mqtt-plus-service.js.map +1 -0
  27. package/dst-stage1/mqtt-plus-sink.js +14 -2
  28. package/dst-stage1/mqtt-plus-sink.js.map +1 -0
  29. package/dst-stage1/mqtt-plus-source.js +27 -16
  30. package/dst-stage1/mqtt-plus-source.js.map +1 -0
  31. package/dst-stage1/mqtt-plus-subscription.js +1 -0
  32. package/dst-stage1/mqtt-plus-subscription.js.map +1 -0
  33. package/dst-stage1/mqtt-plus-timer.js +1 -0
  34. package/dst-stage1/mqtt-plus-timer.js.map +1 -0
  35. package/dst-stage1/mqtt-plus-trace.js +1 -0
  36. package/dst-stage1/mqtt-plus-trace.js.map +1 -0
  37. package/dst-stage1/mqtt-plus-util.js +1 -0
  38. package/dst-stage1/mqtt-plus-util.js.map +1 -0
  39. package/dst-stage1/mqtt-plus-version.d.ts +1 -1
  40. package/dst-stage1/mqtt-plus-version.js +3 -1
  41. package/dst-stage1/mqtt-plus-version.js.map +1 -0
  42. package/dst-stage1/mqtt-plus.js +1 -0
  43. package/dst-stage1/mqtt-plus.js.map +1 -0
  44. package/dst-stage2/mqtt-plus.cjs.js +1972 -2161
  45. package/dst-stage2/mqtt-plus.esm.js +1934 -2131
  46. package/dst-stage2/mqtt-plus.umd.js +13 -14
  47. package/etc/c8.json +16 -0
  48. package/etc/knip.jsonc +7 -1
  49. package/etc/stx.conf +36 -4
  50. package/etc/tsc.json +1 -1
  51. package/etc/vite.mts +11 -5
  52. package/package.d/vite+8.0.0.patch +12 -0
  53. package/package.json +12 -3
  54. package/src/mqtt-plus-sink.ts +14 -2
  55. package/src/mqtt-plus-source.ts +27 -16
  56. package/src/mqtt-plus-version.ts +2 -3
  57. package/tst/.c8/base.css +224 -0
  58. package/tst/.c8/block-navigation.js +87 -0
  59. package/tst/.c8/favicon.png +0 -0
  60. package/tst/.c8/index.html +371 -0
  61. package/tst/.c8/mqtt-plus-auth.ts.html +538 -0
  62. package/tst/.c8/mqtt-plus-base.ts.html +826 -0
  63. package/tst/.c8/mqtt-plus-codec.ts.html +457 -0
  64. package/tst/.c8/mqtt-plus-encode.ts.html +310 -0
  65. package/tst/.c8/mqtt-plus-error.ts.html +1186 -0
  66. package/tst/.c8/mqtt-plus-event.ts.html +733 -0
  67. package/tst/.c8/mqtt-plus-meta.ts.html +271 -0
  68. package/tst/.c8/mqtt-plus-msg.ts.html +1693 -0
  69. package/tst/.c8/mqtt-plus-options.ts.html +319 -0
  70. package/tst/.c8/mqtt-plus-service.ts.html +865 -0
  71. package/tst/.c8/mqtt-plus-sink.ts.html +1645 -0
  72. package/tst/.c8/mqtt-plus-source.ts.html +1585 -0
  73. package/tst/.c8/mqtt-plus-subscription.ts.html +706 -0
  74. package/tst/.c8/mqtt-plus-timer.ts.html +286 -0
  75. package/tst/.c8/mqtt-plus-trace.ts.html +463 -0
  76. package/tst/.c8/mqtt-plus-util.ts.html +823 -0
  77. package/tst/.c8/mqtt-plus-version.ts.html +205 -0
  78. package/tst/.c8/mqtt-plus.ts.html +193 -0
  79. package/tst/.c8/prettify.css +1 -0
  80. package/tst/.c8/prettify.js +2 -0
  81. package/tst/.c8/sort-arrow-sprite.png +0 -0
  82. package/tst/.c8/sorter.js +210 -0
  83. package/tst/.c8/tmp/coverage-6577-1773528098323-2.json +1 -0
  84. package/tst/.c8/tmp/coverage-6577-1773528098331-1.json +1 -0
  85. package/tst/.c8/tmp/coverage-6577-1773528098353-0.json +1 -0
  86. package/tst/.c8/tmp/coverage-6578-1773528089194-0.json +1 -0
  87. package/tst/mqtt-plus-2-event.spec.ts +29 -0
  88. package/tst/mqtt-plus-6-misc.spec.ts +79 -2
  89. package/tst/mqtt-plus-7-spool.spec.ts +101 -0
  90. package/tst/mqtt-plus-8-run.spec.ts +115 -0
  91. package/tst/{tsc.json → tsc.cov.json} +4 -3
  92. package/tst/tsc.std.json +31 -0
@@ -9,2168 +9,1971 @@ import * as sha256 from "@stablelib/sha256";
9
9
  import { EventEmitter } from "node:events";
10
10
  import * as v from "valibot";
11
11
  import * as CBOR from "cbor2";
12
- const PLazy = PLazyAPI.default ?? PLazyAPI;
13
- class CreditGate {
14
- /* initialization */
15
- constructor(initialCredit) {
16
- this.waiters = [];
17
- this.aborted = false;
18
- this.remaining = initialCredit;
19
- }
20
- /* acquire one unit of credit (wait if exhausted) */
21
- async acquire(abortSignal) {
22
- if (this.aborted)
23
- throw new Error("credit gate aborted");
24
- if (abortSignal?.aborted)
25
- throw abortSignal.reason ?? new Error("aborted");
26
- if (this.remaining > 0)
27
- this.remaining--;
28
- else
29
- await new Promise((resolve, reject) => {
30
- const onAbort = () => {
31
- const idx = this.waiters.indexOf(waiter);
32
- if (idx !== -1)
33
- this.waiters.splice(idx, 1);
34
- reject(abortSignal?.reason ?? new Error("aborted"));
35
- };
36
- if (abortSignal)
37
- abortSignal.addEventListener("abort", onAbort, { once: true });
38
- const waiter = (aborted) => {
39
- if (abortSignal)
40
- abortSignal.removeEventListener("abort", onAbort);
41
- if (aborted) {
42
- reject(new Error("credit gate aborted"));
43
- return;
44
- }
45
- this.remaining--;
46
- resolve();
47
- };
48
- this.waiters.push(waiter);
49
- });
50
- }
51
- /* replenish credit (called when credit message received) */
52
- replenish(amount) {
53
- this.remaining += amount;
54
- while (this.waiters.length > 0 && this.remaining > 0)
55
- this.waiters.shift()(false);
56
- }
57
- /* release any waiting producer (for cleanup on error/abort) */
58
- abort() {
59
- this.aborted = true;
60
- while (this.waiters.length > 0)
61
- this.waiters.shift()(true);
62
- }
63
- }
64
- const textEncoder$1 = new TextEncoder();
12
+ //#region dst-stage1/mqtt-plus-util.js
13
+ var PLazy = PLazyAPI.default ?? PLazyAPI;
14
+ var CreditGate = class {
15
+ constructor(initialCredit) {
16
+ this.waiters = [];
17
+ this.aborted = false;
18
+ this.remaining = initialCredit;
19
+ }
20
+ async acquire(abortSignal) {
21
+ if (this.aborted) throw new Error("credit gate aborted");
22
+ if (abortSignal?.aborted) throw abortSignal.reason ?? /* @__PURE__ */ new Error("aborted");
23
+ if (this.remaining > 0) this.remaining--;
24
+ else await new Promise((resolve, reject) => {
25
+ const onAbort = () => {
26
+ const idx = this.waiters.indexOf(waiter);
27
+ if (idx !== -1) this.waiters.splice(idx, 1);
28
+ reject(abortSignal?.reason ?? /* @__PURE__ */ new Error("aborted"));
29
+ };
30
+ if (abortSignal) abortSignal.addEventListener("abort", onAbort, { once: true });
31
+ const waiter = (aborted) => {
32
+ if (abortSignal) abortSignal.removeEventListener("abort", onAbort);
33
+ if (aborted) {
34
+ reject(/* @__PURE__ */ new Error("credit gate aborted"));
35
+ return;
36
+ }
37
+ this.remaining--;
38
+ resolve();
39
+ };
40
+ this.waiters.push(waiter);
41
+ });
42
+ }
43
+ replenish(amount) {
44
+ this.remaining += amount;
45
+ while (this.waiters.length > 0 && this.remaining > 0) this.waiters.shift()(false);
46
+ }
47
+ abort() {
48
+ this.aborted = true;
49
+ while (this.waiters.length > 0) this.waiters.shift()(true);
50
+ }
51
+ };
52
+ var textEncoder$1 = new TextEncoder();
65
53
  function uint8ArrayConcat(arrays) {
66
- const totalLength = arrays.reduce((acc, value) => acc + value.byteLength, 0);
67
- const result = new Uint8Array(totalLength);
68
- let offset = 0;
69
- for (const a of arrays) {
70
- result.set(a, offset);
71
- offset += a.byteLength;
72
- }
73
- return result;
54
+ const totalLength = arrays.reduce((acc, value) => acc + value.byteLength, 0);
55
+ const result = new Uint8Array(totalLength);
56
+ let offset = 0;
57
+ for (const a of arrays) {
58
+ result.set(a, offset);
59
+ offset += a.byteLength;
60
+ }
61
+ return result;
74
62
  }
75
63
  function chunkToBuffer(chunk) {
76
- let buffer;
77
- if (chunk instanceof Buffer$1)
78
- buffer = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
79
- else if (chunk instanceof Uint8Array)
80
- buffer = chunk;
81
- else if (typeof chunk === "string")
82
- buffer = textEncoder$1.encode(chunk);
83
- else
84
- throw new Error("invalid chunk type: expected Buffer, Uint8Array, or string");
85
- return buffer;
64
+ let buffer;
65
+ if (chunk instanceof Buffer$1) buffer = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
66
+ else if (chunk instanceof Uint8Array) buffer = chunk;
67
+ else if (typeof chunk === "string") buffer = textEncoder$1.encode(chunk);
68
+ else throw new Error("invalid chunk type: expected Buffer, Uint8Array, or string");
69
+ return buffer;
86
70
  }
87
71
  function streamToBuffer(stream) {
88
- return new PLazy((resolve, reject) => {
89
- const chunks = [];
90
- stream.on("data", (raw) => {
91
- const data = chunkToBuffer(raw);
92
- chunks.push(data);
93
- });
94
- stream.on("end", () => {
95
- resolve(uint8ArrayConcat(chunks));
96
- });
97
- stream.on("error", (err) => {
98
- reject(err);
99
- });
100
- });
72
+ return new PLazy((resolve, reject) => {
73
+ const chunks = [];
74
+ stream.on("data", (raw) => {
75
+ const data = chunkToBuffer(raw);
76
+ chunks.push(data);
77
+ });
78
+ stream.on("end", () => {
79
+ resolve(uint8ArrayConcat(chunks));
80
+ });
81
+ stream.on("error", (err) => {
82
+ reject(err);
83
+ });
84
+ });
101
85
  }
102
86
  async function sendBufferAsChunks(buffer, chunkSize, sendChunk, creditGate, abortSignal) {
103
- if (buffer.byteLength === 0)
104
- await sendChunk(void 0, void 0, true);
105
- else {
106
- for (let i = 0; i < buffer.byteLength; i += chunkSize) {
107
- if (abortSignal?.aborted)
108
- throw abortSignal.reason ?? new Error("aborted");
109
- const size = Math.min(buffer.byteLength - i, chunkSize);
110
- const chunk = buffer.subarray(i, i + size);
111
- const final = i + size >= buffer.byteLength;
112
- if (creditGate)
113
- await creditGate.acquire(abortSignal);
114
- await sendChunk(chunk, void 0, final);
115
- }
116
- }
87
+ if (buffer.byteLength === 0) await sendChunk(void 0, void 0, true);
88
+ else for (let i = 0; i < buffer.byteLength; i += chunkSize) {
89
+ if (abortSignal?.aborted) throw abortSignal.reason ?? /* @__PURE__ */ new Error("aborted");
90
+ const size = Math.min(buffer.byteLength - i, chunkSize);
91
+ const chunk = buffer.subarray(i, i + size);
92
+ const final = i + size >= buffer.byteLength;
93
+ if (creditGate) await creditGate.acquire(abortSignal);
94
+ await sendChunk(chunk, void 0, final);
95
+ }
117
96
  }
118
97
  async function sendStreamAsChunks(readable, chunkSize, sendChunk, creditGate, abortSignal) {
119
- let pending;
120
- for await (const raw of readable) {
121
- if (abortSignal?.aborted)
122
- throw abortSignal.reason ?? new Error("aborted");
123
- const buffer = chunkToBuffer(raw);
124
- if (buffer.byteLength === 0)
125
- continue;
126
- for (let i = 0; i < buffer.byteLength; i += chunkSize) {
127
- if (abortSignal?.aborted)
128
- throw abortSignal.reason ?? new Error("aborted");
129
- const size = Math.min(buffer.byteLength - i, chunkSize);
130
- const chunk = buffer.subarray(i, i + size);
131
- if (pending !== void 0) {
132
- if (creditGate)
133
- await creditGate.acquire(abortSignal);
134
- await sendChunk(pending, void 0, false);
135
- }
136
- pending = chunk;
137
- }
138
- }
139
- if (abortSignal?.aborted)
140
- throw abortSignal.reason ?? new Error("aborted");
141
- if (pending !== void 0) {
142
- if (creditGate)
143
- await creditGate.acquire(abortSignal);
144
- await sendChunk(pending, void 0, true);
145
- } else
146
- await sendChunk(void 0, void 0, true);
98
+ let pending;
99
+ for await (const raw of readable) {
100
+ if (abortSignal?.aborted) throw abortSignal.reason ?? /* @__PURE__ */ new Error("aborted");
101
+ const buffer = chunkToBuffer(raw);
102
+ if (buffer.byteLength === 0) continue;
103
+ for (let i = 0; i < buffer.byteLength; i += chunkSize) {
104
+ if (abortSignal?.aborted) throw abortSignal.reason ?? /* @__PURE__ */ new Error("aborted");
105
+ const size = Math.min(buffer.byteLength - i, chunkSize);
106
+ const chunk = buffer.subarray(i, i + size);
107
+ if (pending !== void 0) {
108
+ if (creditGate) await creditGate.acquire(abortSignal);
109
+ await sendChunk(pending, void 0, false);
110
+ }
111
+ pending = chunk;
112
+ }
113
+ }
114
+ if (abortSignal?.aborted) throw abortSignal.reason ?? /* @__PURE__ */ new Error("aborted");
115
+ if (pending !== void 0) {
116
+ if (creditGate) await creditGate.acquire(abortSignal);
117
+ await sendChunk(pending, void 0, true);
118
+ } else await sendChunk(void 0, void 0, true);
147
119
  }
148
120
  function makeMutuallyExclusiveFields(obj, f1Name, f2Name) {
149
- if (!(typeof obj === "object" && obj !== null))
150
- throw new Error("invalid object");
151
- let consumed;
152
- const f1Value = obj[f1Name];
153
- const f2Value = obj[f2Name];
154
- Object.defineProperty(obj, f1Name, {
155
- get: () => {
156
- if (consumed === "f2")
157
- throw new Error(`field "${f1Name}" is mutually exclusive with field "${f2Name}" and "${f2Name}" was already consumed`);
158
- consumed = "f1";
159
- return f1Value;
160
- },
161
- enumerable: true,
162
- configurable: true
163
- });
164
- Object.defineProperty(obj, f2Name, {
165
- get: () => {
166
- if (consumed === "f1")
167
- throw new Error(`field "${f2Name}" is mutually exclusive with field "${f1Name}" and "${f1Name}" was already consumed`);
168
- consumed = "f2";
169
- return f2Value;
170
- },
171
- enumerable: true,
172
- configurable: true
173
- });
174
- }
175
- class Spool {
176
- constructor() {
177
- this.resources = [];
178
- }
179
- roll(...args) {
180
- let resource;
181
- let cleanup;
182
- if (args.length === 1) {
183
- resource = void 0;
184
- cleanup = args[0];
185
- } else if (args.length === 2) {
186
- resource = args[0];
187
- cleanup = args[1];
188
- } else
189
- throw new Error("invalid number of arguments");
190
- this.resources.push({ resource, cleanup });
191
- }
192
- /* roll a sub-spool onto spool */
193
- sub() {
194
- const spool = new Spool();
195
- this.roll(spool, () => {
196
- });
197
- return spool;
198
- }
199
- /* unroll all cleanup procedures from spool */
200
- unroll(suppress = true) {
201
- try {
202
- let promise;
203
- while (this.resources.length > 0) {
204
- const entry = this.resources.pop();
205
- const resource = entry.resource;
206
- const cleanup = entry.cleanup;
207
- if (promise) {
208
- if (resource instanceof Spool)
209
- promise = promise.then(
210
- () => resource.unroll()
211
- /* RECURSION */
212
- );
213
- else
214
- promise = promise.then(() => cleanup(resource));
215
- } else {
216
- let result;
217
- if (resource instanceof Spool)
218
- result = resource.unroll();
219
- else
220
- result = cleanup(resource);
221
- if (result instanceof Promise)
222
- promise = result;
223
- }
224
- }
225
- if (promise)
226
- return suppress ? promise.catch(() => {
227
- }) : promise;
228
- else
229
- return;
230
- } catch (error) {
231
- if (suppress)
232
- return;
233
- throw error;
234
- }
235
- }
121
+ if (!(typeof obj === "object" && obj !== null)) throw new Error("invalid object");
122
+ let consumed;
123
+ const f1Value = obj[f1Name];
124
+ const f2Value = obj[f2Name];
125
+ Object.defineProperty(obj, f1Name, {
126
+ get: () => {
127
+ if (consumed === "f2") throw new Error(`field "${f1Name}" is mutually exclusive with field "${f2Name}" and "${f2Name}" was already consumed`);
128
+ consumed = "f1";
129
+ return f1Value;
130
+ },
131
+ enumerable: true,
132
+ configurable: true
133
+ });
134
+ Object.defineProperty(obj, f2Name, {
135
+ get: () => {
136
+ if (consumed === "f1") throw new Error(`field "${f2Name}" is mutually exclusive with field "${f1Name}" and "${f1Name}" was already consumed`);
137
+ consumed = "f2";
138
+ return f2Value;
139
+ },
140
+ enumerable: true,
141
+ configurable: true
142
+ });
236
143
  }
144
+ //#endregion
145
+ //#region dst-stage1/mqtt-plus-error.js
146
+ var Spool = class Spool {
147
+ constructor() {
148
+ this.resources = [];
149
+ }
150
+ roll(...args) {
151
+ let resource;
152
+ let cleanup;
153
+ if (args.length === 1) {
154
+ resource = void 0;
155
+ cleanup = args[0];
156
+ } else if (args.length === 2) {
157
+ resource = args[0];
158
+ cleanup = args[1];
159
+ } else throw new Error("invalid number of arguments");
160
+ this.resources.push({
161
+ resource,
162
+ cleanup
163
+ });
164
+ }
165
+ sub() {
166
+ const spool = new Spool();
167
+ this.roll(spool, () => {});
168
+ return spool;
169
+ }
170
+ unroll(suppress = true) {
171
+ try {
172
+ let promise;
173
+ while (this.resources.length > 0) {
174
+ const entry = this.resources.pop();
175
+ const resource = entry.resource;
176
+ const cleanup = entry.cleanup;
177
+ if (promise) if (resource instanceof Spool) promise = promise.then(() => resource.unroll());
178
+ else promise = promise.then(() => cleanup(resource));
179
+ else {
180
+ let result;
181
+ if (resource instanceof Spool) result = resource.unroll();
182
+ else result = cleanup(resource);
183
+ if (result instanceof Promise) promise = result;
184
+ }
185
+ }
186
+ if (promise) return suppress ? promise.catch(() => {}) : promise;
187
+ else return;
188
+ } catch (error) {
189
+ if (suppress) return;
190
+ throw error;
191
+ }
192
+ }
193
+ };
237
194
  function ensureError(error, prefix, debug = false) {
238
- if (error instanceof Error && prefix === void 0 && debug === false)
239
- return error;
240
- let msg = error instanceof Error ? error.message : String(error);
241
- if (prefix)
242
- msg = `${prefix}: ${msg}`;
243
- if (debug && error instanceof Error)
244
- msg = `${msg}
245
- ${error.stack}`;
246
- if (error instanceof Error) {
247
- const err = new Error(msg, { cause: error });
248
- err.stack = error.stack;
249
- return err;
250
- } else
251
- return new Error(msg);
195
+ if (error instanceof Error && prefix === void 0 && debug === false) return error;
196
+ let msg = error instanceof Error ? error.message : String(error);
197
+ if (prefix) msg = `${prefix}: ${msg}`;
198
+ if (debug && error instanceof Error) msg = `${msg}\n${error.stack}`;
199
+ if (error instanceof Error) {
200
+ const err = new Error(msg, { cause: error });
201
+ err.stack = error.stack;
202
+ return err;
203
+ } else return new Error(msg);
252
204
  }
253
205
  function runFinally(isAsync, onfinally) {
254
- if (!onfinally) {
255
- if (isAsync)
256
- return Promise.resolve();
257
- else
258
- return;
259
- }
260
- let result;
261
- try {
262
- result = onfinally();
263
- } catch (error) {
264
- if (isAsync)
265
- return Promise.reject(error);
266
- else
267
- throw error;
268
- }
269
- if (!isAsync && result instanceof Promise)
270
- throw new Error("onfinally callback returned Promise in non-async context");
271
- if (isAsync && !(result instanceof Promise))
272
- result = Promise.resolve(result);
273
- return result;
206
+ if (!onfinally) if (isAsync) return Promise.resolve();
207
+ else return;
208
+ let result;
209
+ try {
210
+ result = onfinally();
211
+ } catch (error) {
212
+ if (isAsync) return Promise.reject(error);
213
+ else throw error;
214
+ }
215
+ if (!isAsync && result instanceof Promise) throw new Error("onfinally callback returned Promise in non-async context");
216
+ if (isAsync && !(result instanceof Promise)) result = Promise.resolve(result);
217
+ return result;
274
218
  }
275
219
  function runUnroll(isAsync, spool) {
276
- if (!spool) {
277
- if (isAsync)
278
- return Promise.resolve();
279
- else
280
- return;
281
- }
282
- let result = spool.unroll();
283
- if (!isAsync && result instanceof Promise)
284
- throw new Error("spool unroll returned Promise in non-async context");
285
- if (isAsync && !(result instanceof Promise))
286
- result = Promise.resolve(result);
287
- return result;
220
+ if (!spool) if (isAsync) return Promise.resolve();
221
+ else return;
222
+ let result = spool.unroll();
223
+ if (!isAsync && result instanceof Promise) throw new Error("spool unroll returned Promise in non-async context");
224
+ if (isAsync && !(result instanceof Promise)) result = Promise.resolve(result);
225
+ return result;
288
226
  }
289
227
  function run(...args) {
290
- let description;
291
- let spool;
292
- let action;
293
- let oncatch;
294
- let onfinally;
295
- let oncleanup;
296
- if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) {
297
- description = args[0].description;
298
- spool = args[0].spool;
299
- action = args[0].action;
300
- oncatch = args[0].oncatch;
301
- onfinally = args[0].onfinally;
302
- oncleanup = args[0].oncleanup;
303
- } else if (typeof args[0] === "string") {
304
- description = args[0];
305
- if (args[1] instanceof Spool || args[1] === void 0 && typeof args[2] === "function") {
306
- spool = args[1];
307
- action = args[2];
308
- oncatch = args[3];
309
- onfinally = args[4];
310
- oncleanup = args[5];
311
- } else {
312
- action = args[1];
313
- oncatch = args[2];
314
- onfinally = args[3];
315
- oncleanup = args[4];
316
- }
317
- } else {
318
- if (args[0] instanceof Spool || args[0] === void 0 && typeof args[1] === "function") {
319
- spool = args[0];
320
- action = args[1];
321
- oncatch = args[2];
322
- onfinally = args[3];
323
- oncleanup = args[4];
324
- } else {
325
- action = args[0];
326
- oncatch = args[1];
327
- onfinally = args[2];
328
- oncleanup = args[3];
329
- }
330
- }
331
- if (oncleanup && !spool)
332
- throw new Error("oncleanup requires a spool");
333
- let result;
334
- try {
335
- result = action();
336
- } catch (arg) {
337
- let error = ensureError(arg, description);
338
- if (oncatch) {
339
- try {
340
- result = oncatch(error);
341
- } catch (arg2) {
342
- error = ensureError(arg2, description);
343
- runFinally(false, onfinally);
344
- runUnroll(false, spool);
345
- throw error;
346
- }
347
- runFinally(false, onfinally);
348
- return result;
349
- }
350
- runFinally(false, onfinally);
351
- runUnroll(false, spool);
352
- throw error;
353
- }
354
- if (result instanceof Promise) {
355
- return result.then(async (result2) => {
356
- await runFinally(true, onfinally);
357
- if (spool && oncleanup)
358
- spool.roll(result2, oncleanup);
359
- return result2;
360
- }, async (arg) => {
361
- let error = ensureError(arg, description);
362
- if (oncatch) {
363
- try {
364
- const result2 = oncatch(error);
365
- await runFinally(true, onfinally);
366
- return result2;
367
- } catch (arg2) {
368
- error = ensureError(arg2, description);
369
- await runFinally(true, onfinally);
370
- await runUnroll(true, spool);
371
- throw error;
372
- }
373
- }
374
- await runFinally(true, onfinally);
375
- await runUnroll(true, spool);
376
- throw error;
377
- });
378
- } else {
379
- runFinally(false, onfinally);
380
- if (spool && oncleanup)
381
- spool.roll(result, oncleanup);
382
- return result;
383
- }
384
- }
385
- class OptionsTrait {
386
- /* construct API class */
387
- constructor(options = {}) {
388
- this.options = {
389
- id: nanoid(),
390
- codec: "cbor",
391
- timeout: 10 * 1e3,
392
- share: "",
393
- chunkSize: 16 * 1024,
394
- chunkCredit: 4,
395
- topicMake: (name, operation, peerId) => {
396
- return `${name}/${operation}/${peerId ?? "any"}`;
397
- },
398
- topicMatch: (topic) => {
399
- const m = topic.match(/^(.+)\/([^/]+)\/([^/]+)$/);
400
- return m ? {
401
- name: m[1],
402
- operation: m[2],
403
- peerId: m[3] === "any" ? void 0 : m[3]
404
- } : null;
405
- },
406
- ...options
407
- };
408
- }
409
- }
410
- class JSONX {
411
- static uint8ArrayToBase64(arr) {
412
- return Buffer$1.from(arr.buffer, arr.byteOffset, arr.byteLength).toString("base64");
413
- }
414
- static base64ToUint8Array(base64) {
415
- return new Uint8Array(Buffer$1.from(base64, "base64"));
416
- }
417
- static stringify(obj) {
418
- return JSON.stringify(obj, (_, value) => value instanceof Uint8Array ? { __Uint8Array: this.uint8ArrayToBase64(value) } : value);
419
- }
420
- static parse(json) {
421
- return JSON.parse(json, (_, value) => typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
422
- }
423
- }
424
- class Codec {
425
- constructor(format) {
426
- this.format = format;
427
- this.types = new CBOR.TypeEncoderMap();
428
- this.tags = /* @__PURE__ */ new Map();
429
- const TAG_BUFFER = 64e3;
430
- this.types.registerEncoder(Buffer$1, (buffer) => {
431
- return [TAG_BUFFER, new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)];
432
- });
433
- this.tags.set(TAG_BUFFER, (tag) => {
434
- return Buffer$1.from(tag.contents);
435
- });
436
- }
437
- encode(data) {
438
- let result;
439
- if (this.format === "cbor") {
440
- try {
441
- result = CBOR.encode(data, { types: this.types });
442
- } catch (err) {
443
- throw new Error("failed to encode CBOR format", { cause: err });
444
- }
445
- } else if (this.format === "json") {
446
- try {
447
- result = JSONX.stringify(data);
448
- } catch (err) {
449
- throw new Error("failed to encode JSON format", { cause: err });
450
- }
451
- } else
452
- throw new Error(`invalid format "${this.format}"`);
453
- return result;
454
- }
455
- decode(data) {
456
- let result;
457
- if (this.format === "cbor") {
458
- if (!(data instanceof Uint8Array))
459
- throw new Error("failed to decode CBOR format (data type is not Uint8Array)");
460
- if (data.byteLength === 0)
461
- throw new Error("failed to decode CBOR format (data is empty)");
462
- try {
463
- result = CBOR.decode(data, { tags: this.tags });
464
- } catch (err) {
465
- throw new Error("failed to decode CBOR format", { cause: err });
466
- }
467
- } else if (this.format === "json") {
468
- if (typeof data !== "string")
469
- throw new Error("failed to decode JSON format (data type is not string)");
470
- if (data.length === 0)
471
- throw new Error("failed to decode JSON format (data is empty)");
472
- try {
473
- result = JSONX.parse(data);
474
- } catch (err) {
475
- throw new Error("failed to decode JSON format", { cause: err });
476
- }
477
- } else
478
- throw new Error(`invalid format "${this.format}"`);
479
- return result;
480
- }
481
- }
482
- class CodecTrait extends OptionsTrait {
483
- /* construct API class */
484
- constructor(options = {}) {
485
- super(options);
486
- this.codec = new Codec(this.options.codec);
487
- }
488
- }
489
- class EncodeTrait extends CodecTrait {
490
- static {
491
- this.encoder = new TextEncoder();
492
- }
493
- static {
494
- this.decoder = new TextDecoder();
495
- }
496
- /* convert character string to buffer */
497
- str2buf(data) {
498
- return EncodeTrait.encoder.encode(data);
499
- }
500
- /* convert buffer to character string */
501
- buf2str(data) {
502
- return EncodeTrait.decoder.decode(data);
503
- }
504
- /* convert byte-based typed array to buffer */
505
- arr2buf(data) {
506
- let buffer;
507
- if (data instanceof Uint8Array)
508
- buffer = data;
509
- else
510
- buffer = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
511
- return buffer;
512
- }
513
- buf2arr(data, cons) {
514
- let arr;
515
- const ctor = cons;
516
- if (ctor === Buffer$1)
517
- arr = Buffer$1.from(data.buffer, data.byteOffset, data.byteLength);
518
- else if (ctor === Uint8Array)
519
- arr = data;
520
- else if (ctor === Int8Array)
521
- arr = new Int8Array(data.buffer, data.byteOffset, data.byteLength);
522
- else
523
- throw new Error("invalid data type");
524
- return arr;
525
- }
228
+ let description;
229
+ let spool;
230
+ let action;
231
+ let oncatch;
232
+ let onfinally;
233
+ let oncleanup;
234
+ if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) {
235
+ description = args[0].description;
236
+ spool = args[0].spool;
237
+ action = args[0].action;
238
+ oncatch = args[0].oncatch;
239
+ onfinally = args[0].onfinally;
240
+ oncleanup = args[0].oncleanup;
241
+ } else if (typeof args[0] === "string") {
242
+ description = args[0];
243
+ if (args[1] instanceof Spool || args[1] === void 0 && typeof args[2] === "function") {
244
+ spool = args[1];
245
+ action = args[2];
246
+ oncatch = args[3];
247
+ onfinally = args[4];
248
+ oncleanup = args[5];
249
+ } else {
250
+ action = args[1];
251
+ oncatch = args[2];
252
+ onfinally = args[3];
253
+ oncleanup = args[4];
254
+ }
255
+ } else if (args[0] instanceof Spool || args[0] === void 0 && typeof args[1] === "function") {
256
+ spool = args[0];
257
+ action = args[1];
258
+ oncatch = args[2];
259
+ onfinally = args[3];
260
+ oncleanup = args[4];
261
+ } else {
262
+ action = args[0];
263
+ oncatch = args[1];
264
+ onfinally = args[2];
265
+ oncleanup = args[3];
266
+ }
267
+ if (oncleanup && !spool) throw new Error("oncleanup requires a spool");
268
+ let result;
269
+ try {
270
+ result = action();
271
+ } catch (arg) {
272
+ let error = ensureError(arg, description);
273
+ if (oncatch) {
274
+ try {
275
+ result = oncatch(error);
276
+ } catch (arg) {
277
+ error = ensureError(arg, description);
278
+ runFinally(false, onfinally);
279
+ runUnroll(false, spool);
280
+ throw error;
281
+ }
282
+ runFinally(false, onfinally);
283
+ return result;
284
+ }
285
+ runFinally(false, onfinally);
286
+ runUnroll(false, spool);
287
+ throw error;
288
+ }
289
+ if (result instanceof Promise) return result.then(async (result) => {
290
+ await runFinally(true, onfinally);
291
+ if (spool && oncleanup) spool.roll(result, oncleanup);
292
+ return result;
293
+ }, async (arg) => {
294
+ let error = ensureError(arg, description);
295
+ if (oncatch) try {
296
+ const result = oncatch(error);
297
+ await runFinally(true, onfinally);
298
+ return result;
299
+ } catch (arg) {
300
+ error = ensureError(arg, description);
301
+ await runFinally(true, onfinally);
302
+ await runUnroll(true, spool);
303
+ throw error;
304
+ }
305
+ await runFinally(true, onfinally);
306
+ await runUnroll(true, spool);
307
+ throw error;
308
+ });
309
+ else {
310
+ runFinally(false, onfinally);
311
+ if (spool && oncleanup) spool.roll(result, oncleanup);
312
+ return result;
313
+ }
526
314
  }
527
- const versionToNum = (str) => {
528
- const m = str.match(/^(\d+)\.(\d+)$/);
529
- if (m === null)
530
- throw new Error("invalid version string");
531
- const minor = parseInt(m[2], 10);
532
- if (minor > 99)
533
- throw new Error("invalid version string: minor version exceeds 99");
534
- return parseInt(m[1], 10) * 100 + minor;
315
+ //#endregion
316
+ //#region dst-stage1/mqtt-plus-options.js
317
+ var OptionsTrait = class {
318
+ constructor(options = {}) {
319
+ this.options = {
320
+ id: nanoid(),
321
+ codec: "cbor",
322
+ timeout: 10 * 1e3,
323
+ share: "",
324
+ chunkSize: 16 * 1024,
325
+ chunkCredit: 4,
326
+ topicMake: (name, operation, peerId) => {
327
+ return `${name}/${operation}/${peerId ?? "any"}`;
328
+ },
329
+ topicMatch: (topic) => {
330
+ const m = topic.match(/^(.+)\/([^/]+)\/([^/]+)$/);
331
+ return m ? {
332
+ name: m[1],
333
+ operation: m[2],
334
+ peerId: m[3] === "any" ? void 0 : m[3]
335
+ } : null;
336
+ },
337
+ ...options
338
+ };
339
+ }
535
340
  };
536
- const VERSION = "1.4";
537
- const version = versionToNum(VERSION);
538
- const MetaSchema = v.pipe(v.record(v.string(), v.unknown()), v.check((data) => !Array.isArray(data)));
539
- const AuthSchema = v.pipe(v.array(v.pipe(v.string(), v.maxLength(8192))), v.maxLength(8));
540
- class Base {
541
- constructor(type, id, sender, receiver) {
542
- this.type = type;
543
- this.id = id;
544
- this.sender = sender;
545
- this.receiver = receiver;
546
- this.version = `MQTT+/${VERSION}`;
547
- }
548
- }
549
- const BaseSchema = {
550
- version: v.pipe(v.string(), v.regex(/^MQTT\+\/\d+\.\d+$/)),
551
- type: v.string(),
552
- id: v.string(),
553
- sender: v.optional(v.string()),
554
- receiver: v.optional(v.string())
341
+ //#endregion
342
+ //#region dst-stage1/mqtt-plus-codec.js
343
+ var JSONX = class {
344
+ static uint8ArrayToBase64(arr) {
345
+ return Buffer$1.from(arr.buffer, arr.byteOffset, arr.byteLength).toString("base64");
346
+ }
347
+ static base64ToUint8Array(base64) {
348
+ return new Uint8Array(Buffer$1.from(base64, "base64"));
349
+ }
350
+ static stringify(obj) {
351
+ return JSON.stringify(obj, (_, value) => value instanceof Uint8Array ? { __Uint8Array: this.uint8ArrayToBase64(value) } : value);
352
+ }
353
+ static parse(json) {
354
+ return JSON.parse(json, (_, value) => typeof value?.__Uint8Array === "string" ? this.base64ToUint8Array(value.__Uint8Array) : value);
355
+ }
555
356
  };
556
- class EventEmission extends Base {
557
- constructor(id, name, params, sender, receiver, auth, meta) {
558
- super("event-emission", id, sender, receiver);
559
- this.name = name;
560
- this.params = params;
561
- this.auth = auth;
562
- this.meta = meta;
563
- }
564
- }
565
- const EventEmissionSchema = v.strictObject({
566
- ...BaseSchema,
567
- type: v.literal("event-emission"),
568
- name: v.string(),
569
- params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
570
- auth: v.optional(AuthSchema),
571
- meta: v.optional(MetaSchema)
357
+ var Codec = class {
358
+ constructor(format) {
359
+ this.format = format;
360
+ this.types = new CBOR.TypeEncoderMap();
361
+ this.tags = /* @__PURE__ */ new Map();
362
+ const TAG_BUFFER = 64e3;
363
+ this.types.registerEncoder(Buffer$1, (buffer) => {
364
+ return [TAG_BUFFER, new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)];
365
+ });
366
+ this.tags.set(TAG_BUFFER, (tag) => {
367
+ return Buffer$1.from(tag.contents);
368
+ });
369
+ }
370
+ encode(data) {
371
+ let result;
372
+ if (this.format === "cbor") try {
373
+ result = CBOR.encode(data, { types: this.types });
374
+ } catch (err) {
375
+ throw new Error("failed to encode CBOR format", { cause: err });
376
+ }
377
+ else if (this.format === "json") try {
378
+ result = JSONX.stringify(data);
379
+ } catch (err) {
380
+ throw new Error("failed to encode JSON format", { cause: err });
381
+ }
382
+ else throw new Error(`invalid format "${this.format}"`);
383
+ return result;
384
+ }
385
+ decode(data) {
386
+ let result;
387
+ if (this.format === "cbor") {
388
+ if (!(data instanceof Uint8Array)) throw new Error("failed to decode CBOR format (data type is not Uint8Array)");
389
+ if (data.byteLength === 0) throw new Error("failed to decode CBOR format (data is empty)");
390
+ try {
391
+ result = CBOR.decode(data, { tags: this.tags });
392
+ } catch (err) {
393
+ throw new Error("failed to decode CBOR format", { cause: err });
394
+ }
395
+ } else if (this.format === "json") {
396
+ if (typeof data !== "string") throw new Error("failed to decode JSON format (data type is not string)");
397
+ if (data.length === 0) throw new Error("failed to decode JSON format (data is empty)");
398
+ try {
399
+ result = JSONX.parse(data);
400
+ } catch (err) {
401
+ throw new Error("failed to decode JSON format", { cause: err });
402
+ }
403
+ } else throw new Error(`invalid format "${this.format}"`);
404
+ return result;
405
+ }
406
+ };
407
+ var CodecTrait = class extends OptionsTrait {
408
+ constructor(options = {}) {
409
+ super(options);
410
+ this.codec = new Codec(this.options.codec);
411
+ }
412
+ };
413
+ //#endregion
414
+ //#region dst-stage1/mqtt-plus-encode.js
415
+ var EncodeTrait = class EncodeTrait extends CodecTrait {
416
+ static {
417
+ this.encoder = new TextEncoder();
418
+ }
419
+ static {
420
+ this.decoder = new TextDecoder();
421
+ }
422
+ str2buf(data) {
423
+ return EncodeTrait.encoder.encode(data);
424
+ }
425
+ buf2str(data) {
426
+ return EncodeTrait.decoder.decode(data);
427
+ }
428
+ arr2buf(data) {
429
+ let buffer;
430
+ if (data instanceof Uint8Array) buffer = data;
431
+ else buffer = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
432
+ return buffer;
433
+ }
434
+ buf2arr(data, cons) {
435
+ let arr;
436
+ const ctor = cons;
437
+ if (ctor === Buffer$1) arr = Buffer$1.from(data.buffer, data.byteOffset, data.byteLength);
438
+ else if (ctor === Uint8Array) arr = data;
439
+ else if (ctor === Int8Array) arr = new Int8Array(data.buffer, data.byteOffset, data.byteLength);
440
+ else throw new Error("invalid data type");
441
+ return arr;
442
+ }
443
+ };
444
+ //#endregion
445
+ //#region dst-stage1/mqtt-plus-version.js
446
+ var versionToNum = (str) => {
447
+ const m = str.match(/^(\d+)\.(\d+)$/);
448
+ if (m === null) throw new Error("invalid version string");
449
+ const minor = parseInt(m[2], 10);
450
+ if (minor > 99) throw new Error("invalid version string: minor version exceeds 99");
451
+ return parseInt(m[1], 10) * 100 + minor;
452
+ };
453
+ var version = versionToNum("1.4");
454
+ //#endregion
455
+ //#region dst-stage1/mqtt-plus-msg.js
456
+ var MetaSchema = v.pipe(v.record(v.string(), v.unknown()), v.check((data) => !Array.isArray(data)));
457
+ var AuthSchema = v.pipe(v.array(v.pipe(v.string(), v.maxLength(8192))), v.maxLength(8));
458
+ var Base = class {
459
+ constructor(type, id, sender, receiver) {
460
+ this.type = type;
461
+ this.id = id;
462
+ this.sender = sender;
463
+ this.receiver = receiver;
464
+ this.version = `MQTT+/1.4`;
465
+ }
466
+ };
467
+ var BaseSchema = {
468
+ version: v.pipe(v.string(), v.regex(/^MQTT\+\/\d+\.\d+$/)),
469
+ type: v.string(),
470
+ id: v.string(),
471
+ sender: v.optional(v.string()),
472
+ receiver: v.optional(v.string())
473
+ };
474
+ var EventEmission = class extends Base {
475
+ constructor(id, name, params, sender, receiver, auth, meta) {
476
+ super("event-emission", id, sender, receiver);
477
+ this.name = name;
478
+ this.params = params;
479
+ this.auth = auth;
480
+ this.meta = meta;
481
+ }
482
+ };
483
+ var EventEmissionSchema = v.strictObject({
484
+ ...BaseSchema,
485
+ type: v.literal("event-emission"),
486
+ name: v.string(),
487
+ params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
488
+ auth: v.optional(AuthSchema),
489
+ meta: v.optional(MetaSchema)
572
490
  });
573
- class ServiceCallRequest extends Base {
574
- constructor(id, name, params, sender, receiver, auth, meta) {
575
- super("service-call-request", id, sender, receiver);
576
- this.name = name;
577
- this.params = params;
578
- this.auth = auth;
579
- this.meta = meta;
580
- }
581
- }
582
- const ServiceCallRequestSchema = v.strictObject({
583
- ...BaseSchema,
584
- type: v.literal("service-call-request"),
585
- name: v.string(),
586
- params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
587
- auth: v.optional(AuthSchema),
588
- meta: v.optional(MetaSchema)
491
+ var ServiceCallRequest = class extends Base {
492
+ constructor(id, name, params, sender, receiver, auth, meta) {
493
+ super("service-call-request", id, sender, receiver);
494
+ this.name = name;
495
+ this.params = params;
496
+ this.auth = auth;
497
+ this.meta = meta;
498
+ }
499
+ };
500
+ var ServiceCallRequestSchema = v.strictObject({
501
+ ...BaseSchema,
502
+ type: v.literal("service-call-request"),
503
+ name: v.string(),
504
+ params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
505
+ auth: v.optional(AuthSchema),
506
+ meta: v.optional(MetaSchema)
589
507
  });
590
- class ServiceCallResponse extends Base {
591
- constructor(id, result, error, sender, receiver) {
592
- super("service-call-response", id, sender, receiver);
593
- this.result = result;
594
- this.error = error;
595
- }
596
- }
597
- const ServiceCallResponseSchema = v.strictObject({
598
- ...BaseSchema,
599
- type: v.literal("service-call-response"),
600
- result: v.optional(v.unknown()),
601
- error: v.optional(v.string())
508
+ var ServiceCallResponse = class extends Base {
509
+ constructor(id, result, error, sender, receiver) {
510
+ super("service-call-response", id, sender, receiver);
511
+ this.result = result;
512
+ this.error = error;
513
+ }
514
+ };
515
+ var ServiceCallResponseSchema = v.strictObject({
516
+ ...BaseSchema,
517
+ type: v.literal("service-call-response"),
518
+ result: v.optional(v.unknown()),
519
+ error: v.optional(v.string())
602
520
  });
603
- class SinkPushRequest extends Base {
604
- constructor(id, name, params, sender, receiver, auth, meta) {
605
- super("sink-push-request", id, sender, receiver);
606
- this.name = name;
607
- this.params = params;
608
- this.auth = auth;
609
- this.meta = meta;
610
- }
611
- }
612
- const SinkPushRequestSchema = v.strictObject({
613
- ...BaseSchema,
614
- type: v.literal("sink-push-request"),
615
- name: v.string(),
616
- params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
617
- auth: v.optional(AuthSchema),
618
- meta: v.optional(MetaSchema)
521
+ var SinkPushRequest = class extends Base {
522
+ constructor(id, name, params, sender, receiver, auth, meta) {
523
+ super("sink-push-request", id, sender, receiver);
524
+ this.name = name;
525
+ this.params = params;
526
+ this.auth = auth;
527
+ this.meta = meta;
528
+ }
529
+ };
530
+ var SinkPushRequestSchema = v.strictObject({
531
+ ...BaseSchema,
532
+ type: v.literal("sink-push-request"),
533
+ name: v.string(),
534
+ params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
535
+ auth: v.optional(AuthSchema),
536
+ meta: v.optional(MetaSchema)
619
537
  });
620
- class SinkPushResponse extends Base {
621
- constructor(id, name, error, sender, receiver, credit) {
622
- super("sink-push-response", id, sender, receiver);
623
- this.name = name;
624
- this.error = error;
625
- this.credit = credit;
626
- }
627
- }
628
- const SinkPushResponseSchema = v.strictObject({
629
- ...BaseSchema,
630
- type: v.literal("sink-push-response"),
631
- name: v.string(),
632
- error: v.optional(v.string()),
633
- credit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1)))
538
+ var SinkPushResponse = class extends Base {
539
+ constructor(id, name, error, sender, receiver, credit) {
540
+ super("sink-push-response", id, sender, receiver);
541
+ this.name = name;
542
+ this.error = error;
543
+ this.credit = credit;
544
+ }
545
+ };
546
+ var SinkPushResponseSchema = v.strictObject({
547
+ ...BaseSchema,
548
+ type: v.literal("sink-push-response"),
549
+ name: v.string(),
550
+ error: v.optional(v.string()),
551
+ credit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1)))
634
552
  });
635
- class SinkPushChunk extends Base {
636
- constructor(id, name, chunk, error, final, sender, receiver) {
637
- super("sink-push-chunk", id, sender, receiver);
638
- this.name = name;
639
- this.chunk = chunk;
640
- this.error = error;
641
- this.final = final;
642
- }
643
- }
644
- const SinkPushChunkSchema = v.strictObject({
645
- ...BaseSchema,
646
- type: v.literal("sink-push-chunk"),
647
- name: v.string(),
648
- chunk: v.optional(v.instance(Uint8Array)),
649
- error: v.optional(v.string()),
650
- final: v.optional(v.boolean())
553
+ var SinkPushChunk = class extends Base {
554
+ constructor(id, name, chunk, error, final, sender, receiver) {
555
+ super("sink-push-chunk", id, sender, receiver);
556
+ this.name = name;
557
+ this.chunk = chunk;
558
+ this.error = error;
559
+ this.final = final;
560
+ }
561
+ };
562
+ var SinkPushChunkSchema = v.strictObject({
563
+ ...BaseSchema,
564
+ type: v.literal("sink-push-chunk"),
565
+ name: v.string(),
566
+ chunk: v.optional(v.instance(Uint8Array)),
567
+ error: v.optional(v.string()),
568
+ final: v.optional(v.boolean())
651
569
  });
652
- class SinkPushCredit extends Base {
653
- constructor(id, name, credit, sender, receiver) {
654
- super("sink-push-credit", id, sender, receiver);
655
- this.name = name;
656
- this.credit = credit;
657
- }
658
- }
659
- const SinkPushCreditSchema = v.strictObject({
660
- ...BaseSchema,
661
- type: v.literal("sink-push-credit"),
662
- name: v.string(),
663
- credit: v.pipe(v.number(), v.integer(), v.minValue(1))
570
+ var SinkPushCredit = class extends Base {
571
+ constructor(id, name, credit, sender, receiver) {
572
+ super("sink-push-credit", id, sender, receiver);
573
+ this.name = name;
574
+ this.credit = credit;
575
+ }
576
+ };
577
+ var SinkPushCreditSchema = v.strictObject({
578
+ ...BaseSchema,
579
+ type: v.literal("sink-push-credit"),
580
+ name: v.string(),
581
+ credit: v.pipe(v.number(), v.integer(), v.minValue(1))
664
582
  });
665
- class SourceFetchRequest extends Base {
666
- constructor(id, name, params, sender, receiver, auth, meta, credit) {
667
- super("source-fetch-request", id, sender, receiver);
668
- this.name = name;
669
- this.params = params;
670
- this.auth = auth;
671
- this.meta = meta;
672
- this.credit = credit;
673
- }
674
- }
675
- const SourceFetchRequestSchema = v.strictObject({
676
- ...BaseSchema,
677
- type: v.literal("source-fetch-request"),
678
- name: v.string(),
679
- params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
680
- auth: v.optional(AuthSchema),
681
- meta: v.optional(MetaSchema),
682
- credit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1)))
583
+ var SourceFetchRequest = class extends Base {
584
+ constructor(id, name, params, sender, receiver, auth, meta, credit) {
585
+ super("source-fetch-request", id, sender, receiver);
586
+ this.name = name;
587
+ this.params = params;
588
+ this.auth = auth;
589
+ this.meta = meta;
590
+ this.credit = credit;
591
+ }
592
+ };
593
+ var SourceFetchRequestSchema = v.strictObject({
594
+ ...BaseSchema,
595
+ type: v.literal("source-fetch-request"),
596
+ name: v.string(),
597
+ params: v.optional(v.pipe(v.array(v.unknown()), v.maxLength(64))),
598
+ auth: v.optional(AuthSchema),
599
+ meta: v.optional(MetaSchema),
600
+ credit: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1)))
683
601
  });
684
- class SourceFetchResponse extends Base {
685
- constructor(id, name, error, sender, receiver, meta) {
686
- super("source-fetch-response", id, sender, receiver);
687
- this.name = name;
688
- this.error = error;
689
- this.meta = meta;
690
- }
691
- }
692
- const SourceFetchResponseSchema = v.strictObject({
693
- ...BaseSchema,
694
- type: v.literal("source-fetch-response"),
695
- name: v.string(),
696
- error: v.optional(v.string()),
697
- meta: v.optional(MetaSchema)
602
+ var SourceFetchResponse = class extends Base {
603
+ constructor(id, name, error, sender, receiver, meta) {
604
+ super("source-fetch-response", id, sender, receiver);
605
+ this.name = name;
606
+ this.error = error;
607
+ this.meta = meta;
608
+ }
609
+ };
610
+ var SourceFetchResponseSchema = v.strictObject({
611
+ ...BaseSchema,
612
+ type: v.literal("source-fetch-response"),
613
+ name: v.string(),
614
+ error: v.optional(v.string()),
615
+ meta: v.optional(MetaSchema)
698
616
  });
699
- class SourceFetchChunk extends Base {
700
- constructor(id, name, chunk, error, final, sender, receiver) {
701
- super("source-fetch-chunk", id, sender, receiver);
702
- this.name = name;
703
- this.chunk = chunk;
704
- this.error = error;
705
- this.final = final;
706
- }
707
- }
708
- const SourceFetchChunkSchema = v.strictObject({
709
- ...BaseSchema,
710
- type: v.literal("source-fetch-chunk"),
711
- name: v.string(),
712
- chunk: v.optional(v.instance(Uint8Array)),
713
- error: v.optional(v.string()),
714
- final: v.optional(v.boolean())
617
+ var SourceFetchChunk = class extends Base {
618
+ constructor(id, name, chunk, error, final, sender, receiver) {
619
+ super("source-fetch-chunk", id, sender, receiver);
620
+ this.name = name;
621
+ this.chunk = chunk;
622
+ this.error = error;
623
+ this.final = final;
624
+ }
625
+ };
626
+ var SourceFetchChunkSchema = v.strictObject({
627
+ ...BaseSchema,
628
+ type: v.literal("source-fetch-chunk"),
629
+ name: v.string(),
630
+ chunk: v.optional(v.instance(Uint8Array)),
631
+ error: v.optional(v.string()),
632
+ final: v.optional(v.boolean())
715
633
  });
716
- class SourceFetchCredit extends Base {
717
- constructor(id, name, credit, sender, receiver) {
718
- super("source-fetch-credit", id, sender, receiver);
719
- this.name = name;
720
- this.credit = credit;
721
- }
722
- }
723
- const SourceFetchCreditSchema = v.strictObject({
724
- ...BaseSchema,
725
- type: v.literal("source-fetch-credit"),
726
- name: v.string(),
727
- credit: v.pipe(v.number(), v.integer(), v.minValue(0))
634
+ var SourceFetchCredit = class extends Base {
635
+ constructor(id, name, credit, sender, receiver) {
636
+ super("source-fetch-credit", id, sender, receiver);
637
+ this.name = name;
638
+ this.credit = credit;
639
+ }
640
+ };
641
+ var SourceFetchCreditSchema = v.strictObject({
642
+ ...BaseSchema,
643
+ type: v.literal("source-fetch-credit"),
644
+ name: v.string(),
645
+ credit: v.pipe(v.number(), v.integer(), v.minValue(0))
728
646
  });
729
- class Msg {
730
- /* factories for creating objects */
731
- makeEventEmission(id, name, params, sender, receiver, auth, meta) {
732
- return new EventEmission(id, name, params, sender, receiver, auth, meta);
733
- }
734
- makeServiceCallRequest(id, name, params, sender, receiver, auth, meta) {
735
- return new ServiceCallRequest(id, name, params, sender, receiver, auth, meta);
736
- }
737
- makeServiceCallResponse(id, result, error, sender, receiver) {
738
- return new ServiceCallResponse(id, result, error, sender, receiver);
739
- }
740
- makeSinkPushRequest(id, name, params, sender, receiver, auth, meta) {
741
- return new SinkPushRequest(id, name, params, sender, receiver, auth, meta);
742
- }
743
- makeSinkPushResponse(id, name, error, sender, receiver, credit) {
744
- return new SinkPushResponse(id, name, error, sender, receiver, credit);
745
- }
746
- makeSinkPushChunk(id, name, chunk, error, final, sender, receiver) {
747
- return new SinkPushChunk(id, name, chunk, error, final, sender, receiver);
748
- }
749
- makeSinkPushCredit(id, name, credit, sender, receiver) {
750
- return new SinkPushCredit(id, name, credit, sender, receiver);
751
- }
752
- makeSourceFetchRequest(id, name, params, sender, receiver, auth, meta, credit) {
753
- return new SourceFetchRequest(id, name, params, sender, receiver, auth, meta, credit);
754
- }
755
- makeSourceFetchResponse(id, name, error, sender, receiver, meta) {
756
- return new SourceFetchResponse(id, name, error, sender, receiver, meta);
757
- }
758
- makeSourceFetchChunk(id, name, chunk, error, final, sender, receiver) {
759
- return new SourceFetchChunk(id, name, chunk, error, final, sender, receiver);
760
- }
761
- makeSourceFetchCredit(id, name, credit, sender, receiver) {
762
- return new SourceFetchCredit(id, name, credit, sender, receiver);
763
- }
764
- /* parse any object into typed object */
765
- parse(obj) {
766
- if (typeof obj !== "object" || obj === null)
767
- throw new Error("invalid argument: not an object");
768
- if (typeof obj.version !== "string")
769
- throw new Error('invalid object: missing or invalid "version" field');
770
- const match = obj.version.match(/^MQTT\+\/(\d+\.\d+)$/);
771
- const versionNum = match !== null ? versionToNum(match[1]) : 0;
772
- if (versionNum !== version)
773
- throw new Error(`protocol version mismatch (expected version ${VERSION}, got version ${obj.version})`);
774
- const parseObject = (obj2, name, schema) => {
775
- const res = v.safeParse(schema, obj2);
776
- if (!res.success) {
777
- const issues = res.issues.map((issue) => issue.message).join("; ");
778
- throw new Error(`invalid ${name} object: ${issues}`);
779
- }
780
- return res.output;
781
- };
782
- if (typeof obj.type !== "string")
783
- throw new Error('invalid object: missing or invalid "type" field');
784
- if (obj.type === "event-emission") {
785
- const out = parseObject(obj, "EventEmission", EventEmissionSchema);
786
- return this.makeEventEmission(out.id, out.name, out.params, out.sender, out.receiver, out.auth, out.meta);
787
- } else if (obj.type === "service-call-request") {
788
- const out = parseObject(obj, "ServiceCallRequest", ServiceCallRequestSchema);
789
- return this.makeServiceCallRequest(out.id, out.name, out.params, out.sender, out.receiver, out.auth, out.meta);
790
- } else if (obj.type === "service-call-response") {
791
- const out = parseObject(obj, "ServiceCallResponse", ServiceCallResponseSchema);
792
- return this.makeServiceCallResponse(out.id, out.result, out.error, out.sender, out.receiver);
793
- } else if (obj.type === "sink-push-request") {
794
- const out = parseObject(obj, "SinkPushRequest", SinkPushRequestSchema);
795
- return this.makeSinkPushRequest(out.id, out.name, out.params, out.sender, out.receiver, out.auth, out.meta);
796
- } else if (obj.type === "sink-push-response") {
797
- const out = parseObject(obj, "SinkPushResponse", SinkPushResponseSchema);
798
- return this.makeSinkPushResponse(out.id, out.name, out.error, out.sender, out.receiver, out.credit);
799
- } else if (obj.type === "sink-push-chunk") {
800
- const out = parseObject(obj, "SinkPushChunk", SinkPushChunkSchema);
801
- return this.makeSinkPushChunk(out.id, out.name, out.chunk, out.error, out.final, out.sender, out.receiver);
802
- } else if (obj.type === "sink-push-credit") {
803
- const out = parseObject(obj, "SinkPushCredit", SinkPushCreditSchema);
804
- return this.makeSinkPushCredit(out.id, out.name, out.credit, out.sender, out.receiver);
805
- } else if (obj.type === "source-fetch-request") {
806
- const out = parseObject(obj, "SourceFetchRequest", SourceFetchRequestSchema);
807
- return this.makeSourceFetchRequest(out.id, out.name, out.params, out.sender, out.receiver, out.auth, out.meta, out.credit);
808
- } else if (obj.type === "source-fetch-response") {
809
- const out = parseObject(obj, "SourceFetchResponse", SourceFetchResponseSchema);
810
- return this.makeSourceFetchResponse(out.id, out.name, out.error, out.sender, out.receiver, out.meta);
811
- } else if (obj.type === "source-fetch-chunk") {
812
- const out = parseObject(obj, "SourceFetchChunk", SourceFetchChunkSchema);
813
- return this.makeSourceFetchChunk(out.id, out.name, out.chunk, out.error, out.final, out.sender, out.receiver);
814
- } else if (obj.type === "source-fetch-credit") {
815
- const out = parseObject(obj, "SourceFetchCredit", SourceFetchCreditSchema);
816
- return this.makeSourceFetchCredit(out.id, out.name, out.credit, out.sender, out.receiver);
817
- } else
818
- throw new Error("invalid object: not of any known type");
819
- }
820
- /* guard for request messages */
821
- isRequest(msg) {
822
- return msg instanceof EventEmission || msg instanceof ServiceCallRequest || msg instanceof SourceFetchRequest || msg instanceof SinkPushRequest;
823
- }
824
- /* guard for response messages */
825
- isResponse(msg) {
826
- return msg instanceof ServiceCallResponse || msg instanceof SinkPushResponse || msg instanceof SinkPushChunk || msg instanceof SinkPushCredit || msg instanceof SourceFetchResponse || msg instanceof SourceFetchChunk || msg instanceof SourceFetchCredit;
827
- }
828
- }
829
- class MsgTrait extends EncodeTrait {
830
- constructor() {
831
- super(...arguments);
832
- this.msg = new Msg();
833
- }
834
- }
835
- class LogEvent {
836
- constructor(timestamp, level, msg, data) {
837
- this.timestamp = timestamp;
838
- this.level = level;
839
- this.msg = msg;
840
- this.data = data;
841
- }
842
- /* resolve all pending promises in the log event */
843
- async resolve() {
844
- if (this.msg instanceof Promise)
845
- this.msg = await this.msg.catch(() => "<resolve-failed>");
846
- if (this.data) {
847
- for (const k of Object.keys(this.data))
848
- if (this.data[k] instanceof Promise)
849
- this.data[k] = await this.data[k].catch(() => "<resolve-failed>");
850
- }
851
- }
852
- /* render log event as string */
853
- toString() {
854
- const timestamp = new Date(this.timestamp);
855
- const year = timestamp.getFullYear();
856
- const month = (timestamp.getMonth() + 1).toString().padStart(2, "0");
857
- const day = timestamp.getDate().toString().padStart(2, "0");
858
- const hours = timestamp.getHours().toString().padStart(2, "0");
859
- const minutes = timestamp.getMinutes().toString().padStart(2, "0");
860
- const seconds = timestamp.getSeconds().toString().padStart(2, "0");
861
- const ms = timestamp.getMilliseconds().toString().padStart(3, "0");
862
- const time = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
863
- const msg = this.msg instanceof Promise ? "<unresolved>" : this.msg;
864
- let extra = "";
865
- if (this.data !== void 0) {
866
- const data = this.data;
867
- const kv = Object.keys(data).map((key) => {
868
- const value = data[key] instanceof Promise ? "<unresolved>" : data[key];
869
- return `${key}: ${JSONX.stringify(value)}`;
870
- }).join(", ");
871
- extra = ` (${kv})`;
872
- }
873
- return `[${time}] ${this.level}: ${msg}${extra}`;
874
- }
875
- }
876
- class TraceTrait extends MsgTrait {
877
- constructor() {
878
- super(...arguments);
879
- this._events = new EventEmitter();
880
- }
881
- on(...args) {
882
- this._events.on(...args);
883
- }
884
- off(...args) {
885
- this._events.off(...args);
886
- }
887
- emitEvent(...args) {
888
- if (this._events.listenerCount(args[0]) === 0)
889
- return false;
890
- return this._events.emit(...args);
891
- }
892
- /* log an event */
893
- log(level, msg, data) {
894
- const event = new LogEvent(Date.now(), level, msg, data);
895
- this.emitEvent("log", event);
896
- }
897
- /* raise an error event */
898
- error(error, msg) {
899
- let err = error;
900
- if (msg !== void 0)
901
- err = new Error(`${msg}: ${error.message}`, { cause: error });
902
- this.emitEvent("error", err);
903
- this.log("error", err.message);
904
- }
905
- }
906
- class BaseTrait extends TraceTrait {
907
- /* construct API class */
908
- constructor(mqtt, options = {}) {
909
- super(options);
910
- this.onRequest = /* @__PURE__ */ new Map();
911
- this.onResponse = /* @__PURE__ */ new Map();
912
- if (mqtt === null) {
913
- this.log("info", "establishing proxy MQTT client");
914
- mqtt = new Proxy({}, {
915
- get(_target, prop, _receiver) {
916
- if (prop === "isFakeProxy")
917
- return true;
918
- else if (typeof prop === "string" && ["on", "off", "once"].includes(prop))
919
- return () => {
920
- };
921
- else
922
- return () => {
923
- throw new Error(`Underlying MQTT operation "${String(prop)}" called on a null MQTT client -- only MQTT+ "emit({ ..., dry: true })" is supported in this special operation mode`);
924
- };
925
- }
926
- });
927
- }
928
- this.mqtt = mqtt;
929
- this.log("info", "hooking into MQTT client");
930
- this.messageHandler = (topic, message, packet) => {
931
- let input;
932
- if (this.options.codec === "json")
933
- input = message.toString();
934
- else if (this.options.codec === "cbor")
935
- input = Buffer.isBuffer(message) ? new Uint8Array(message.buffer, message.byteOffset, message.byteLength) : message;
936
- else
937
- throw new Error("invalid codec configured");
938
- this._onMessage(topic, input, packet);
939
- };
940
- this.mqtt.on("message", this.messageHandler);
941
- }
942
- /* destroy API class */
943
- async destroy() {
944
- this.log("info", "un-hooking from MQTT client");
945
- this.mqtt.off("message", this.messageHandler);
946
- this.onRequest.clear();
947
- this.onResponse.clear();
948
- }
949
- /* create a registration for subsequent destruction */
950
- makeRegistration(spool, kind, name, key) {
951
- return {
952
- destroy: async () => {
953
- if (!this.onRequest.has(key))
954
- throw new Error(`destroy: ${kind} "${name}" not registered`);
955
- await spool.unroll(false)?.catch((err) => {
956
- const error = ensureError(err, `destroy: ${kind} "${name}" failed to cleanup`);
957
- this.error(error);
958
- throw error;
959
- });
960
- }
961
- };
962
- }
963
- /* subscribe to an MQTT topic (Promise-based) */
964
- async subscribeTopic(topic, options = {}) {
965
- this.log("info", `subscribing to MQTT topic "${topic}"`);
966
- return new Promise((resolve, reject) => {
967
- this.mqtt.subscribe(topic, { qos: 2, ...options }, (err, _granted) => {
968
- if (err) {
969
- this.error(err, `subscribing to MQTT topic "${topic}" failed`);
970
- reject(err);
971
- } else
972
- resolve();
973
- });
974
- });
975
- }
976
- /* unsubscribe from an MQTT topic (Promise-based) */
977
- async unsubscribeTopic(topic) {
978
- this.log("info", `unsubscribing from MQTT topic "${topic}"`);
979
- return new Promise((resolve, reject) => {
980
- this.mqtt.unsubscribe(topic, (err, _packet) => {
981
- if (err) {
982
- this.error(err, `unsubscribing from MQTT topic "${topic}" failed`);
983
- reject(err);
984
- } else
985
- resolve();
986
- });
987
- });
988
- }
989
- /* publish to an MQTT topic (Promise-based) */
990
- async publishToTopic(topic, message, options = {}) {
991
- if (typeof message === "string")
992
- this.log("info", `publishing to MQTT topic "${topic}" (type: string, length: ${message.length} chars)`);
993
- else
994
- this.log("info", `publishing to MQTT topic "${topic}" (type: buffer, length: ${message.byteLength} bytes)`);
995
- const messageOnDemand = new PLazy((resolve, reject) => {
996
- let parsed;
997
- try {
998
- const payload = this.codec.decode(message);
999
- parsed = this.msg.parse(payload);
1000
- } catch (err) {
1001
- return reject(err);
1002
- }
1003
- resolve(parsed);
1004
- });
1005
- this.log("debug", `publishing to MQTT topic "${topic}"`, { message: messageOnDemand });
1006
- return new Promise((resolve, reject) => {
1007
- const messageData = typeof message === "string" ? message : Buffer.from(message.buffer, message.byteOffset, message.byteLength);
1008
- this.mqtt.publish(topic, messageData, options, (err) => {
1009
- if (err) {
1010
- this.error(err, `publishing to MQTT topic "${topic}" failed`);
1011
- reject(err);
1012
- } else
1013
- resolve();
1014
- });
1015
- });
1016
- }
1017
- /* handle incoming MQTT message */
1018
- _onMessage(topic, data, _packet) {
1019
- const topicMatch = this.options.topicMatch(topic);
1020
- if (topicMatch === null)
1021
- return;
1022
- if (typeof data === "string")
1023
- this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${data.length} chars)`);
1024
- else
1025
- this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`);
1026
- let payload;
1027
- try {
1028
- payload = this.codec.decode(data);
1029
- } catch (err) {
1030
- this.error(ensureError(err, "failed to parse message into object"));
1031
- return;
1032
- }
1033
- let message;
1034
- try {
1035
- message = this.msg.parse(payload);
1036
- } catch (err) {
1037
- this.error(ensureError(err, "failed to parse object into typed message object"));
1038
- return;
1039
- }
1040
- this.log("debug", `received from MQTT topic "${topic}"`, { message });
1041
- if (this.msg.isRequest(message)) {
1042
- const handler = this.onRequest.get(`${message.type}:${message.name}`);
1043
- if (handler !== void 0) {
1044
- Promise.resolve().then(() => handler(message, topicMatch.name)).catch((err) => {
1045
- this.error(ensureError(err, `dispatching request message from MQTT topic "${topic}" failed`));
1046
- });
1047
- }
1048
- } else if (this.msg.isResponse(message)) {
1049
- const handler = this.onResponse.get(`${message.type}:${message.id}`);
1050
- if (handler !== void 0) {
1051
- Promise.resolve().then(() => handler(message, topicMatch.name)).catch((err) => {
1052
- this.error(ensureError(err, `dispatching response message from MQTT topic "${topic}" failed`));
1053
- });
1054
- }
1055
- }
1056
- }
1057
- }
1058
- class RefCountedSubscription {
1059
- /* initial construction with configuration */
1060
- constructor(subscribeFn, unsubscribeFn, lingerMs = 30 * 1e3) {
1061
- this.subscribeFn = subscribeFn;
1062
- this.unsubscribeFn = unsubscribeFn;
1063
- this.lingerMs = lingerMs;
1064
- this.counts = /* @__PURE__ */ new Map();
1065
- this.pending = /* @__PURE__ */ new Map();
1066
- this.lingers = /* @__PURE__ */ new Map();
1067
- this.unsubbing = /* @__PURE__ */ new Map();
1068
- }
1069
- /* increment reference count for a topic */
1070
- incrementCount(topic) {
1071
- const count = this.counts.get(topic) ?? 0;
1072
- this.counts.set(topic, count + 1);
1073
- return count;
1074
- }
1075
- /* decrement reference count for a topic */
1076
- decrementCount(topic) {
1077
- const count = this.counts.get(topic);
1078
- if (count !== void 0) {
1079
- if (count <= 1) {
1080
- this.counts.delete(topic);
1081
- return 0;
1082
- } else {
1083
- this.counts.set(topic, count - 1);
1084
- return count - 1;
1085
- }
1086
- }
1087
- return void 0;
1088
- }
1089
- /* subscribe to a topic (reference-counted) */
1090
- async subscribe(topic, options = { qos: 2 }) {
1091
- const count = this.incrementCount(topic);
1092
- const linger = this.lingers.get(topic);
1093
- if (linger) {
1094
- clearTimeout(linger);
1095
- this.lingers.delete(topic);
1096
- return;
1097
- }
1098
- if (count === 0) {
1099
- let resolve;
1100
- let reject;
1101
- const deferred = new Promise((res, rej) => {
1102
- resolve = res;
1103
- reject = rej;
1104
- });
1105
- deferred.catch(() => {
1106
- });
1107
- this.pending.set(topic, deferred);
1108
- const inflight = this.unsubbing.get(topic);
1109
- if (inflight)
1110
- await inflight;
1111
- const promise = this.subscribeFn(topic, options).then(() => {
1112
- this.pending.delete(topic);
1113
- resolve();
1114
- }).catch((err) => {
1115
- this.pending.delete(topic);
1116
- this.decrementCount(topic);
1117
- reject(err);
1118
- throw err;
1119
- });
1120
- return promise;
1121
- } else {
1122
- const pending = this.pending.get(topic);
1123
- if (pending)
1124
- return pending.catch((err) => {
1125
- this.decrementCount(topic);
1126
- throw err;
1127
- });
1128
- }
1129
- }
1130
- /* unsubscribe from a topic (reference-counted) */
1131
- async unsubscribe(topic) {
1132
- const count = this.decrementCount(topic);
1133
- if (count === 0) {
1134
- if (this.lingerMs > 0) {
1135
- const timer = setTimeout(() => {
1136
- this.lingers.delete(topic);
1137
- const promise = this.unsubscribeFn(topic).catch(() => {
1138
- }).finally(() => {
1139
- this.unsubbing.delete(topic);
1140
- });
1141
- this.unsubbing.set(topic, promise);
1142
- }, this.lingerMs);
1143
- this.lingers.set(topic, timer);
1144
- } else {
1145
- const promise = this.unsubscribeFn(topic).catch(() => {
1146
- }).finally(() => {
1147
- this.unsubbing.delete(topic);
1148
- });
1149
- this.unsubbing.set(topic, promise);
1150
- await promise;
1151
- }
1152
- }
1153
- }
1154
- /* flush all pending linger timers and unsubscribe */
1155
- async flush() {
1156
- const topics = /* @__PURE__ */ new Set([
1157
- ...this.counts.keys(),
1158
- ...this.lingers.keys(),
1159
- ...this.pending.keys(),
1160
- ...this.unsubbing.keys()
1161
- ]);
1162
- for (const timer of this.lingers.values())
1163
- clearTimeout(timer);
1164
- this.lingers.clear();
1165
- this.counts.clear();
1166
- await Promise.allSettled([...this.pending.values(), ...this.unsubbing.values()]);
1167
- await Promise.allSettled([...topics].map((topic) => this.unsubscribeFn(topic).catch(() => {
1168
- })));
1169
- this.pending.clear();
1170
- this.unsubbing.clear();
1171
- }
1172
- }
1173
- class SubscriptionTrait extends BaseTrait {
1174
- constructor() {
1175
- super(...arguments);
1176
- this.subscriptions = new RefCountedSubscription((topic, options) => this.subscribeTopic(topic, options), (topic) => this.unsubscribeTopic(topic));
1177
- }
1178
- /* subscribe to an MQTT topic (reference-counted) and spool the unsubscription */
1179
- async subscribeTopicAndSpool(spool, topic, options = {}) {
1180
- await run(`subscribe to MQTT topic "${topic}"`, spool, () => this.subscriptions.subscribe(topic, { qos: 2, ...options }));
1181
- spool.roll(() => this.subscriptions.unsubscribe(topic));
1182
- }
1183
- /* destroy subscription trait */
1184
- async destroy() {
1185
- await this.subscriptions.flush();
1186
- await super.destroy();
1187
- }
1188
- }
1189
- class TimerTrait extends SubscriptionTrait {
1190
- constructor() {
1191
- super(...arguments);
1192
- this.timers = /* @__PURE__ */ new Map();
1193
- }
1194
- /* destroy timer trait */
1195
- async destroy() {
1196
- for (const timer of this.timers.values())
1197
- clearTimeout(timer);
1198
- this.timers.clear();
1199
- await super.destroy();
1200
- }
1201
- /* refresh (or start) a named timer */
1202
- timerRefresh(id, onTimeout) {
1203
- const timer = this.timers.get(id);
1204
- if (timer !== void 0)
1205
- clearTimeout(timer);
1206
- this.timers.set(id, setTimeout(async () => {
1207
- this.timers.delete(id);
1208
- try {
1209
- await onTimeout();
1210
- } catch (err) {
1211
- this.error(ensureError(err), `timer "${id}" failed`);
1212
- }
1213
- }, this.options.timeout));
1214
- }
1215
- /* clear a named timer */
1216
- timerClear(id) {
1217
- const timer = this.timers.get(id);
1218
- if (timer !== void 0) {
1219
- clearTimeout(timer);
1220
- this.timers.delete(id);
1221
- }
1222
- }
1223
- }
1224
- class MetaTrait extends TimerTrait {
1225
- constructor() {
1226
- super(...arguments);
1227
- this._meta = /* @__PURE__ */ new Map();
1228
- }
1229
- meta(...args) {
1230
- const [key, value] = args;
1231
- if (args.length === 0)
1232
- return Object.fromEntries(this._meta);
1233
- else if (args.length === 1)
1234
- return this._meta.get(key);
1235
- else if (value === void 0 || value === null)
1236
- this._meta.delete(key);
1237
- else
1238
- this._meta.set(key, value);
1239
- }
1240
- /* determine meta store */
1241
- metaStore(extra) {
1242
- const extraEmpty = extra === void 0 || Object.keys(extra).length === 0;
1243
- if (this._meta.size === 0 && extraEmpty)
1244
- return void 0;
1245
- else if (this._meta.size > 0 && extraEmpty)
1246
- return Object.fromEntries(this._meta);
1247
- else if (this._meta.size === 0 && !extraEmpty)
1248
- return extra;
1249
- else
1250
- return { ...Object.fromEntries(this._meta), ...extra };
1251
- }
1252
- }
1253
- const textEncoder = new TextEncoder();
1254
- class AuthTrait extends MetaTrait {
1255
- constructor() {
1256
- super(...arguments);
1257
- this._credential = null;
1258
- this._tokens = /* @__PURE__ */ new Set();
1259
- }
1260
- /* store server-side secret credential */
1261
- credential(credential) {
1262
- if (credential.length === 0)
1263
- throw new Error("credential must not be empty");
1264
- const pass = textEncoder.encode(credential);
1265
- const salt = textEncoder.encode("mqtt-plus");
1266
- this._credential = pbkdf2.deriveKey(sha256.SHA256, pass, salt, 6e5, 32);
1267
- }
1268
- /* issue client-side token on server-side */
1269
- async issue(payload) {
1270
- if (this._credential === null)
1271
- throw new Error("credential has to be provided before issuing tokens");
1272
- if (payload.roles.length === 0)
1273
- throw new Error("payload.roles must be a non-empty array");
1274
- if (payload.roles.length > 64)
1275
- throw new Error("payload.roles must not exceed 64 roles");
1276
- const jwt = new SignJWT(payload);
1277
- jwt.setProtectedHeader({ alg: "HS256", typ: "JWT" });
1278
- const token = await jwt.sign(this._credential);
1279
- return token;
1280
- }
1281
- authenticate(token, remove) {
1282
- if (token === void 0) {
1283
- const tokens = Array.from(this._tokens).filter((token2) => token2.length <= 8192).slice(0, 8);
1284
- return tokens.length > 0 ? tokens : void 0;
1285
- } else if (remove === true)
1286
- this._tokens.delete(token);
1287
- else {
1288
- if (token.length > 8192)
1289
- throw new Error("token must not exceed 8192 characters");
1290
- if (!this._tokens.has(token) && this._tokens.size >= 8)
1291
- throw new Error("at most 8 tokens can be authenticated at once");
1292
- this._tokens.add(token);
1293
- }
1294
- }
1295
- /* validate client-side token on server-side */
1296
- async validateToken(token) {
1297
- if (this._credential === null)
1298
- throw new Error("credential has to be provided before validating tokens");
1299
- const result = await jwtVerify(token, this._credential).catch(() => null);
1300
- return result?.payload ?? null;
1301
- }
1302
- /* check whether request is authenticated */
1303
- async authenticated(clientId, tokens, option) {
1304
- let authenticated = false;
1305
- let mode;
1306
- let roles;
1307
- if (typeof option === "string") {
1308
- mode = "require";
1309
- roles = [option];
1310
- } else {
1311
- mode = option.mode;
1312
- roles = option.roles;
1313
- }
1314
- if (tokens !== void 0) {
1315
- for (const token of tokens.slice(0, 8)) {
1316
- if (token.length > 8192)
1317
- continue;
1318
- const payload = await this.validateToken(token);
1319
- if (payload === null)
1320
- continue;
1321
- if (payload.id && payload.id !== clientId)
1322
- continue;
1323
- if (!Array.isArray(payload.roles))
1324
- continue;
1325
- if (payload.roles.length > 64)
1326
- continue;
1327
- for (const role of roles) {
1328
- if (payload.roles.includes(role)) {
1329
- authenticated = true;
1330
- break;
1331
- }
1332
- }
1333
- if (authenticated)
1334
- break;
1335
- }
1336
- }
1337
- if (!authenticated && mode === "optional")
1338
- authenticated = true;
1339
- return authenticated;
1340
- }
1341
- }
1342
- class EventTrait extends AuthTrait {
1343
- async event(nameOrConfig, ...args) {
1344
- let name;
1345
- let callback;
1346
- let options = {};
1347
- let share = this.options.share;
1348
- let auth;
1349
- if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1350
- name = nameOrConfig.name;
1351
- callback = nameOrConfig.callback;
1352
- options = nameOrConfig.options ?? {};
1353
- share = nameOrConfig.share ?? this.options.share;
1354
- auth = nameOrConfig.auth;
1355
- } else {
1356
- name = nameOrConfig;
1357
- callback = args[0];
1358
- }
1359
- const spool = new Spool();
1360
- if (this.onRequest.has(`event-emission:${name}`))
1361
- throw new Error(`event: event "${name}" already registered`);
1362
- const topicS = share !== "" ? `$share/${share}/${name}` : name;
1363
- const topicB = this.options.topicMake(topicS, "event-emission");
1364
- const topicD = this.options.topicMake(name, "event-emission", this.options.id);
1365
- this.onRequest.set(`event-emission:${name}`, async (request, topicName) => {
1366
- const senderId = request.sender;
1367
- if (senderId === void 0 || senderId === "")
1368
- throw new Error("invalid request: missing sender");
1369
- const params = request.params ?? [];
1370
- const info = { sender: senderId };
1371
- if (request.receiver)
1372
- info.receiver = request.receiver;
1373
- if (request.meta)
1374
- info.meta = request.meta;
1375
- try {
1376
- if (topicName !== request.name)
1377
- throw new Error(`event name mismatch (topic: "${topicName}", payload: "${request.name}")`);
1378
- if (auth)
1379
- info.authenticated = await this.authenticated(senderId, request.auth, auth);
1380
- if (info.authenticated !== void 0 && !info.authenticated)
1381
- throw new Error(`event "${name}" failed authentication`);
1382
- await callback(...params, info);
1383
- } catch (result) {
1384
- const error = ensureError(result);
1385
- this.error(error, `handler for event "${name}" failed`);
1386
- }
1387
- });
1388
- spool.roll(() => {
1389
- this.onRequest.delete(`event-emission:${name}`);
1390
- });
1391
- await this.subscribeTopicAndSpool(spool, topicB, options);
1392
- await this.subscribeTopicAndSpool(spool, topicD, options);
1393
- return this.makeRegistration(spool, "event", name, `event-emission:${name}`);
1394
- }
1395
- emit(nameOrConfig, ...args) {
1396
- let name;
1397
- let params;
1398
- let receiver;
1399
- let options = {};
1400
- let meta;
1401
- let dry;
1402
- if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1403
- name = nameOrConfig.name;
1404
- params = nameOrConfig.params;
1405
- receiver = nameOrConfig.receiver;
1406
- options = nameOrConfig.options ?? {};
1407
- meta = nameOrConfig.meta;
1408
- dry = nameOrConfig.dry;
1409
- } else {
1410
- name = nameOrConfig;
1411
- params = args;
1412
- }
1413
- const requestId = nanoid();
1414
- const auth = this.authenticate();
1415
- const metaStore = this.metaStore(meta);
1416
- const request = this.msg.makeEventEmission(requestId, name, params, this.options.id, receiver, auth, metaStore);
1417
- const message = this.codec.encode(request);
1418
- const topic = this.options.topicMake(name, "event-emission", receiver);
1419
- if (dry)
1420
- return { topic, payload: message, options: { qos: 2, ...options } };
1421
- else
1422
- this.publishToTopic(topic, message, { qos: 2, ...options }).catch((err) => {
1423
- this.error(err, `emitting event "${name}" failed`);
1424
- });
1425
- }
1426
- }
1427
- class ServiceTrait extends EventTrait {
1428
- async service(nameOrConfig, ...args) {
1429
- let name;
1430
- let callback;
1431
- let options = {};
1432
- let share = this.options.share;
1433
- let auth;
1434
- if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1435
- name = nameOrConfig.name;
1436
- callback = nameOrConfig.callback;
1437
- options = nameOrConfig.options ?? {};
1438
- share = nameOrConfig.share ?? this.options.share;
1439
- auth = nameOrConfig.auth;
1440
- } else {
1441
- name = nameOrConfig;
1442
- callback = args[0];
1443
- }
1444
- const spool = new Spool();
1445
- if (this.onRequest.has(`service-call-request:${name}`))
1446
- throw new Error(`service: service "${name}" already registered`);
1447
- const topicS = share !== "" ? `$share/${share}/${name}` : name;
1448
- const topicB = this.options.topicMake(topicS, "service-call-request");
1449
- const topicD = this.options.topicMake(name, "service-call-request", this.options.id);
1450
- this.onRequest.set(`service-call-request:${name}`, async (request, topicName) => {
1451
- const requestId = request.id;
1452
- const senderId = request.sender;
1453
- if (senderId === void 0 || senderId === "")
1454
- throw new Error("invalid request: missing sender");
1455
- const params = request.params ?? [];
1456
- const info = { sender: senderId };
1457
- if (request.receiver)
1458
- info.receiver = request.receiver;
1459
- if (request.meta)
1460
- info.meta = request.meta;
1461
- try {
1462
- if (topicName !== request.name)
1463
- throw new Error(`service name mismatch (topic: "${topicName}", payload: "${request.name}")`);
1464
- if (auth)
1465
- info.authenticated = await this.authenticated(senderId, request.auth, auth);
1466
- if (info.authenticated !== void 0 && !info.authenticated)
1467
- throw new Error(`service "${name}" failed authentication`);
1468
- const result = await callback(...params, info);
1469
- const rpcResponse = this.msg.makeServiceCallResponse(requestId, result, void 0, this.options.id, senderId);
1470
- const encoded = this.codec.encode(rpcResponse);
1471
- const topic = this.options.topicMake(name, "service-call-response", senderId);
1472
- await this.publishToTopic(topic, encoded, { qos: options.qos ?? 2 });
1473
- } catch (err) {
1474
- const error = ensureError(err);
1475
- this.error(error, `handler for service "${name}" failed`);
1476
- const rpcResponse = this.msg.makeServiceCallResponse(requestId, void 0, error.message, this.options.id, senderId);
1477
- try {
1478
- const encoded = this.codec.encode(rpcResponse);
1479
- const topic = this.options.topicMake(name, "service-call-response", senderId);
1480
- await this.publishToTopic(topic, encoded, { qos: options.qos ?? 2 });
1481
- } catch (err2) {
1482
- this.error(ensureError(err2), `sending error response for service "${name}" failed`);
1483
- }
1484
- }
1485
- });
1486
- spool.roll(() => {
1487
- this.onRequest.delete(`service-call-request:${name}`);
1488
- });
1489
- await this.subscribeTopicAndSpool(spool, topicB, options);
1490
- await this.subscribeTopicAndSpool(spool, topicD, options);
1491
- return this.makeRegistration(spool, "service", name, `service-call-request:${name}`);
1492
- }
1493
- async call(nameOrConfig, ...args) {
1494
- let name;
1495
- let params;
1496
- let receiver;
1497
- let options = {};
1498
- let meta;
1499
- if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1500
- name = nameOrConfig.name;
1501
- params = nameOrConfig.params;
1502
- receiver = nameOrConfig.receiver;
1503
- options = nameOrConfig.options ?? {};
1504
- meta = nameOrConfig.meta;
1505
- } else {
1506
- name = nameOrConfig;
1507
- params = args;
1508
- }
1509
- const spool = new Spool();
1510
- let requestId = nanoid();
1511
- while (this.onResponse.has(`service-call-response:${requestId}`))
1512
- requestId = nanoid();
1513
- const responseTopic = this.options.topicMake(name, "service-call-response", this.options.id);
1514
- await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
1515
- const timerId = `service-call:${requestId}`;
1516
- let rejectPromise;
1517
- const promise = new Promise((resolve, reject) => {
1518
- rejectPromise = reject;
1519
- this.timerRefresh(timerId, async () => {
1520
- await spool.unroll();
1521
- reject(new Error("communication timeout"));
1522
- });
1523
- spool.roll(() => {
1524
- this.timerClear(timerId);
1525
- });
1526
- this.onResponse.set(`service-call-response:${requestId}`, async (response) => {
1527
- await spool.unroll();
1528
- if (response.error !== void 0)
1529
- reject(new Error(response.error));
1530
- else
1531
- resolve(response.result);
1532
- });
1533
- spool.roll(() => {
1534
- this.onResponse.delete(`service-call-response:${requestId}`);
1535
- });
1536
- });
1537
- const auth = this.authenticate();
1538
- const metaStore = this.metaStore(meta);
1539
- const request = this.msg.makeServiceCallRequest(requestId, name, params, this.options.id, receiver, auth, metaStore);
1540
- const message = this.codec.encode(request);
1541
- const topic = this.options.topicMake(name, "service-call-request", receiver);
1542
- try {
1543
- await run(`publish service request as MQTT message to topic "${topic}"`, spool, () => this.publishToTopic(topic, message, { qos: 2, ...options }));
1544
- } catch (err) {
1545
- rejectPromise(err);
1546
- return promise;
1547
- }
1548
- return promise;
1549
- }
1550
- }
1551
- class SourceTrait extends ServiceTrait {
1552
- constructor() {
1553
- super(...arguments);
1554
- this.sourceCreditGates = /* @__PURE__ */ new Map();
1555
- this.sourceControllers = /* @__PURE__ */ new Map();
1556
- }
1557
- /* destroy source trait */
1558
- async destroy() {
1559
- for (const controller of this.sourceControllers.values())
1560
- controller.abort(new Error("source destroyed"));
1561
- this.sourceControllers.clear();
1562
- for (const gate of this.sourceCreditGates.values())
1563
- gate.abort();
1564
- this.sourceCreditGates.clear();
1565
- await super.destroy();
1566
- }
1567
- async source(nameOrConfig, ...args) {
1568
- let name;
1569
- let callback;
1570
- let options = {};
1571
- let share = this.options.share;
1572
- let auth;
1573
- if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1574
- name = nameOrConfig.name;
1575
- callback = nameOrConfig.callback;
1576
- options = nameOrConfig.options ?? {};
1577
- share = nameOrConfig.share ?? this.options.share;
1578
- auth = nameOrConfig.auth;
1579
- } else {
1580
- name = nameOrConfig;
1581
- callback = args[0];
1582
- }
1583
- const spool = new Spool();
1584
- if (this.onRequest.has(`source-fetch-request:${name}`))
1585
- throw new Error(`source: source "${name}" already registered`);
1586
- const topicS = share !== "" ? `$share/${share}/${name}` : name;
1587
- const topicReqB = this.options.topicMake(topicS, "source-fetch-request");
1588
- const topicReqD = this.options.topicMake(name, "source-fetch-request", this.options.id);
1589
- this.onRequest.set(`source-fetch-request:${name}`, async (request, topicName) => {
1590
- const requestId = request.id;
1591
- const params = request.params ?? [];
1592
- const sender = request.sender;
1593
- if (sender === void 0 || sender === "")
1594
- throw new Error("invalid request: missing sender");
1595
- const receiver = request.receiver;
1596
- const info = { sender };
1597
- if (receiver)
1598
- info.receiver = receiver;
1599
- if (request.meta)
1600
- info.meta = request.meta;
1601
- const responseTopic = this.options.topicMake(name, "source-fetch-response", sender);
1602
- const sendResponse = async (error) => {
1603
- const metaStore = this.metaStore(info.meta);
1604
- const response = this.msg.makeSourceFetchResponse(requestId, name, error, this.options.id, sender, metaStore);
1605
- const message = this.codec.encode(response);
1606
- await this.publishToTopic(responseTopic, message, { qos: options.qos ?? 2 });
1607
- };
1608
- const abortController = new AbortController();
1609
- this.sourceControllers.set(requestId, abortController);
1610
- const abortSignal = abortController.signal;
1611
- abortSignal.addEventListener("abort", () => {
1612
- if (info.stream instanceof Readable && !info.stream.destroyed)
1613
- info.stream.destroy(ensureError(abortSignal.reason));
1614
- }, { once: true });
1615
- const sourceTimerId = `source-fetch-send:${requestId}`;
1616
- const refreshSourceTimeout = () => this.timerRefresh(sourceTimerId, () => {
1617
- const error = new Error(`source fetch "${name}" timed out`);
1618
- abortController.abort(error);
1619
- const gate = this.sourceCreditGates.get(requestId);
1620
- if (gate !== void 0) {
1621
- gate.abort();
1622
- this.sourceCreditGates.delete(requestId);
1623
- }
1624
- this.sourceControllers.delete(requestId);
1625
- this.onResponse.delete(`source-fetch-credit:${requestId}`);
1626
- });
1627
- const clearSourceTimeout = () => this.timerClear(sourceTimerId);
1628
- refreshSourceTimeout();
1629
- const sendChunk = async (chunk, error, final) => {
1630
- refreshSourceTimeout();
1631
- const chunkMsg = this.msg.makeSourceFetchChunk(requestId, name, chunk, error, final, this.options.id, sender);
1632
- const message = this.codec.encode(chunkMsg);
1633
- await this.publishToTopic(responseTopic, message, { qos: options.qos ?? 2 });
1634
- };
1635
- let ackSent = false;
1636
- let creditGate;
1637
- try {
1638
- if (topicName !== request.name)
1639
- throw new Error(`source name mismatch (topic: "${topicName}", payload: "${request.name}")`);
1640
- if (auth)
1641
- info.authenticated = await this.authenticated(sender, request.auth, auth);
1642
- if (info.authenticated !== void 0 && !info.authenticated)
1643
- throw new Error(`source "${name}" failed authentication`);
1644
- const initialCredit = request.credit;
1645
- creditGate = initialCredit !== void 0 && initialCredit > 0 ? new CreditGate(initialCredit) : void 0;
1646
- if (creditGate)
1647
- this.sourceCreditGates.set(requestId, creditGate);
1648
- this.onResponse.set(`source-fetch-credit:${requestId}`, (creditParsed) => {
1649
- if (creditParsed.credit === 0) {
1650
- abortController.abort(new Error(`source fetch "${name}" cancelled by fetcher`));
1651
- return;
1652
- }
1653
- if (creditGate) {
1654
- creditGate.replenish(creditParsed.credit);
1655
- refreshSourceTimeout();
1656
- }
1657
- });
1658
- await callback(...params, info);
1659
- if (!(info.stream instanceof Readable) && !(info.buffer instanceof Promise))
1660
- throw new Error("handler did not provide data via info.stream or info.buffer fields");
1661
- if (info.stream instanceof Readable && info.buffer instanceof Promise)
1662
- throw new Error("handler has set both info.stream and info.buffer fields");
1663
- await sendResponse();
1664
- ackSent = true;
1665
- if (info.stream instanceof Readable)
1666
- await sendStreamAsChunks(info.stream, this.options.chunkSize, sendChunk, creditGate, abortSignal);
1667
- else if (info.buffer instanceof Promise)
1668
- await sendBufferAsChunks(await info.buffer, this.options.chunkSize, sendChunk, creditGate, abortSignal);
1669
- } catch (err) {
1670
- const error = ensureError(err, `handler for source "${name}" failed`);
1671
- abortController.abort(error);
1672
- this.error(error);
1673
- if (ackSent)
1674
- await sendChunk(void 0, error.message, true).catch(() => {
1675
- });
1676
- else
1677
- await sendResponse(error.message).catch(() => {
1678
- });
1679
- } finally {
1680
- clearSourceTimeout();
1681
- if (creditGate) {
1682
- creditGate.abort();
1683
- this.sourceCreditGates.delete(requestId);
1684
- }
1685
- this.sourceControllers.delete(requestId);
1686
- this.onResponse.delete(`source-fetch-credit:${requestId}`);
1687
- }
1688
- });
1689
- spool.roll(() => {
1690
- this.onRequest.delete(`source-fetch-request:${name}`);
1691
- });
1692
- await this.subscribeTopicAndSpool(spool, topicReqB, options);
1693
- await this.subscribeTopicAndSpool(spool, topicReqD, options);
1694
- return this.makeRegistration(spool, "source", name, `source-fetch-request:${name}`);
1695
- }
1696
- async fetch(nameOrConfig, ...args) {
1697
- let name;
1698
- let params;
1699
- let receiver;
1700
- let options = {};
1701
- let meta;
1702
- if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1703
- name = nameOrConfig.name;
1704
- params = nameOrConfig.params;
1705
- receiver = nameOrConfig.receiver;
1706
- options = nameOrConfig.options ?? {};
1707
- meta = nameOrConfig.meta;
1708
- } else {
1709
- name = nameOrConfig;
1710
- params = args;
1711
- }
1712
- const spool = new Spool();
1713
- let requestId = nanoid();
1714
- while (this.onResponse.has(`source-fetch-response:${requestId}`) || this.onResponse.has(`source-fetch-chunk:${requestId}`))
1715
- requestId = nanoid();
1716
- const responseTopic = this.options.topicMake(name, "source-fetch-response", this.options.id);
1717
- await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
1718
- const chunkCredit = this.options.chunkCredit;
1719
- let chunksReceived = 0;
1720
- let creditGranted = chunkCredit;
1721
- let serverId;
1722
- let streamEnded = false;
1723
- const timerId = `source-fetch:${requestId}`;
1724
- let stream = void 0;
1725
- const refreshTimeout = () => {
1726
- this.timerRefresh(timerId, () => {
1727
- stream?.destroy(new Error("communication timeout"));
1728
- spool.unroll();
1729
- });
1730
- };
1731
- spool.roll(() => {
1732
- this.timerClear(timerId);
1733
- });
1734
- stream = new Readable({
1735
- highWaterMark: chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16 * 1024,
1736
- read: (_size) => {
1737
- if (chunkCredit <= 0 || !this.onResponse.has(`source-fetch-response:${requestId}`))
1738
- return;
1739
- const targetId = serverId ?? receiver;
1740
- if (!targetId)
1741
- return;
1742
- const creditToGrant = Math.max(0, chunksReceived + chunkCredit - creditGranted);
1743
- if (creditToGrant > 0) {
1744
- creditGranted += creditToGrant;
1745
- const creditMsg = this.msg.makeSourceFetchCredit(requestId, name, creditToGrant, this.options.id, targetId);
1746
- const encoded = this.codec.encode(creditMsg);
1747
- const creditTopic = this.options.topicMake(name, "source-fetch-request", targetId);
1748
- this.publishToTopic(creditTopic, encoded, { qos: options.qos ?? 2 }).catch((err) => {
1749
- this.error(err, `sending credit for fetch "${name}" failed`);
1750
- });
1751
- refreshTimeout();
1752
- }
1753
- }
1754
- });
1755
- const buffer = streamToBuffer(stream);
1756
- let metaResolve;
1757
- const metaP = new Promise((resolve) => {
1758
- metaResolve = resolve;
1759
- });
1760
- spool.roll(() => {
1761
- metaResolve(void 0);
1762
- });
1763
- refreshTimeout();
1764
- let cancelled = false;
1765
- const cancelAndUnroll = () => {
1766
- if (!cancelled && !streamEnded) {
1767
- cancelled = true;
1768
- const targetId = serverId ?? receiver;
1769
- if (targetId) {
1770
- const cancelMsg = this.msg.makeSourceFetchCredit(requestId, name, 0, this.options.id, targetId);
1771
- const encoded = this.codec.encode(cancelMsg);
1772
- const cancelTopic = this.options.topicMake(name, "source-fetch-request", targetId);
1773
- this.publishToTopic(cancelTopic, encoded, { qos: options.qos ?? 2 }).catch(() => {
1774
- });
1775
- }
1776
- }
1777
- spool.unroll();
1778
- };
1779
- stream.once("close", cancelAndUnroll);
1780
- stream.once("error", cancelAndUnroll);
1781
- this.onResponse.set(`source-fetch-response:${requestId}`, (response) => {
1782
- if (streamEnded)
1783
- return;
1784
- if (response.name !== name) {
1785
- streamEnded = true;
1786
- stream.destroy(new Error(`source name mismatch (expected "${name}", got "${response.name}")`));
1787
- spool.unroll();
1788
- return;
1789
- }
1790
- if (response.sender)
1791
- serverId = response.sender;
1792
- if (response.error) {
1793
- streamEnded = true;
1794
- stream.destroy(new Error(response.error));
1795
- spool.unroll();
1796
- } else {
1797
- metaResolve(response.meta);
1798
- refreshTimeout();
1799
- }
1800
- });
1801
- this.onResponse.set(`source-fetch-chunk:${requestId}`, (response) => {
1802
- if (streamEnded)
1803
- return;
1804
- if (response.error) {
1805
- streamEnded = true;
1806
- stream.destroy(new Error(response.error));
1807
- spool.unroll();
1808
- } else {
1809
- refreshTimeout();
1810
- if (response.chunk !== void 0) {
1811
- chunksReceived++;
1812
- stream.push(response.chunk);
1813
- }
1814
- if (response.final) {
1815
- streamEnded = true;
1816
- stream.push(null);
1817
- spool.unroll();
1818
- }
1819
- }
1820
- });
1821
- spool.roll(() => {
1822
- this.onResponse.delete(`source-fetch-response:${requestId}`);
1823
- this.onResponse.delete(`source-fetch-chunk:${requestId}`);
1824
- });
1825
- const auth = this.authenticate();
1826
- const metaStore = this.metaStore(meta);
1827
- const credit = chunkCredit > 0 ? chunkCredit : void 0;
1828
- const request = this.msg.makeSourceFetchRequest(requestId, name, params, this.options.id, receiver, auth, metaStore, credit);
1829
- const message = this.codec.encode(request);
1830
- const topic = this.options.topicMake(name, "source-fetch-request", receiver);
1831
- run(`publish fetch request as MQTT message to topic "${topic}"`, () => this.publishToTopic(topic, message, { qos: 2, ...options })).catch((err) => {
1832
- stream.destroy(ensureError(err));
1833
- });
1834
- const result = { stream, buffer, meta: metaP };
1835
- makeMutuallyExclusiveFields(result, "stream", "buffer");
1836
- return result;
1837
- }
1838
- }
1839
- class SinkTrait extends SourceTrait {
1840
- constructor() {
1841
- super(...arguments);
1842
- this.pushStreams = /* @__PURE__ */ new Map();
1843
- this.pushSpools = /* @__PURE__ */ new Map();
1844
- }
1845
- /* destroy trait */
1846
- async destroy() {
1847
- for (const stream of this.pushStreams.values())
1848
- stream.destroy(new Error("sink destroyed"));
1849
- this.pushStreams.clear();
1850
- for (const spool of this.pushSpools.values())
1851
- await spool.unroll();
1852
- this.pushSpools.clear();
1853
- await super.destroy();
1854
- }
1855
- async sink(nameOrConfig, ...args) {
1856
- let name;
1857
- let callback;
1858
- let options = {};
1859
- let share = this.options.share;
1860
- let auth;
1861
- if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1862
- name = nameOrConfig.name;
1863
- callback = nameOrConfig.callback;
1864
- options = nameOrConfig.options ?? {};
1865
- share = nameOrConfig.share ?? this.options.share;
1866
- auth = nameOrConfig.auth;
1867
- } else {
1868
- name = nameOrConfig;
1869
- callback = args[0];
1870
- }
1871
- const spool = new Spool();
1872
- if (this.onRequest.has(`sink-push-request:${name}`))
1873
- throw new Error(`sink: sink "${name}" already registered`);
1874
- const topicS = share !== "" ? `$share/${share}/${name}` : name;
1875
- const topicReqB = this.options.topicMake(topicS, "sink-push-request");
1876
- const topicReqD = this.options.topicMake(name, "sink-push-request", this.options.id);
1877
- this.onRequest.set(`sink-push-request:${name}`, async (request, topicName) => {
1878
- const requestId = request.id;
1879
- const params = request.params ?? [];
1880
- const sender = request.sender;
1881
- if (sender === void 0 || sender === "")
1882
- throw new Error("invalid request: missing sender");
1883
- const receiver = request.receiver;
1884
- const responseTopic = this.options.topicMake(name, "sink-push-response", sender);
1885
- const chunkCredit = this.options.chunkCredit;
1886
- const sendResponse = async (error, withCredit = false) => {
1887
- const credit = error === void 0 && withCredit && chunkCredit > 0 ? chunkCredit : void 0;
1888
- const response = this.msg.makeSinkPushResponse(requestId, name, error, this.options.id, sender, credit);
1889
- const message = this.codec.encode(response);
1890
- await this.publishToTopic(responseTopic, message, { qos: options.qos ?? 2 });
1891
- };
1892
- const reqSpool = new Spool();
1893
- this.pushSpools.set(requestId, reqSpool);
1894
- reqSpool.roll(() => {
1895
- this.pushSpools.delete(requestId);
1896
- });
1897
- try {
1898
- if (topicName !== request.name)
1899
- throw new Error(`sink name mismatch (topic: "${topicName}", payload: "${request.name}")`);
1900
- let authenticated = void 0;
1901
- if (auth)
1902
- authenticated = await this.authenticated(sender, request.auth, auth);
1903
- if (authenticated !== void 0 && !authenticated)
1904
- throw new Error(`sink "${name}" failed authentication`);
1905
- const creditState = chunkCredit > 0 ? {
1906
- chunksReceived: 0,
1907
- creditGranted: chunkCredit
1908
- } : void 0;
1909
- const pushTimerId = `sink-push-recv:${requestId}`;
1910
- const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
1911
- const stream = this.pushStreams.get(requestId);
1912
- if (stream !== void 0)
1913
- stream.destroy(new Error("push stream timeout"));
1914
- const spool2 = this.pushSpools.get(requestId);
1915
- spool2?.unroll();
1916
- });
1917
- const clearPushTimeout = () => this.timerClear(pushTimerId);
1918
- const readable = new Readable({
1919
- highWaterMark: chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16 * 1024,
1920
- read: (_size) => {
1921
- if (!creditState || !this.pushSpools.has(requestId))
1922
- return;
1923
- const creditToGrant = Math.max(0, creditState.chunksReceived + chunkCredit - creditState.creditGranted);
1924
- if (creditToGrant > 0) {
1925
- creditState.creditGranted += creditToGrant;
1926
- const creditMsg = this.msg.makeSinkPushCredit(requestId, name, creditToGrant, this.options.id, sender);
1927
- const encoded = this.codec.encode(creditMsg);
1928
- this.publishToTopic(responseTopic, encoded, { qos: options.qos ?? 2 }).catch((err) => {
1929
- this.error(err, `sending credit for push "${name}" failed`);
1930
- });
1931
- refreshPushTimeout();
1932
- }
1933
- }
1934
- });
1935
- this.pushStreams.set(requestId, readable);
1936
- reqSpool.roll(() => {
1937
- this.pushStreams.delete(requestId);
1938
- });
1939
- readable.once("error", () => {
1940
- });
1941
- let streamEnded = false;
1942
- this.onResponse.set(`sink-push-chunk:${requestId}`, async (chunkParsed) => {
1943
- if (streamEnded)
1944
- return;
1945
- if (chunkParsed.error !== void 0) {
1946
- streamEnded = true;
1947
- readable.destroy(new Error(chunkParsed.error));
1948
- } else {
1949
- refreshPushTimeout();
1950
- if (chunkParsed.chunk !== void 0) {
1951
- if (creditState)
1952
- creditState.chunksReceived++;
1953
- readable.push(chunkParsed.chunk);
1954
- }
1955
- if (chunkParsed.final) {
1956
- streamEnded = true;
1957
- readable.push(null);
1958
- }
1959
- }
1960
- });
1961
- reqSpool.roll(() => {
1962
- this.onResponse.delete(`sink-push-chunk:${requestId}`);
1963
- });
1964
- refreshPushTimeout();
1965
- reqSpool.roll(() => {
1966
- clearPushTimeout();
1967
- });
1968
- const promise = streamToBuffer(readable);
1969
- const streamDone = new Promise((resolve, reject) => {
1970
- readable.once("end", () => {
1971
- resolve();
1972
- });
1973
- readable.once("error", (err) => {
1974
- reject(err);
1975
- });
1976
- });
1977
- streamDone.catch(() => {
1978
- });
1979
- const info = {
1980
- sender,
1981
- stream: readable,
1982
- buffer: promise
1983
- };
1984
- if (receiver)
1985
- info.receiver = receiver;
1986
- if (authenticated !== void 0)
1987
- info.authenticated = authenticated;
1988
- if (request.meta)
1989
- info.meta = request.meta;
1990
- makeMutuallyExclusiveFields(info, "stream", "buffer");
1991
- await sendResponse(void 0, true);
1992
- await callback(...params, info);
1993
- await streamDone;
1994
- try {
1995
- await sendResponse();
1996
- } catch (err2) {
1997
- this.error(ensureError(err2), `sending terminal response for sink "${name}" failed`);
1998
- }
1999
- } catch (err) {
2000
- const error = ensureError(err, `handler for sink "${name}" failed`);
2001
- this.error(error);
2002
- await sendResponse(error.message).catch(() => {
2003
- });
2004
- } finally {
2005
- const stream = this.pushStreams.get(requestId);
2006
- if (stream !== void 0 && !stream.destroyed)
2007
- stream.destroy();
2008
- await reqSpool.unroll();
2009
- }
2010
- });
2011
- spool.roll(() => {
2012
- this.onRequest.delete(`sink-push-request:${name}`);
2013
- });
2014
- await this.subscribeTopicAndSpool(spool, topicReqB, options);
2015
- await this.subscribeTopicAndSpool(spool, topicReqD, options);
2016
- return this.makeRegistration(spool, "sink", name, `sink-push-request:${name}`);
2017
- }
2018
- async push(nameOrConfig, ...args) {
2019
- let name;
2020
- let data;
2021
- let params;
2022
- let receiver;
2023
- let options = {};
2024
- let meta;
2025
- if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
2026
- name = nameOrConfig.name;
2027
- data = nameOrConfig.data;
2028
- params = nameOrConfig.params;
2029
- receiver = nameOrConfig.receiver;
2030
- options = nameOrConfig.options ?? {};
2031
- meta = nameOrConfig.meta;
2032
- } else {
2033
- name = nameOrConfig;
2034
- data = args[0];
2035
- params = args.slice(1);
2036
- }
2037
- if (!(data instanceof Readable) && !(data instanceof Uint8Array))
2038
- throw new Error("invalid data type: expected Readable or Uint8Array");
2039
- const spool = new Spool();
2040
- let requestId = nanoid();
2041
- while (this.onResponse.has(`sink-push-response:${requestId}`) || this.onResponse.has(`sink-push-credit:${requestId}`))
2042
- requestId = nanoid();
2043
- const responseTopic = this.options.topicMake(name, "sink-push-response", this.options.id);
2044
- await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
2045
- const abortController = new AbortController();
2046
- const abortSignal = abortController.signal;
2047
- if (data instanceof Readable) {
2048
- const stream = data;
2049
- abortSignal.addEventListener("abort", () => {
2050
- if (!stream.destroyed)
2051
- stream.destroy(ensureError(abortSignal.reason));
2052
- }, { once: true });
2053
- }
2054
- const pushTimerId = `sink-push-send:${requestId}`;
2055
- const refreshTimeout = () => this.timerRefresh(pushTimerId, () => {
2056
- const error = new Error(`push to sink "${name}" timed out`);
2057
- abortController.abort(error);
2058
- spool.unroll();
2059
- });
2060
- spool.roll(() => {
2061
- this.timerClear(pushTimerId);
2062
- });
2063
- refreshTimeout();
2064
- let initialCredit;
2065
- let creditGate;
2066
- let remoteError = false;
2067
- let pushAcked = false;
2068
- let pushFinalized = false;
2069
- let pushFinalizeResolve;
2070
- let pushFinalizeReject;
2071
- const pushFinalize = new Promise((resolve, reject) => {
2072
- pushFinalizeResolve = resolve;
2073
- pushFinalizeReject = reject;
2074
- });
2075
- pushFinalize.catch(() => {
2076
- });
2077
- try {
2078
- await new Promise((resolve, reject) => {
2079
- const onAbort = () => {
2080
- reject(abortSignal.reason);
2081
- };
2082
- abortSignal.addEventListener("abort", onAbort, { once: true });
2083
- spool.roll(() => {
2084
- abortSignal.removeEventListener("abort", onAbort);
2085
- });
2086
- this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
2087
- if (response.error)
2088
- reject(new Error(response.error));
2089
- else {
2090
- if (response.sender)
2091
- receiver = response.sender;
2092
- initialCredit = response.credit;
2093
- pushAcked = true;
2094
- resolve();
2095
- }
2096
- });
2097
- spool.roll(() => {
2098
- this.onResponse.delete(`sink-push-response:${requestId}`);
2099
- });
2100
- const auth = this.authenticate();
2101
- const metaStore = this.metaStore(meta);
2102
- const request = this.msg.makeSinkPushRequest(requestId, name, params, this.options.id, receiver, auth, metaStore);
2103
- const message = this.codec.encode(request);
2104
- const requestTopic = this.options.topicMake(name, "sink-push-request", receiver);
2105
- run(`publish push request as MQTT message to topic "${requestTopic}"`, spool, () => this.publishToTopic(requestTopic, message, { qos: 2, ...options })).catch((err) => {
2106
- reject(err);
2107
- });
2108
- });
2109
- this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
2110
- if (response.error) {
2111
- remoteError = true;
2112
- pushFinalizeReject(new Error(response.error));
2113
- abortController.abort(new Error(response.error));
2114
- } else if (pushAcked && !pushFinalized) {
2115
- pushFinalized = true;
2116
- pushFinalizeResolve();
2117
- }
2118
- });
2119
- if (initialCredit !== void 0 && initialCredit > 0)
2120
- creditGate = new CreditGate(initialCredit);
2121
- if (creditGate) {
2122
- const gate = creditGate;
2123
- spool.roll(() => {
2124
- gate.abort();
2125
- });
2126
- this.onResponse.set(`sink-push-credit:${requestId}`, (response) => {
2127
- gate.replenish(response.credit);
2128
- refreshTimeout();
2129
- });
2130
- spool.roll(() => {
2131
- this.onResponse.delete(`sink-push-credit:${requestId}`);
2132
- });
2133
- }
2134
- const chunkTopic = this.options.topicMake(name, "sink-push-request", receiver);
2135
- const sendChunk = async (chunk, error, final) => {
2136
- refreshTimeout();
2137
- const chunkMsg = this.msg.makeSinkPushChunk(requestId, name, chunk, error, final, this.options.id, receiver);
2138
- const message = this.codec.encode(chunkMsg);
2139
- await this.publishToTopic(chunkTopic, message, { qos: 2, ...options });
2140
- };
2141
- if (data instanceof Readable)
2142
- await sendStreamAsChunks(data, this.options.chunkSize, sendChunk, creditGate, abortSignal);
2143
- else if (data instanceof Uint8Array)
2144
- await sendBufferAsChunks(data, this.options.chunkSize, sendChunk, creditGate, abortSignal);
2145
- if (!pushFinalized) {
2146
- await new Promise((resolve, reject) => {
2147
- const onAbort = () => {
2148
- reject(abortSignal.reason);
2149
- };
2150
- abortSignal.addEventListener("abort", onAbort, { once: true });
2151
- pushFinalize.then(resolve, reject).finally(() => {
2152
- abortSignal.removeEventListener("abort", onAbort);
2153
- });
2154
- });
2155
- }
2156
- } catch (err) {
2157
- const error = ensureError(err);
2158
- abortController.abort(error);
2159
- if (pushAcked && !remoteError) {
2160
- const chunkTopic = this.options.topicMake(name, "sink-push-request", receiver);
2161
- const chunkMsg = this.msg.makeSinkPushChunk(requestId, name, void 0, error.message, true, this.options.id, receiver);
2162
- const message = this.codec.encode(chunkMsg);
2163
- await this.publishToTopic(chunkTopic, message, { qos: 2, ...options }).catch(() => {
2164
- });
2165
- }
2166
- throw err;
2167
- } finally {
2168
- await spool.unroll();
2169
- }
2170
- }
2171
- }
2172
- class MQTTp extends SinkTrait {
2173
- }
2174
- export {
2175
- MQTTp as default
647
+ var Msg = class {
648
+ makeEventEmission(id, name, params, sender, receiver, auth, meta) {
649
+ return new EventEmission(id, name, params, sender, receiver, auth, meta);
650
+ }
651
+ makeServiceCallRequest(id, name, params, sender, receiver, auth, meta) {
652
+ return new ServiceCallRequest(id, name, params, sender, receiver, auth, meta);
653
+ }
654
+ makeServiceCallResponse(id, result, error, sender, receiver) {
655
+ return new ServiceCallResponse(id, result, error, sender, receiver);
656
+ }
657
+ makeSinkPushRequest(id, name, params, sender, receiver, auth, meta) {
658
+ return new SinkPushRequest(id, name, params, sender, receiver, auth, meta);
659
+ }
660
+ makeSinkPushResponse(id, name, error, sender, receiver, credit) {
661
+ return new SinkPushResponse(id, name, error, sender, receiver, credit);
662
+ }
663
+ makeSinkPushChunk(id, name, chunk, error, final, sender, receiver) {
664
+ return new SinkPushChunk(id, name, chunk, error, final, sender, receiver);
665
+ }
666
+ makeSinkPushCredit(id, name, credit, sender, receiver) {
667
+ return new SinkPushCredit(id, name, credit, sender, receiver);
668
+ }
669
+ makeSourceFetchRequest(id, name, params, sender, receiver, auth, meta, credit) {
670
+ return new SourceFetchRequest(id, name, params, sender, receiver, auth, meta, credit);
671
+ }
672
+ makeSourceFetchResponse(id, name, error, sender, receiver, meta) {
673
+ return new SourceFetchResponse(id, name, error, sender, receiver, meta);
674
+ }
675
+ makeSourceFetchChunk(id, name, chunk, error, final, sender, receiver) {
676
+ return new SourceFetchChunk(id, name, chunk, error, final, sender, receiver);
677
+ }
678
+ makeSourceFetchCredit(id, name, credit, sender, receiver) {
679
+ return new SourceFetchCredit(id, name, credit, sender, receiver);
680
+ }
681
+ parse(obj) {
682
+ if (typeof obj !== "object" || obj === null) throw new Error("invalid argument: not an object");
683
+ if (typeof obj.version !== "string") throw new Error("invalid object: missing or invalid \"version\" field");
684
+ const match = obj.version.match(/^MQTT\+\/(\d+\.\d+)$/);
685
+ if ((match !== null ? versionToNum(match[1]) : 0) !== version) throw new Error(`protocol version mismatch (expected version 1.4, got version ${obj.version})`);
686
+ const parseObject = (obj, name, schema) => {
687
+ const res = v.safeParse(schema, obj);
688
+ if (!res.success) {
689
+ const issues = res.issues.map((issue) => issue.message).join("; ");
690
+ throw new Error(`invalid ${name} object: ${issues}`);
691
+ }
692
+ return res.output;
693
+ };
694
+ if (typeof obj.type !== "string") throw new Error("invalid object: missing or invalid \"type\" field");
695
+ if (obj.type === "event-emission") {
696
+ const out = parseObject(obj, "EventEmission", EventEmissionSchema);
697
+ return this.makeEventEmission(out.id, out.name, out.params, out.sender, out.receiver, out.auth, out.meta);
698
+ } else if (obj.type === "service-call-request") {
699
+ const out = parseObject(obj, "ServiceCallRequest", ServiceCallRequestSchema);
700
+ return this.makeServiceCallRequest(out.id, out.name, out.params, out.sender, out.receiver, out.auth, out.meta);
701
+ } else if (obj.type === "service-call-response") {
702
+ const out = parseObject(obj, "ServiceCallResponse", ServiceCallResponseSchema);
703
+ return this.makeServiceCallResponse(out.id, out.result, out.error, out.sender, out.receiver);
704
+ } else if (obj.type === "sink-push-request") {
705
+ const out = parseObject(obj, "SinkPushRequest", SinkPushRequestSchema);
706
+ return this.makeSinkPushRequest(out.id, out.name, out.params, out.sender, out.receiver, out.auth, out.meta);
707
+ } else if (obj.type === "sink-push-response") {
708
+ const out = parseObject(obj, "SinkPushResponse", SinkPushResponseSchema);
709
+ return this.makeSinkPushResponse(out.id, out.name, out.error, out.sender, out.receiver, out.credit);
710
+ } else if (obj.type === "sink-push-chunk") {
711
+ const out = parseObject(obj, "SinkPushChunk", SinkPushChunkSchema);
712
+ return this.makeSinkPushChunk(out.id, out.name, out.chunk, out.error, out.final, out.sender, out.receiver);
713
+ } else if (obj.type === "sink-push-credit") {
714
+ const out = parseObject(obj, "SinkPushCredit", SinkPushCreditSchema);
715
+ return this.makeSinkPushCredit(out.id, out.name, out.credit, out.sender, out.receiver);
716
+ } else if (obj.type === "source-fetch-request") {
717
+ const out = parseObject(obj, "SourceFetchRequest", SourceFetchRequestSchema);
718
+ return this.makeSourceFetchRequest(out.id, out.name, out.params, out.sender, out.receiver, out.auth, out.meta, out.credit);
719
+ } else if (obj.type === "source-fetch-response") {
720
+ const out = parseObject(obj, "SourceFetchResponse", SourceFetchResponseSchema);
721
+ return this.makeSourceFetchResponse(out.id, out.name, out.error, out.sender, out.receiver, out.meta);
722
+ } else if (obj.type === "source-fetch-chunk") {
723
+ const out = parseObject(obj, "SourceFetchChunk", SourceFetchChunkSchema);
724
+ return this.makeSourceFetchChunk(out.id, out.name, out.chunk, out.error, out.final, out.sender, out.receiver);
725
+ } else if (obj.type === "source-fetch-credit") {
726
+ const out = parseObject(obj, "SourceFetchCredit", SourceFetchCreditSchema);
727
+ return this.makeSourceFetchCredit(out.id, out.name, out.credit, out.sender, out.receiver);
728
+ } else throw new Error("invalid object: not of any known type");
729
+ }
730
+ isRequest(msg) {
731
+ return msg instanceof EventEmission || msg instanceof ServiceCallRequest || msg instanceof SourceFetchRequest || msg instanceof SinkPushRequest;
732
+ }
733
+ isResponse(msg) {
734
+ return msg instanceof ServiceCallResponse || msg instanceof SinkPushResponse || msg instanceof SinkPushChunk || msg instanceof SinkPushCredit || msg instanceof SourceFetchResponse || msg instanceof SourceFetchChunk || msg instanceof SourceFetchCredit;
735
+ }
736
+ };
737
+ var MsgTrait = class extends EncodeTrait {
738
+ constructor() {
739
+ super(...arguments);
740
+ this.msg = new Msg();
741
+ }
742
+ };
743
+ //#endregion
744
+ //#region dst-stage1/mqtt-plus-trace.js
745
+ var LogEvent = class {
746
+ constructor(timestamp, level, msg, data) {
747
+ this.timestamp = timestamp;
748
+ this.level = level;
749
+ this.msg = msg;
750
+ this.data = data;
751
+ }
752
+ async resolve() {
753
+ if (this.msg instanceof Promise) this.msg = await this.msg.catch(() => "<resolve-failed>");
754
+ if (this.data) {
755
+ for (const k of Object.keys(this.data)) if (this.data[k] instanceof Promise) this.data[k] = await this.data[k].catch(() => "<resolve-failed>");
756
+ }
757
+ }
758
+ toString() {
759
+ const timestamp = new Date(this.timestamp);
760
+ const time = `${timestamp.getFullYear()}-${(timestamp.getMonth() + 1).toString().padStart(2, "0")}-${timestamp.getDate().toString().padStart(2, "0")} ${timestamp.getHours().toString().padStart(2, "0")}:${timestamp.getMinutes().toString().padStart(2, "0")}:${timestamp.getSeconds().toString().padStart(2, "0")}.${timestamp.getMilliseconds().toString().padStart(3, "0")}`;
761
+ const msg = this.msg instanceof Promise ? "<unresolved>" : this.msg;
762
+ let extra = "";
763
+ if (this.data !== void 0) {
764
+ const data = this.data;
765
+ extra = ` (${Object.keys(data).map((key) => {
766
+ const value = data[key] instanceof Promise ? "<unresolved>" : data[key];
767
+ return `${key}: ${JSONX.stringify(value)}`;
768
+ }).join(", ")})`;
769
+ }
770
+ return `[${time}] ${this.level}: ${msg}${extra}`;
771
+ }
772
+ };
773
+ var TraceTrait = class extends MsgTrait {
774
+ constructor() {
775
+ super(...arguments);
776
+ this._events = new EventEmitter();
777
+ }
778
+ on(...args) {
779
+ this._events.on(...args);
780
+ }
781
+ off(...args) {
782
+ this._events.off(...args);
783
+ }
784
+ emitEvent(...args) {
785
+ if (this._events.listenerCount(args[0]) === 0) return false;
786
+ return this._events.emit(...args);
787
+ }
788
+ log(level, msg, data) {
789
+ const event = new LogEvent(Date.now(), level, msg, data);
790
+ this.emitEvent("log", event);
791
+ }
792
+ error(error, msg) {
793
+ let err = error;
794
+ if (msg !== void 0) err = new Error(`${msg}: ${error.message}`, { cause: error });
795
+ this.emitEvent("error", err);
796
+ this.log("error", err.message);
797
+ }
798
+ };
799
+ //#endregion
800
+ //#region dst-stage1/mqtt-plus-base.js
801
+ var BaseTrait = class extends TraceTrait {
802
+ constructor(mqtt, options = {}) {
803
+ super(options);
804
+ this.onRequest = /* @__PURE__ */ new Map();
805
+ this.onResponse = /* @__PURE__ */ new Map();
806
+ if (mqtt === null) {
807
+ this.log("info", "establishing proxy MQTT client");
808
+ mqtt = new Proxy({}, { get(_target, prop, _receiver) {
809
+ if (prop === "isFakeProxy") return true;
810
+ else if (typeof prop === "string" && [
811
+ "on",
812
+ "off",
813
+ "once"
814
+ ].includes(prop)) return () => {};
815
+ else return () => {
816
+ throw new Error(`Underlying MQTT operation "${String(prop)}" called on a null MQTT client -- only MQTT+ "emit({ ..., dry: true })" is supported in this special operation mode`);
817
+ };
818
+ } });
819
+ }
820
+ this.mqtt = mqtt;
821
+ this.log("info", "hooking into MQTT client");
822
+ this.messageHandler = (topic, message, packet) => {
823
+ let input;
824
+ if (this.options.codec === "json") input = message.toString();
825
+ else if (this.options.codec === "cbor") input = Buffer.isBuffer(message) ? new Uint8Array(message.buffer, message.byteOffset, message.byteLength) : message;
826
+ else throw new Error("invalid codec configured");
827
+ this._onMessage(topic, input, packet);
828
+ };
829
+ this.mqtt.on("message", this.messageHandler);
830
+ }
831
+ async destroy() {
832
+ this.log("info", "un-hooking from MQTT client");
833
+ this.mqtt.off("message", this.messageHandler);
834
+ this.onRequest.clear();
835
+ this.onResponse.clear();
836
+ }
837
+ makeRegistration(spool, kind, name, key) {
838
+ return { destroy: async () => {
839
+ if (!this.onRequest.has(key)) throw new Error(`destroy: ${kind} "${name}" not registered`);
840
+ await spool.unroll(false)?.catch((err) => {
841
+ const error = ensureError(err, `destroy: ${kind} "${name}" failed to cleanup`);
842
+ this.error(error);
843
+ throw error;
844
+ });
845
+ } };
846
+ }
847
+ async subscribeTopic(topic, options = {}) {
848
+ this.log("info", `subscribing to MQTT topic "${topic}"`);
849
+ return new Promise((resolve, reject) => {
850
+ this.mqtt.subscribe(topic, {
851
+ qos: 2,
852
+ ...options
853
+ }, (err, _granted) => {
854
+ if (err) {
855
+ this.error(err, `subscribing to MQTT topic "${topic}" failed`);
856
+ reject(err);
857
+ } else resolve();
858
+ });
859
+ });
860
+ }
861
+ async unsubscribeTopic(topic) {
862
+ this.log("info", `unsubscribing from MQTT topic "${topic}"`);
863
+ return new Promise((resolve, reject) => {
864
+ this.mqtt.unsubscribe(topic, (err, _packet) => {
865
+ if (err) {
866
+ this.error(err, `unsubscribing from MQTT topic "${topic}" failed`);
867
+ reject(err);
868
+ } else resolve();
869
+ });
870
+ });
871
+ }
872
+ async publishToTopic(topic, message, options = {}) {
873
+ if (typeof message === "string") this.log("info", `publishing to MQTT topic "${topic}" (type: string, length: ${message.length} chars)`);
874
+ else this.log("info", `publishing to MQTT topic "${topic}" (type: buffer, length: ${message.byteLength} bytes)`);
875
+ const messageOnDemand = new PLazy((resolve, reject) => {
876
+ let parsed;
877
+ try {
878
+ const payload = this.codec.decode(message);
879
+ parsed = this.msg.parse(payload);
880
+ } catch (err) {
881
+ return reject(err);
882
+ }
883
+ resolve(parsed);
884
+ });
885
+ this.log("debug", `publishing to MQTT topic "${topic}"`, { message: messageOnDemand });
886
+ return new Promise((resolve, reject) => {
887
+ const messageData = typeof message === "string" ? message : Buffer.from(message.buffer, message.byteOffset, message.byteLength);
888
+ this.mqtt.publish(topic, messageData, options, (err) => {
889
+ if (err) {
890
+ this.error(err, `publishing to MQTT topic "${topic}" failed`);
891
+ reject(err);
892
+ } else resolve();
893
+ });
894
+ });
895
+ }
896
+ _onMessage(topic, data, _packet) {
897
+ const topicMatch = this.options.topicMatch(topic);
898
+ if (topicMatch === null) return;
899
+ if (typeof data === "string") this.log("info", `received from MQTT topic "${topic}" (type: string, length: ${data.length} chars)`);
900
+ else this.log("info", `received from MQTT topic "${topic}" (type: buffer, length: ${data.byteLength} bytes)`);
901
+ let payload;
902
+ try {
903
+ payload = this.codec.decode(data);
904
+ } catch (err) {
905
+ this.error(ensureError(err, "failed to parse message into object"));
906
+ return;
907
+ }
908
+ let message;
909
+ try {
910
+ message = this.msg.parse(payload);
911
+ } catch (err) {
912
+ this.error(ensureError(err, "failed to parse object into typed message object"));
913
+ return;
914
+ }
915
+ this.log("debug", `received from MQTT topic "${topic}"`, { message });
916
+ if (this.msg.isRequest(message)) {
917
+ const handler = this.onRequest.get(`${message.type}:${message.name}`);
918
+ if (handler !== void 0) Promise.resolve().then(() => handler(message, topicMatch.name)).catch((err) => {
919
+ this.error(ensureError(err, `dispatching request message from MQTT topic "${topic}" failed`));
920
+ });
921
+ } else if (this.msg.isResponse(message)) {
922
+ const handler = this.onResponse.get(`${message.type}:${message.id}`);
923
+ if (handler !== void 0) Promise.resolve().then(() => handler(message, topicMatch.name)).catch((err) => {
924
+ this.error(ensureError(err, `dispatching response message from MQTT topic "${topic}" failed`));
925
+ });
926
+ }
927
+ }
928
+ };
929
+ //#endregion
930
+ //#region dst-stage1/mqtt-plus-subscription.js
931
+ var RefCountedSubscription = class {
932
+ constructor(subscribeFn, unsubscribeFn, lingerMs = 30 * 1e3) {
933
+ this.subscribeFn = subscribeFn;
934
+ this.unsubscribeFn = unsubscribeFn;
935
+ this.lingerMs = lingerMs;
936
+ this.counts = /* @__PURE__ */ new Map();
937
+ this.pending = /* @__PURE__ */ new Map();
938
+ this.lingers = /* @__PURE__ */ new Map();
939
+ this.unsubbing = /* @__PURE__ */ new Map();
940
+ }
941
+ incrementCount(topic) {
942
+ const count = this.counts.get(topic) ?? 0;
943
+ this.counts.set(topic, count + 1);
944
+ return count;
945
+ }
946
+ decrementCount(topic) {
947
+ const count = this.counts.get(topic);
948
+ if (count !== void 0) if (count <= 1) {
949
+ this.counts.delete(topic);
950
+ return 0;
951
+ } else {
952
+ this.counts.set(topic, count - 1);
953
+ return count - 1;
954
+ }
955
+ }
956
+ async subscribe(topic, options = { qos: 2 }) {
957
+ const count = this.incrementCount(topic);
958
+ const linger = this.lingers.get(topic);
959
+ if (linger) {
960
+ clearTimeout(linger);
961
+ this.lingers.delete(topic);
962
+ return;
963
+ }
964
+ if (count === 0) {
965
+ let resolve;
966
+ let reject;
967
+ const deferred = new Promise((res, rej) => {
968
+ resolve = res;
969
+ reject = rej;
970
+ });
971
+ deferred.catch(() => {});
972
+ this.pending.set(topic, deferred);
973
+ const inflight = this.unsubbing.get(topic);
974
+ if (inflight) await inflight;
975
+ return this.subscribeFn(topic, options).then(() => {
976
+ this.pending.delete(topic);
977
+ resolve();
978
+ }).catch((err) => {
979
+ this.pending.delete(topic);
980
+ this.decrementCount(topic);
981
+ reject(err);
982
+ throw err;
983
+ });
984
+ } else {
985
+ const pending = this.pending.get(topic);
986
+ if (pending) return pending.catch((err) => {
987
+ this.decrementCount(topic);
988
+ throw err;
989
+ });
990
+ }
991
+ }
992
+ async unsubscribe(topic) {
993
+ if (this.decrementCount(topic) === 0) if (this.lingerMs > 0) {
994
+ const timer = setTimeout(() => {
995
+ this.lingers.delete(topic);
996
+ const promise = this.unsubscribeFn(topic).catch(() => {}).finally(() => {
997
+ this.unsubbing.delete(topic);
998
+ });
999
+ this.unsubbing.set(topic, promise);
1000
+ }, this.lingerMs);
1001
+ this.lingers.set(topic, timer);
1002
+ } else {
1003
+ const promise = this.unsubscribeFn(topic).catch(() => {}).finally(() => {
1004
+ this.unsubbing.delete(topic);
1005
+ });
1006
+ this.unsubbing.set(topic, promise);
1007
+ await promise;
1008
+ }
1009
+ }
1010
+ async flush() {
1011
+ const topics = new Set([
1012
+ ...this.counts.keys(),
1013
+ ...this.lingers.keys(),
1014
+ ...this.pending.keys(),
1015
+ ...this.unsubbing.keys()
1016
+ ]);
1017
+ for (const timer of this.lingers.values()) clearTimeout(timer);
1018
+ this.lingers.clear();
1019
+ this.counts.clear();
1020
+ await Promise.allSettled([...this.pending.values(), ...this.unsubbing.values()]);
1021
+ await Promise.allSettled([...topics].map((topic) => this.unsubscribeFn(topic).catch(() => {})));
1022
+ this.pending.clear();
1023
+ this.unsubbing.clear();
1024
+ }
1025
+ };
1026
+ var SubscriptionTrait = class extends BaseTrait {
1027
+ constructor() {
1028
+ super(...arguments);
1029
+ this.subscriptions = new RefCountedSubscription((topic, options) => this.subscribeTopic(topic, options), (topic) => this.unsubscribeTopic(topic));
1030
+ }
1031
+ async subscribeTopicAndSpool(spool, topic, options = {}) {
1032
+ await run(`subscribe to MQTT topic "${topic}"`, spool, () => this.subscriptions.subscribe(topic, {
1033
+ qos: 2,
1034
+ ...options
1035
+ }));
1036
+ spool.roll(() => this.subscriptions.unsubscribe(topic));
1037
+ }
1038
+ async destroy() {
1039
+ await this.subscriptions.flush();
1040
+ await super.destroy();
1041
+ }
1042
+ };
1043
+ //#endregion
1044
+ //#region dst-stage1/mqtt-plus-timer.js
1045
+ var TimerTrait = class extends SubscriptionTrait {
1046
+ constructor() {
1047
+ super(...arguments);
1048
+ this.timers = /* @__PURE__ */ new Map();
1049
+ }
1050
+ async destroy() {
1051
+ for (const timer of this.timers.values()) clearTimeout(timer);
1052
+ this.timers.clear();
1053
+ await super.destroy();
1054
+ }
1055
+ timerRefresh(id, onTimeout) {
1056
+ const timer = this.timers.get(id);
1057
+ if (timer !== void 0) clearTimeout(timer);
1058
+ this.timers.set(id, setTimeout(async () => {
1059
+ this.timers.delete(id);
1060
+ try {
1061
+ await onTimeout();
1062
+ } catch (err) {
1063
+ this.error(ensureError(err), `timer "${id}" failed`);
1064
+ }
1065
+ }, this.options.timeout));
1066
+ }
1067
+ timerClear(id) {
1068
+ const timer = this.timers.get(id);
1069
+ if (timer !== void 0) {
1070
+ clearTimeout(timer);
1071
+ this.timers.delete(id);
1072
+ }
1073
+ }
1074
+ };
1075
+ //#endregion
1076
+ //#region dst-stage1/mqtt-plus-meta.js
1077
+ var MetaTrait = class extends TimerTrait {
1078
+ constructor() {
1079
+ super(...arguments);
1080
+ this._meta = /* @__PURE__ */ new Map();
1081
+ }
1082
+ meta(...args) {
1083
+ const [key, value] = args;
1084
+ if (args.length === 0) return Object.fromEntries(this._meta);
1085
+ else if (args.length === 1) return this._meta.get(key);
1086
+ else if (value === void 0 || value === null) this._meta.delete(key);
1087
+ else this._meta.set(key, value);
1088
+ }
1089
+ metaStore(extra) {
1090
+ const extraEmpty = extra === void 0 || Object.keys(extra).length === 0;
1091
+ if (this._meta.size === 0 && extraEmpty) return void 0;
1092
+ else if (this._meta.size > 0 && extraEmpty) return Object.fromEntries(this._meta);
1093
+ else if (this._meta.size === 0 && !extraEmpty) return extra;
1094
+ else return {
1095
+ ...Object.fromEntries(this._meta),
1096
+ ...extra
1097
+ };
1098
+ }
1099
+ };
1100
+ //#endregion
1101
+ //#region dst-stage1/mqtt-plus-auth.js
1102
+ var textEncoder = new TextEncoder();
1103
+ var AuthTrait = class extends MetaTrait {
1104
+ constructor() {
1105
+ super(...arguments);
1106
+ this._credential = null;
1107
+ this._tokens = /* @__PURE__ */ new Set();
1108
+ }
1109
+ credential(credential) {
1110
+ if (credential.length === 0) throw new Error("credential must not be empty");
1111
+ const pass = textEncoder.encode(credential);
1112
+ const salt = textEncoder.encode("mqtt-plus");
1113
+ this._credential = pbkdf2.deriveKey(sha256.SHA256, pass, salt, 6e5, 32);
1114
+ }
1115
+ async issue(payload) {
1116
+ if (this._credential === null) throw new Error("credential has to be provided before issuing tokens");
1117
+ if (payload.roles.length === 0) throw new Error("payload.roles must be a non-empty array");
1118
+ if (payload.roles.length > 64) throw new Error("payload.roles must not exceed 64 roles");
1119
+ const jwt = new SignJWT(payload);
1120
+ jwt.setProtectedHeader({
1121
+ alg: "HS256",
1122
+ typ: "JWT"
1123
+ });
1124
+ return await jwt.sign(this._credential);
1125
+ }
1126
+ authenticate(token, remove) {
1127
+ if (token === void 0) {
1128
+ const tokens = Array.from(this._tokens).filter((token) => token.length <= 8192).slice(0, 8);
1129
+ return tokens.length > 0 ? tokens : void 0;
1130
+ } else if (remove === true) this._tokens.delete(token);
1131
+ else {
1132
+ if (token.length > 8192) throw new Error("token must not exceed 8192 characters");
1133
+ if (!this._tokens.has(token) && this._tokens.size >= 8) throw new Error("at most 8 tokens can be authenticated at once");
1134
+ this._tokens.add(token);
1135
+ }
1136
+ }
1137
+ async validateToken(token) {
1138
+ if (this._credential === null) throw new Error("credential has to be provided before validating tokens");
1139
+ return (await jwtVerify(token, this._credential).catch(() => null))?.payload ?? null;
1140
+ }
1141
+ async authenticated(clientId, tokens, option) {
1142
+ let authenticated = false;
1143
+ let mode;
1144
+ let roles;
1145
+ if (typeof option === "string") {
1146
+ mode = "require";
1147
+ roles = [option];
1148
+ } else {
1149
+ mode = option.mode;
1150
+ roles = option.roles;
1151
+ }
1152
+ if (tokens !== void 0) for (const token of tokens.slice(0, 8)) {
1153
+ if (token.length > 8192) continue;
1154
+ const payload = await this.validateToken(token);
1155
+ if (payload === null) continue;
1156
+ if (payload.id && payload.id !== clientId) continue;
1157
+ if (!Array.isArray(payload.roles)) continue;
1158
+ if (payload.roles.length > 64) continue;
1159
+ for (const role of roles) if (payload.roles.includes(role)) {
1160
+ authenticated = true;
1161
+ break;
1162
+ }
1163
+ if (authenticated) break;
1164
+ }
1165
+ if (!authenticated && mode === "optional") authenticated = true;
1166
+ return authenticated;
1167
+ }
1168
+ };
1169
+ //#endregion
1170
+ //#region dst-stage1/mqtt-plus-event.js
1171
+ var EventTrait = class extends AuthTrait {
1172
+ async event(nameOrConfig, ...args) {
1173
+ let name;
1174
+ let callback;
1175
+ let options = {};
1176
+ let share = this.options.share;
1177
+ let auth;
1178
+ if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1179
+ name = nameOrConfig.name;
1180
+ callback = nameOrConfig.callback;
1181
+ options = nameOrConfig.options ?? {};
1182
+ share = nameOrConfig.share ?? this.options.share;
1183
+ auth = nameOrConfig.auth;
1184
+ } else {
1185
+ name = nameOrConfig;
1186
+ callback = args[0];
1187
+ }
1188
+ const spool = new Spool();
1189
+ if (this.onRequest.has(`event-emission:${name}`)) throw new Error(`event: event "${name}" already registered`);
1190
+ const topicS = share !== "" ? `$share/${share}/${name}` : name;
1191
+ const topicB = this.options.topicMake(topicS, "event-emission");
1192
+ const topicD = this.options.topicMake(name, "event-emission", this.options.id);
1193
+ this.onRequest.set(`event-emission:${name}`, async (request, topicName) => {
1194
+ const senderId = request.sender;
1195
+ if (senderId === void 0 || senderId === "") throw new Error("invalid request: missing sender");
1196
+ const params = request.params ?? [];
1197
+ const info = { sender: senderId };
1198
+ if (request.receiver) info.receiver = request.receiver;
1199
+ if (request.meta) info.meta = request.meta;
1200
+ try {
1201
+ if (topicName !== request.name) throw new Error(`event name mismatch (topic: "${topicName}", payload: "${request.name}")`);
1202
+ if (auth) info.authenticated = await this.authenticated(senderId, request.auth, auth);
1203
+ if (info.authenticated !== void 0 && !info.authenticated) throw new Error(`event "${name}" failed authentication`);
1204
+ await callback(...params, info);
1205
+ } catch (result) {
1206
+ const error = ensureError(result);
1207
+ this.error(error, `handler for event "${name}" failed`);
1208
+ }
1209
+ });
1210
+ spool.roll(() => {
1211
+ this.onRequest.delete(`event-emission:${name}`);
1212
+ });
1213
+ await this.subscribeTopicAndSpool(spool, topicB, options);
1214
+ await this.subscribeTopicAndSpool(spool, topicD, options);
1215
+ return this.makeRegistration(spool, "event", name, `event-emission:${name}`);
1216
+ }
1217
+ emit(nameOrConfig, ...args) {
1218
+ let name;
1219
+ let params;
1220
+ let receiver;
1221
+ let options = {};
1222
+ let meta;
1223
+ let dry;
1224
+ if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1225
+ name = nameOrConfig.name;
1226
+ params = nameOrConfig.params;
1227
+ receiver = nameOrConfig.receiver;
1228
+ options = nameOrConfig.options ?? {};
1229
+ meta = nameOrConfig.meta;
1230
+ dry = nameOrConfig.dry;
1231
+ } else {
1232
+ name = nameOrConfig;
1233
+ params = args;
1234
+ }
1235
+ const requestId = nanoid();
1236
+ const auth = this.authenticate();
1237
+ const metaStore = this.metaStore(meta);
1238
+ const request = this.msg.makeEventEmission(requestId, name, params, this.options.id, receiver, auth, metaStore);
1239
+ const message = this.codec.encode(request);
1240
+ const topic = this.options.topicMake(name, "event-emission", receiver);
1241
+ if (dry) return {
1242
+ topic,
1243
+ payload: message,
1244
+ options: {
1245
+ qos: 2,
1246
+ ...options
1247
+ }
1248
+ };
1249
+ else this.publishToTopic(topic, message, {
1250
+ qos: 2,
1251
+ ...options
1252
+ }).catch((err) => {
1253
+ this.error(err, `emitting event "${name}" failed`);
1254
+ });
1255
+ }
1256
+ };
1257
+ //#endregion
1258
+ //#region dst-stage1/mqtt-plus-service.js
1259
+ var ServiceTrait = class extends EventTrait {
1260
+ async service(nameOrConfig, ...args) {
1261
+ let name;
1262
+ let callback;
1263
+ let options = {};
1264
+ let share = this.options.share;
1265
+ let auth;
1266
+ if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1267
+ name = nameOrConfig.name;
1268
+ callback = nameOrConfig.callback;
1269
+ options = nameOrConfig.options ?? {};
1270
+ share = nameOrConfig.share ?? this.options.share;
1271
+ auth = nameOrConfig.auth;
1272
+ } else {
1273
+ name = nameOrConfig;
1274
+ callback = args[0];
1275
+ }
1276
+ const spool = new Spool();
1277
+ if (this.onRequest.has(`service-call-request:${name}`)) throw new Error(`service: service "${name}" already registered`);
1278
+ const topicS = share !== "" ? `$share/${share}/${name}` : name;
1279
+ const topicB = this.options.topicMake(topicS, "service-call-request");
1280
+ const topicD = this.options.topicMake(name, "service-call-request", this.options.id);
1281
+ this.onRequest.set(`service-call-request:${name}`, async (request, topicName) => {
1282
+ const requestId = request.id;
1283
+ const senderId = request.sender;
1284
+ if (senderId === void 0 || senderId === "") throw new Error("invalid request: missing sender");
1285
+ const params = request.params ?? [];
1286
+ const info = { sender: senderId };
1287
+ if (request.receiver) info.receiver = request.receiver;
1288
+ if (request.meta) info.meta = request.meta;
1289
+ try {
1290
+ if (topicName !== request.name) throw new Error(`service name mismatch (topic: "${topicName}", payload: "${request.name}")`);
1291
+ if (auth) info.authenticated = await this.authenticated(senderId, request.auth, auth);
1292
+ if (info.authenticated !== void 0 && !info.authenticated) throw new Error(`service "${name}" failed authentication`);
1293
+ const result = await callback(...params, info);
1294
+ const rpcResponse = this.msg.makeServiceCallResponse(requestId, result, void 0, this.options.id, senderId);
1295
+ const encoded = this.codec.encode(rpcResponse);
1296
+ const topic = this.options.topicMake(name, "service-call-response", senderId);
1297
+ await this.publishToTopic(topic, encoded, { qos: options.qos ?? 2 });
1298
+ } catch (err) {
1299
+ const error = ensureError(err);
1300
+ this.error(error, `handler for service "${name}" failed`);
1301
+ const rpcResponse = this.msg.makeServiceCallResponse(requestId, void 0, error.message, this.options.id, senderId);
1302
+ try {
1303
+ const encoded = this.codec.encode(rpcResponse);
1304
+ const topic = this.options.topicMake(name, "service-call-response", senderId);
1305
+ await this.publishToTopic(topic, encoded, { qos: options.qos ?? 2 });
1306
+ } catch (err2) {
1307
+ this.error(ensureError(err2), `sending error response for service "${name}" failed`);
1308
+ }
1309
+ }
1310
+ });
1311
+ spool.roll(() => {
1312
+ this.onRequest.delete(`service-call-request:${name}`);
1313
+ });
1314
+ await this.subscribeTopicAndSpool(spool, topicB, options);
1315
+ await this.subscribeTopicAndSpool(spool, topicD, options);
1316
+ return this.makeRegistration(spool, "service", name, `service-call-request:${name}`);
1317
+ }
1318
+ async call(nameOrConfig, ...args) {
1319
+ let name;
1320
+ let params;
1321
+ let receiver;
1322
+ let options = {};
1323
+ let meta;
1324
+ if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1325
+ name = nameOrConfig.name;
1326
+ params = nameOrConfig.params;
1327
+ receiver = nameOrConfig.receiver;
1328
+ options = nameOrConfig.options ?? {};
1329
+ meta = nameOrConfig.meta;
1330
+ } else {
1331
+ name = nameOrConfig;
1332
+ params = args;
1333
+ }
1334
+ const spool = new Spool();
1335
+ let requestId = nanoid();
1336
+ while (this.onResponse.has(`service-call-response:${requestId}`)) requestId = nanoid();
1337
+ const responseTopic = this.options.topicMake(name, "service-call-response", this.options.id);
1338
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
1339
+ const timerId = `service-call:${requestId}`;
1340
+ let rejectPromise;
1341
+ const promise = new Promise((resolve, reject) => {
1342
+ rejectPromise = reject;
1343
+ this.timerRefresh(timerId, async () => {
1344
+ await spool.unroll();
1345
+ reject(/* @__PURE__ */ new Error("communication timeout"));
1346
+ });
1347
+ spool.roll(() => {
1348
+ this.timerClear(timerId);
1349
+ });
1350
+ this.onResponse.set(`service-call-response:${requestId}`, async (response) => {
1351
+ await spool.unroll();
1352
+ if (response.error !== void 0) reject(new Error(response.error));
1353
+ else resolve(response.result);
1354
+ });
1355
+ spool.roll(() => {
1356
+ this.onResponse.delete(`service-call-response:${requestId}`);
1357
+ });
1358
+ });
1359
+ const auth = this.authenticate();
1360
+ const metaStore = this.metaStore(meta);
1361
+ const request = this.msg.makeServiceCallRequest(requestId, name, params, this.options.id, receiver, auth, metaStore);
1362
+ const message = this.codec.encode(request);
1363
+ const topic = this.options.topicMake(name, "service-call-request", receiver);
1364
+ try {
1365
+ await run(`publish service request as MQTT message to topic "${topic}"`, spool, () => this.publishToTopic(topic, message, {
1366
+ qos: 2,
1367
+ ...options
1368
+ }));
1369
+ } catch (err) {
1370
+ rejectPromise(err);
1371
+ return promise;
1372
+ }
1373
+ return promise;
1374
+ }
1375
+ };
1376
+ //#endregion
1377
+ //#region dst-stage1/mqtt-plus-source.js
1378
+ var SourceTrait = class extends ServiceTrait {
1379
+ constructor() {
1380
+ super(...arguments);
1381
+ this.sourceCreditGates = /* @__PURE__ */ new Map();
1382
+ this.sourceControllers = /* @__PURE__ */ new Map();
1383
+ }
1384
+ async destroy() {
1385
+ for (const controller of this.sourceControllers.values()) controller.abort(/* @__PURE__ */ new Error("source destroyed"));
1386
+ this.sourceControllers.clear();
1387
+ for (const gate of this.sourceCreditGates.values()) gate.abort();
1388
+ this.sourceCreditGates.clear();
1389
+ await super.destroy();
1390
+ }
1391
+ async source(nameOrConfig, ...args) {
1392
+ let name;
1393
+ let callback;
1394
+ let options = {};
1395
+ let share = this.options.share;
1396
+ let auth;
1397
+ if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1398
+ name = nameOrConfig.name;
1399
+ callback = nameOrConfig.callback;
1400
+ options = nameOrConfig.options ?? {};
1401
+ share = nameOrConfig.share ?? this.options.share;
1402
+ auth = nameOrConfig.auth;
1403
+ } else {
1404
+ name = nameOrConfig;
1405
+ callback = args[0];
1406
+ }
1407
+ const spool = new Spool();
1408
+ if (this.onRequest.has(`source-fetch-request:${name}`)) throw new Error(`source: source "${name}" already registered`);
1409
+ const topicS = share !== "" ? `$share/${share}/${name}` : name;
1410
+ const topicReqB = this.options.topicMake(topicS, "source-fetch-request");
1411
+ const topicReqD = this.options.topicMake(name, "source-fetch-request", this.options.id);
1412
+ this.onRequest.set(`source-fetch-request:${name}`, async (request, topicName) => {
1413
+ const requestId = request.id;
1414
+ const params = request.params ?? [];
1415
+ const sender = request.sender;
1416
+ if (sender === void 0 || sender === "") throw new Error("invalid request: missing sender");
1417
+ const receiver = request.receiver;
1418
+ const info = { sender };
1419
+ if (receiver) info.receiver = receiver;
1420
+ if (request.meta) info.meta = request.meta;
1421
+ const responseTopic = this.options.topicMake(name, "source-fetch-response", sender);
1422
+ const sendResponse = async (error) => {
1423
+ const metaStore = this.metaStore(info.meta);
1424
+ const response = this.msg.makeSourceFetchResponse(requestId, name, error, this.options.id, sender, metaStore);
1425
+ const message = this.codec.encode(response);
1426
+ await this.publishToTopic(responseTopic, message, { qos: options.qos ?? 2 });
1427
+ };
1428
+ const reqSpool = new Spool();
1429
+ reqSpool.roll(() => {
1430
+ this.onResponse.delete(`source-fetch-credit:${requestId}`);
1431
+ this.sourceControllers.delete(requestId);
1432
+ });
1433
+ const abortController = new AbortController();
1434
+ this.sourceControllers.set(requestId, abortController);
1435
+ const abortSignal = abortController.signal;
1436
+ abortSignal.addEventListener("abort", () => {
1437
+ if (info.stream instanceof Readable && !info.stream.destroyed) info.stream.destroy(ensureError(abortSignal.reason));
1438
+ }, { once: true });
1439
+ const sourceTimerId = `source-fetch-send:${requestId}`;
1440
+ const refreshSourceTimeout = () => this.timerRefresh(sourceTimerId, () => {
1441
+ const error = /* @__PURE__ */ new Error(`source fetch "${name}" timed out`);
1442
+ abortController.abort(error);
1443
+ const gate = this.sourceCreditGates.get(requestId);
1444
+ if (gate !== void 0) {
1445
+ gate.abort();
1446
+ this.sourceCreditGates.delete(requestId);
1447
+ }
1448
+ reqSpool.unroll();
1449
+ });
1450
+ const clearSourceTimeout = () => this.timerClear(sourceTimerId);
1451
+ refreshSourceTimeout();
1452
+ reqSpool.roll(() => {
1453
+ clearSourceTimeout();
1454
+ });
1455
+ const sendChunk = async (chunk, error, final) => {
1456
+ refreshSourceTimeout();
1457
+ const chunkMsg = this.msg.makeSourceFetchChunk(requestId, name, chunk, error, final, this.options.id, sender);
1458
+ const message = this.codec.encode(chunkMsg);
1459
+ await this.publishToTopic(responseTopic, message, { qos: options.qos ?? 2 });
1460
+ };
1461
+ let ackSent = false;
1462
+ let creditGate;
1463
+ let cancelledByFetcher = false;
1464
+ try {
1465
+ if (topicName !== request.name) throw new Error(`source name mismatch (topic: "${topicName}", payload: "${request.name}")`);
1466
+ if (auth) info.authenticated = await this.authenticated(sender, request.auth, auth);
1467
+ if (info.authenticated !== void 0 && !info.authenticated) throw new Error(`source "${name}" failed authentication`);
1468
+ const initialCredit = request.credit;
1469
+ creditGate = initialCredit !== void 0 && initialCredit > 0 ? new CreditGate(initialCredit) : void 0;
1470
+ if (creditGate) {
1471
+ this.sourceCreditGates.set(requestId, creditGate);
1472
+ reqSpool.roll(() => {
1473
+ creditGate.abort();
1474
+ this.sourceCreditGates.delete(requestId);
1475
+ });
1476
+ }
1477
+ this.onResponse.set(`source-fetch-credit:${requestId}`, (creditParsed) => {
1478
+ if (creditParsed.credit === 0) {
1479
+ cancelledByFetcher = true;
1480
+ abortController.abort(/* @__PURE__ */ new Error(`source fetch "${name}" cancelled by fetcher`));
1481
+ return;
1482
+ }
1483
+ if (creditGate) {
1484
+ creditGate.replenish(creditParsed.credit);
1485
+ refreshSourceTimeout();
1486
+ }
1487
+ });
1488
+ await callback(...params, info);
1489
+ if (!(info.stream instanceof Readable) && !(info.buffer instanceof Promise)) throw new Error("handler did not provide data via info.stream or info.buffer fields");
1490
+ if (info.stream instanceof Readable && info.buffer instanceof Promise) throw new Error("handler has set both info.stream and info.buffer fields");
1491
+ await sendResponse();
1492
+ ackSent = true;
1493
+ if (info.stream instanceof Readable) await sendStreamAsChunks(info.stream, this.options.chunkSize, sendChunk, creditGate, abortSignal);
1494
+ else if (info.buffer instanceof Promise) await sendBufferAsChunks(await info.buffer, this.options.chunkSize, sendChunk, creditGate, abortSignal);
1495
+ } catch (err) {
1496
+ const error = ensureError(err, `handler for source "${name}" failed`);
1497
+ abortController.abort(error);
1498
+ if (!cancelledByFetcher) {
1499
+ this.error(error);
1500
+ if (ackSent) await sendChunk(void 0, error.message, true).catch(() => {});
1501
+ else await sendResponse(error.message).catch(() => {});
1502
+ }
1503
+ } finally {
1504
+ await reqSpool.unroll();
1505
+ }
1506
+ });
1507
+ spool.roll(() => {
1508
+ this.onRequest.delete(`source-fetch-request:${name}`);
1509
+ });
1510
+ await this.subscribeTopicAndSpool(spool, topicReqB, options);
1511
+ await this.subscribeTopicAndSpool(spool, topicReqD, options);
1512
+ return this.makeRegistration(spool, "source", name, `source-fetch-request:${name}`);
1513
+ }
1514
+ async fetch(nameOrConfig, ...args) {
1515
+ let name;
1516
+ let params;
1517
+ let receiver;
1518
+ let options = {};
1519
+ let meta;
1520
+ if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1521
+ name = nameOrConfig.name;
1522
+ params = nameOrConfig.params;
1523
+ receiver = nameOrConfig.receiver;
1524
+ options = nameOrConfig.options ?? {};
1525
+ meta = nameOrConfig.meta;
1526
+ } else {
1527
+ name = nameOrConfig;
1528
+ params = args;
1529
+ }
1530
+ const spool = new Spool();
1531
+ let requestId = nanoid();
1532
+ while (this.onResponse.has(`source-fetch-response:${requestId}`) || this.onResponse.has(`source-fetch-chunk:${requestId}`)) requestId = nanoid();
1533
+ const responseTopic = this.options.topicMake(name, "source-fetch-response", this.options.id);
1534
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
1535
+ const chunkCredit = this.options.chunkCredit;
1536
+ let chunksReceived = 0;
1537
+ let creditGranted = chunkCredit;
1538
+ let serverId;
1539
+ let streamEnded = false;
1540
+ const timerId = `source-fetch:${requestId}`;
1541
+ let stream = void 0;
1542
+ const refreshTimeout = () => {
1543
+ this.timerRefresh(timerId, () => {
1544
+ stream?.destroy(/* @__PURE__ */ new Error("communication timeout"));
1545
+ spool.unroll();
1546
+ });
1547
+ };
1548
+ spool.roll(() => {
1549
+ this.timerClear(timerId);
1550
+ });
1551
+ stream = new Readable({
1552
+ highWaterMark: chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16 * 1024,
1553
+ read: (_size) => {
1554
+ if (chunkCredit <= 0 || !this.onResponse.has(`source-fetch-response:${requestId}`)) return;
1555
+ const targetId = serverId ?? receiver;
1556
+ if (!targetId) return;
1557
+ const creditToGrant = Math.max(0, chunksReceived + chunkCredit - creditGranted);
1558
+ if (creditToGrant > 0) {
1559
+ creditGranted += creditToGrant;
1560
+ const creditMsg = this.msg.makeSourceFetchCredit(requestId, name, creditToGrant, this.options.id, targetId);
1561
+ const encoded = this.codec.encode(creditMsg);
1562
+ const creditTopic = this.options.topicMake(name, "source-fetch-request", targetId);
1563
+ this.publishToTopic(creditTopic, encoded, { qos: options.qos ?? 2 }).catch((err) => {
1564
+ this.error(err, `sending credit for fetch "${name}" failed`);
1565
+ });
1566
+ refreshTimeout();
1567
+ }
1568
+ }
1569
+ });
1570
+ const buffer = streamToBuffer(stream);
1571
+ let metaResolve;
1572
+ const metaP = new Promise((resolve) => {
1573
+ metaResolve = resolve;
1574
+ });
1575
+ spool.roll(() => {
1576
+ metaResolve(void 0);
1577
+ });
1578
+ refreshTimeout();
1579
+ let cancelled = false;
1580
+ const cancelAndUnroll = () => {
1581
+ if (!cancelled && !streamEnded) {
1582
+ cancelled = true;
1583
+ const targetId = serverId ?? receiver;
1584
+ if (targetId) {
1585
+ const cancelMsg = this.msg.makeSourceFetchCredit(requestId, name, 0, this.options.id, targetId);
1586
+ const encoded = this.codec.encode(cancelMsg);
1587
+ const cancelTopic = this.options.topicMake(name, "source-fetch-request", targetId);
1588
+ this.publishToTopic(cancelTopic, encoded, { qos: options.qos ?? 2 }).catch(() => {});
1589
+ }
1590
+ }
1591
+ spool.unroll();
1592
+ };
1593
+ stream.once("close", cancelAndUnroll);
1594
+ stream.once("error", cancelAndUnroll);
1595
+ this.onResponse.set(`source-fetch-response:${requestId}`, (response) => {
1596
+ if (streamEnded) return;
1597
+ if (response.name !== name) {
1598
+ streamEnded = true;
1599
+ stream.destroy(/* @__PURE__ */ new Error(`source name mismatch (expected "${name}", got "${response.name}")`));
1600
+ spool.unroll();
1601
+ return;
1602
+ }
1603
+ if (response.sender) serverId = response.sender;
1604
+ if (response.error) {
1605
+ streamEnded = true;
1606
+ stream.destroy(new Error(response.error));
1607
+ spool.unroll();
1608
+ } else {
1609
+ metaResolve(response.meta);
1610
+ refreshTimeout();
1611
+ }
1612
+ });
1613
+ this.onResponse.set(`source-fetch-chunk:${requestId}`, (response) => {
1614
+ if (streamEnded) return;
1615
+ if (response.error) {
1616
+ streamEnded = true;
1617
+ stream.destroy(new Error(response.error));
1618
+ spool.unroll();
1619
+ } else {
1620
+ refreshTimeout();
1621
+ if (response.chunk !== void 0) {
1622
+ chunksReceived++;
1623
+ stream.push(response.chunk);
1624
+ }
1625
+ if (response.final) {
1626
+ streamEnded = true;
1627
+ stream.push(null);
1628
+ spool.unroll();
1629
+ }
1630
+ }
1631
+ });
1632
+ spool.roll(() => {
1633
+ this.onResponse.delete(`source-fetch-response:${requestId}`);
1634
+ this.onResponse.delete(`source-fetch-chunk:${requestId}`);
1635
+ });
1636
+ const auth = this.authenticate();
1637
+ const metaStore = this.metaStore(meta);
1638
+ const credit = chunkCredit > 0 ? chunkCredit : void 0;
1639
+ const request = this.msg.makeSourceFetchRequest(requestId, name, params, this.options.id, receiver, auth, metaStore, credit);
1640
+ const message = this.codec.encode(request);
1641
+ const topic = this.options.topicMake(name, "source-fetch-request", receiver);
1642
+ run(`publish fetch request as MQTT message to topic "${topic}"`, () => this.publishToTopic(topic, message, {
1643
+ qos: 2,
1644
+ ...options
1645
+ })).catch((err) => {
1646
+ stream.destroy(ensureError(err));
1647
+ });
1648
+ const result = {
1649
+ stream,
1650
+ buffer,
1651
+ meta: metaP
1652
+ };
1653
+ makeMutuallyExclusiveFields(result, "stream", "buffer");
1654
+ return result;
1655
+ }
1656
+ };
1657
+ //#endregion
1658
+ //#region dst-stage1/mqtt-plus-sink.js
1659
+ var SinkTrait = class extends SourceTrait {
1660
+ constructor() {
1661
+ super(...arguments);
1662
+ this.pushStreams = /* @__PURE__ */ new Map();
1663
+ this.pushSpools = /* @__PURE__ */ new Map();
1664
+ }
1665
+ async destroy() {
1666
+ for (const stream of this.pushStreams.values()) stream.destroy(/* @__PURE__ */ new Error("sink destroyed"));
1667
+ this.pushStreams.clear();
1668
+ for (const spool of this.pushSpools.values()) await spool.unroll();
1669
+ this.pushSpools.clear();
1670
+ await super.destroy();
1671
+ }
1672
+ async sink(nameOrConfig, ...args) {
1673
+ let name;
1674
+ let callback;
1675
+ let options = {};
1676
+ let share = this.options.share;
1677
+ let auth;
1678
+ if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1679
+ name = nameOrConfig.name;
1680
+ callback = nameOrConfig.callback;
1681
+ options = nameOrConfig.options ?? {};
1682
+ share = nameOrConfig.share ?? this.options.share;
1683
+ auth = nameOrConfig.auth;
1684
+ } else {
1685
+ name = nameOrConfig;
1686
+ callback = args[0];
1687
+ }
1688
+ const spool = new Spool();
1689
+ if (this.onRequest.has(`sink-push-request:${name}`)) throw new Error(`sink: sink "${name}" already registered`);
1690
+ const topicS = share !== "" ? `$share/${share}/${name}` : name;
1691
+ const topicReqB = this.options.topicMake(topicS, "sink-push-request");
1692
+ const topicReqD = this.options.topicMake(name, "sink-push-request", this.options.id);
1693
+ this.onRequest.set(`sink-push-request:${name}`, async (request, topicName) => {
1694
+ const requestId = request.id;
1695
+ const params = request.params ?? [];
1696
+ const sender = request.sender;
1697
+ if (sender === void 0 || sender === "") throw new Error("invalid request: missing sender");
1698
+ const receiver = request.receiver;
1699
+ const responseTopic = this.options.topicMake(name, "sink-push-response", sender);
1700
+ const chunkCredit = this.options.chunkCredit;
1701
+ const sendResponse = async (error, withCredit = false) => {
1702
+ const credit = error === void 0 && withCredit && chunkCredit > 0 ? chunkCredit : void 0;
1703
+ const response = this.msg.makeSinkPushResponse(requestId, name, error, this.options.id, sender, credit);
1704
+ const message = this.codec.encode(response);
1705
+ await this.publishToTopic(responseTopic, message, { qos: options.qos ?? 2 });
1706
+ };
1707
+ const reqSpool = new Spool();
1708
+ this.pushSpools.set(requestId, reqSpool);
1709
+ reqSpool.roll(() => {
1710
+ this.pushSpools.delete(requestId);
1711
+ });
1712
+ try {
1713
+ if (topicName !== request.name) throw new Error(`sink name mismatch (topic: "${topicName}", payload: "${request.name}")`);
1714
+ let authenticated = void 0;
1715
+ if (auth) authenticated = await this.authenticated(sender, request.auth, auth);
1716
+ if (authenticated !== void 0 && !authenticated) throw new Error(`sink "${name}" failed authentication`);
1717
+ const creditState = chunkCredit > 0 ? {
1718
+ chunksReceived: 0,
1719
+ creditGranted: chunkCredit
1720
+ } : void 0;
1721
+ const pushTimerId = `sink-push-recv:${requestId}`;
1722
+ const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
1723
+ if (streamEnded) return;
1724
+ const stream = this.pushStreams.get(requestId);
1725
+ if (stream !== void 0) stream.destroy(/* @__PURE__ */ new Error("push stream timeout"));
1726
+ this.pushSpools.get(requestId)?.unroll();
1727
+ });
1728
+ const clearPushTimeout = () => this.timerClear(pushTimerId);
1729
+ const readable = new Readable({
1730
+ highWaterMark: chunkCredit > 0 ? chunkCredit * this.options.chunkSize : 16 * 1024,
1731
+ read: (_size) => {
1732
+ if (!creditState || !this.pushSpools.has(requestId)) return;
1733
+ const creditToGrant = Math.max(0, creditState.chunksReceived + chunkCredit - creditState.creditGranted);
1734
+ if (creditToGrant > 0) {
1735
+ creditState.creditGranted += creditToGrant;
1736
+ const creditMsg = this.msg.makeSinkPushCredit(requestId, name, creditToGrant, this.options.id, sender);
1737
+ const encoded = this.codec.encode(creditMsg);
1738
+ this.publishToTopic(responseTopic, encoded, { qos: options.qos ?? 2 }).catch((err) => {
1739
+ this.error(err, `sending credit for push "${name}" failed`);
1740
+ });
1741
+ refreshPushTimeout();
1742
+ }
1743
+ }
1744
+ });
1745
+ this.pushStreams.set(requestId, readable);
1746
+ reqSpool.roll(() => {
1747
+ this.pushStreams.delete(requestId);
1748
+ });
1749
+ readable.once("error", () => {});
1750
+ let streamEnded = false;
1751
+ this.onResponse.set(`sink-push-chunk:${requestId}`, async (chunkParsed) => {
1752
+ if (streamEnded) return;
1753
+ if (chunkParsed.error !== void 0) {
1754
+ streamEnded = true;
1755
+ clearPushTimeout();
1756
+ readable.destroy(new Error(chunkParsed.error));
1757
+ } else {
1758
+ refreshPushTimeout();
1759
+ if (chunkParsed.chunk !== void 0) {
1760
+ if (creditState) creditState.chunksReceived++;
1761
+ readable.push(chunkParsed.chunk);
1762
+ }
1763
+ if (chunkParsed.final) {
1764
+ streamEnded = true;
1765
+ clearPushTimeout();
1766
+ readable.push(null);
1767
+ }
1768
+ }
1769
+ });
1770
+ reqSpool.roll(() => {
1771
+ this.onResponse.delete(`sink-push-chunk:${requestId}`);
1772
+ });
1773
+ refreshPushTimeout();
1774
+ reqSpool.roll(() => {
1775
+ clearPushTimeout();
1776
+ });
1777
+ const promise = streamToBuffer(readable);
1778
+ const streamDone = new Promise((resolve, reject) => {
1779
+ readable.once("end", () => {
1780
+ resolve();
1781
+ });
1782
+ readable.once("error", (err) => {
1783
+ reject(err);
1784
+ });
1785
+ });
1786
+ streamDone.catch(() => {});
1787
+ const info = {
1788
+ sender,
1789
+ stream: readable,
1790
+ buffer: promise
1791
+ };
1792
+ if (receiver) info.receiver = receiver;
1793
+ if (authenticated !== void 0) info.authenticated = authenticated;
1794
+ if (request.meta) info.meta = request.meta;
1795
+ makeMutuallyExclusiveFields(info, "stream", "buffer");
1796
+ await sendResponse(void 0, true);
1797
+ await callback(...params, info);
1798
+ if (readable.readableFlowing !== true && !readable.destroyed) readable.resume();
1799
+ await streamDone;
1800
+ try {
1801
+ await sendResponse();
1802
+ } catch (err2) {
1803
+ this.error(ensureError(err2), `sending terminal response for sink "${name}" failed`);
1804
+ }
1805
+ } catch (err) {
1806
+ const error = ensureError(err, `handler for sink "${name}" failed`);
1807
+ this.error(error);
1808
+ await sendResponse(error.message).catch(() => {});
1809
+ } finally {
1810
+ const stream = this.pushStreams.get(requestId);
1811
+ if (stream !== void 0 && !stream.destroyed) stream.destroy();
1812
+ await reqSpool.unroll();
1813
+ }
1814
+ });
1815
+ spool.roll(() => {
1816
+ this.onRequest.delete(`sink-push-request:${name}`);
1817
+ });
1818
+ await this.subscribeTopicAndSpool(spool, topicReqB, options);
1819
+ await this.subscribeTopicAndSpool(spool, topicReqD, options);
1820
+ return this.makeRegistration(spool, "sink", name, `sink-push-request:${name}`);
1821
+ }
1822
+ async push(nameOrConfig, ...args) {
1823
+ let name;
1824
+ let data;
1825
+ let params;
1826
+ let receiver;
1827
+ let options = {};
1828
+ let meta;
1829
+ if (typeof nameOrConfig === "object" && nameOrConfig !== null) {
1830
+ name = nameOrConfig.name;
1831
+ data = nameOrConfig.data;
1832
+ params = nameOrConfig.params;
1833
+ receiver = nameOrConfig.receiver;
1834
+ options = nameOrConfig.options ?? {};
1835
+ meta = nameOrConfig.meta;
1836
+ } else {
1837
+ name = nameOrConfig;
1838
+ data = args[0];
1839
+ params = args.slice(1);
1840
+ }
1841
+ if (!(data instanceof Readable) && !(data instanceof Uint8Array)) throw new Error("invalid data type: expected Readable or Uint8Array");
1842
+ const spool = new Spool();
1843
+ let requestId = nanoid();
1844
+ while (this.onResponse.has(`sink-push-response:${requestId}`) || this.onResponse.has(`sink-push-credit:${requestId}`)) requestId = nanoid();
1845
+ const responseTopic = this.options.topicMake(name, "sink-push-response", this.options.id);
1846
+ await this.subscribeTopicAndSpool(spool, responseTopic, { qos: options.qos ?? 2 });
1847
+ const abortController = new AbortController();
1848
+ const abortSignal = abortController.signal;
1849
+ if (data instanceof Readable) {
1850
+ const stream = data;
1851
+ abortSignal.addEventListener("abort", () => {
1852
+ if (!stream.destroyed) stream.destroy(ensureError(abortSignal.reason));
1853
+ }, { once: true });
1854
+ }
1855
+ const pushTimerId = `sink-push-send:${requestId}`;
1856
+ const refreshTimeout = () => this.timerRefresh(pushTimerId, () => {
1857
+ const error = /* @__PURE__ */ new Error(`push to sink "${name}" timed out`);
1858
+ abortController.abort(error);
1859
+ spool.unroll();
1860
+ });
1861
+ spool.roll(() => {
1862
+ this.timerClear(pushTimerId);
1863
+ });
1864
+ refreshTimeout();
1865
+ let initialCredit;
1866
+ let creditGate;
1867
+ let remoteError = false;
1868
+ let pushAcked = false;
1869
+ let pushFinalized = false;
1870
+ let pushDataFinalSent = false;
1871
+ let pushFinalizeResolve;
1872
+ let pushFinalizeReject;
1873
+ const pushFinalize = new Promise((resolve, reject) => {
1874
+ pushFinalizeResolve = resolve;
1875
+ pushFinalizeReject = reject;
1876
+ });
1877
+ pushFinalize.catch(() => {});
1878
+ try {
1879
+ await new Promise((resolve, reject) => {
1880
+ const onAbort = () => {
1881
+ reject(abortSignal.reason);
1882
+ };
1883
+ abortSignal.addEventListener("abort", onAbort, { once: true });
1884
+ spool.roll(() => {
1885
+ abortSignal.removeEventListener("abort", onAbort);
1886
+ });
1887
+ this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
1888
+ if (response.error) reject(new Error(response.error));
1889
+ else {
1890
+ if (response.sender) receiver = response.sender;
1891
+ initialCredit = response.credit;
1892
+ pushAcked = true;
1893
+ resolve();
1894
+ }
1895
+ });
1896
+ spool.roll(() => {
1897
+ this.onResponse.delete(`sink-push-response:${requestId}`);
1898
+ });
1899
+ const auth = this.authenticate();
1900
+ const metaStore = this.metaStore(meta);
1901
+ const request = this.msg.makeSinkPushRequest(requestId, name, params, this.options.id, receiver, auth, metaStore);
1902
+ const message = this.codec.encode(request);
1903
+ const requestTopic = this.options.topicMake(name, "sink-push-request", receiver);
1904
+ run(`publish push request as MQTT message to topic "${requestTopic}"`, spool, () => this.publishToTopic(requestTopic, message, {
1905
+ qos: 2,
1906
+ ...options
1907
+ })).catch((err) => {
1908
+ reject(err);
1909
+ });
1910
+ });
1911
+ this.onResponse.set(`sink-push-response:${requestId}`, (response) => {
1912
+ if (response.error) {
1913
+ remoteError = true;
1914
+ pushFinalizeReject(new Error(response.error));
1915
+ abortController.abort(new Error(response.error));
1916
+ } else if (pushAcked && !pushFinalized) {
1917
+ pushFinalized = true;
1918
+ pushFinalizeResolve();
1919
+ }
1920
+ });
1921
+ if (initialCredit !== void 0 && initialCredit > 0) creditGate = new CreditGate(initialCredit);
1922
+ if (creditGate) {
1923
+ const gate = creditGate;
1924
+ spool.roll(() => {
1925
+ gate.abort();
1926
+ });
1927
+ this.onResponse.set(`sink-push-credit:${requestId}`, (response) => {
1928
+ gate.replenish(response.credit);
1929
+ refreshTimeout();
1930
+ });
1931
+ spool.roll(() => {
1932
+ this.onResponse.delete(`sink-push-credit:${requestId}`);
1933
+ });
1934
+ }
1935
+ const chunkTopic = this.options.topicMake(name, "sink-push-request", receiver);
1936
+ const sendChunk = async (chunk, error, final) => {
1937
+ refreshTimeout();
1938
+ const chunkMsg = this.msg.makeSinkPushChunk(requestId, name, chunk, error, final, this.options.id, receiver);
1939
+ const message = this.codec.encode(chunkMsg);
1940
+ await this.publishToTopic(chunkTopic, message, {
1941
+ qos: 2,
1942
+ ...options
1943
+ });
1944
+ if (error === void 0 && final) pushDataFinalSent = true;
1945
+ };
1946
+ if (data instanceof Readable) await sendStreamAsChunks(data, this.options.chunkSize, sendChunk, creditGate, abortSignal);
1947
+ else if (data instanceof Uint8Array) await sendBufferAsChunks(data, this.options.chunkSize, sendChunk, creditGate, abortSignal);
1948
+ if (!pushFinalized) await new Promise((resolve, reject) => {
1949
+ const onAbort = () => {
1950
+ reject(abortSignal.reason);
1951
+ };
1952
+ abortSignal.addEventListener("abort", onAbort, { once: true });
1953
+ pushFinalize.then(resolve, reject).finally(() => {
1954
+ abortSignal.removeEventListener("abort", onAbort);
1955
+ });
1956
+ });
1957
+ } catch (err) {
1958
+ const error = ensureError(err);
1959
+ abortController.abort(error);
1960
+ if (pushAcked && !remoteError && !pushDataFinalSent) {
1961
+ const chunkTopic = this.options.topicMake(name, "sink-push-request", receiver);
1962
+ const chunkMsg = this.msg.makeSinkPushChunk(requestId, name, void 0, error.message, true, this.options.id, receiver);
1963
+ const message = this.codec.encode(chunkMsg);
1964
+ await this.publishToTopic(chunkTopic, message, {
1965
+ qos: 2,
1966
+ ...options
1967
+ }).catch(() => {});
1968
+ }
1969
+ throw err;
1970
+ } finally {
1971
+ await spool.unroll();
1972
+ }
1973
+ }
2176
1974
  };
1975
+ //#endregion
1976
+ //#region dst-stage1/mqtt-plus.js
1977
+ var MQTTp = class extends SinkTrait {};
1978
+ //#endregion
1979
+ export { MQTTp as default };