mqtt-plus 1.4.13 → 1.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/dst-stage1/mqtt-plus-sink.js +10 -2
- package/dst-stage1/mqtt-plus-source.js +26 -16
- package/dst-stage2/mqtt-plus.cjs.js +35 -18
- package/dst-stage2/mqtt-plus.esm.js +35 -18
- package/dst-stage2/mqtt-plus.umd.js +9 -9
- package/package.json +1 -1
- package/src/mqtt-plus-sink.ts +10 -2
- package/src/mqtt-plus-source.ts +27 -16
package/package.json
CHANGED
package/src/mqtt-plus-sink.ts
CHANGED
|
@@ -162,6 +162,8 @@ export class SinkTrait<T extends APISchema = APISchema> extends SourceTrait<T> {
|
|
|
162
162
|
/* utility functions for timeout management */
|
|
163
163
|
const pushTimerId = `sink-push-recv:${requestId}`
|
|
164
164
|
const refreshPushTimeout = () => this.timerRefresh(pushTimerId, () => {
|
|
165
|
+
if (streamEnded)
|
|
166
|
+
return
|
|
165
167
|
const stream = this.pushStreams.get(requestId)
|
|
166
168
|
if (stream !== undefined)
|
|
167
169
|
stream.destroy(new Error("push stream timeout"))
|
|
@@ -201,6 +203,7 @@ export class SinkTrait<T extends APISchema = APISchema> extends SourceTrait<T> {
|
|
|
201
203
|
return
|
|
202
204
|
if (chunkParsed.error !== undefined) {
|
|
203
205
|
streamEnded = true
|
|
206
|
+
clearPushTimeout()
|
|
204
207
|
readable.destroy(new Error(chunkParsed.error))
|
|
205
208
|
}
|
|
206
209
|
else {
|
|
@@ -212,6 +215,7 @@ export class SinkTrait<T extends APISchema = APISchema> extends SourceTrait<T> {
|
|
|
212
215
|
}
|
|
213
216
|
if (chunkParsed.final) {
|
|
214
217
|
streamEnded = true
|
|
218
|
+
clearPushTimeout()
|
|
215
219
|
readable.push(null)
|
|
216
220
|
}
|
|
217
221
|
}
|
|
@@ -382,6 +386,7 @@ export class SinkTrait<T extends APISchema = APISchema> extends SourceTrait<T> {
|
|
|
382
386
|
let remoteError = false
|
|
383
387
|
let pushAcked = false
|
|
384
388
|
let pushFinalized = false
|
|
389
|
+
let pushDataFinalSent = false
|
|
385
390
|
let pushFinalizeResolve!: () => void
|
|
386
391
|
let pushFinalizeReject!: (reason?: any) => void
|
|
387
392
|
const pushFinalize = new Promise<void>((resolve, reject) => {
|
|
@@ -465,6 +470,8 @@ export class SinkTrait<T extends APISchema = APISchema> extends SourceTrait<T> {
|
|
|
465
470
|
name, chunk, error, final, this.options.id, receiver)
|
|
466
471
|
const message = this.codec.encode(chunkMsg)
|
|
467
472
|
await this.publishToTopic(chunkTopic, message, { qos: 2, ...options })
|
|
473
|
+
if (error === undefined && final)
|
|
474
|
+
pushDataFinalSent = true
|
|
468
475
|
}
|
|
469
476
|
|
|
470
477
|
/* iterate over all chunks of the buffer */
|
|
@@ -491,8 +498,9 @@ export class SinkTrait<T extends APISchema = APISchema> extends SourceTrait<T> {
|
|
|
491
498
|
abortController.abort(error)
|
|
492
499
|
|
|
493
500
|
/* send error chunk only if push was acked and error did not originate from receiver
|
|
494
|
-
(before ack, the sink has no chunk handler yet and will time out on its own
|
|
495
|
-
|
|
501
|
+
(before ack, the sink has no chunk handler yet and will time out on its own;
|
|
502
|
+
after final data chunk, no additional terminal chunk should be sent) */
|
|
503
|
+
if (pushAcked && !remoteError && !pushDataFinalSent) {
|
|
496
504
|
const chunkTopic = this.options.topicMake(name, "sink-push-request", receiver)
|
|
497
505
|
const chunkMsg = this.msg.makeSinkPushChunk(requestId,
|
|
498
506
|
name, undefined, error.message, true, this.options.id, receiver)
|
package/src/mqtt-plus-source.ts
CHANGED
|
@@ -142,6 +142,13 @@ export class SourceTrait<T extends APISchema = APISchema> extends ServiceTrait<T
|
|
|
142
142
|
await this.publishToTopic(responseTopic, message, { qos: options.qos ?? 2 })
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/* create a resource spool for request cleanup */
|
|
146
|
+
const reqSpool = new Spool()
|
|
147
|
+
reqSpool.roll(() => {
|
|
148
|
+
this.onResponse.delete(`source-fetch-credit:${requestId}`)
|
|
149
|
+
this.sourceControllers.delete(requestId)
|
|
150
|
+
})
|
|
151
|
+
|
|
145
152
|
/* define abort controller and signal */
|
|
146
153
|
const abortController = new AbortController()
|
|
147
154
|
this.sourceControllers.set(requestId, abortController)
|
|
@@ -163,11 +170,11 @@ export class SourceTrait<T extends APISchema = APISchema> extends ServiceTrait<T
|
|
|
163
170
|
gate.abort()
|
|
164
171
|
this.sourceCreditGates.delete(requestId)
|
|
165
172
|
}
|
|
166
|
-
|
|
167
|
-
this.onResponse.delete(`source-fetch-credit:${requestId}`)
|
|
173
|
+
reqSpool.unroll()
|
|
168
174
|
})
|
|
169
175
|
const clearSourceTimeout = () => this.timerClear(sourceTimerId)
|
|
170
176
|
refreshSourceTimeout()
|
|
177
|
+
reqSpool.roll(() => { clearSourceTimeout() })
|
|
171
178
|
|
|
172
179
|
/* callback for creating and sending a chunk message */
|
|
173
180
|
const sendChunk = async (
|
|
@@ -185,6 +192,7 @@ export class SourceTrait<T extends APISchema = APISchema> extends ServiceTrait<T
|
|
|
185
192
|
/* call the handler callback */
|
|
186
193
|
let ackSent = false
|
|
187
194
|
let creditGate: CreditGate | undefined
|
|
195
|
+
let cancelledByFetcher = false
|
|
188
196
|
try {
|
|
189
197
|
if (topicName !== request.name)
|
|
190
198
|
throw new Error(`source name mismatch (topic: "${topicName}", payload: "${request.name}")`)
|
|
@@ -197,13 +205,19 @@ export class SourceTrait<T extends APISchema = APISchema> extends ServiceTrait<T
|
|
|
197
205
|
const initialCredit = request.credit
|
|
198
206
|
creditGate = (initialCredit !== undefined && initialCredit > 0)
|
|
199
207
|
? new CreditGate(initialCredit) : undefined
|
|
200
|
-
if (creditGate)
|
|
208
|
+
if (creditGate) {
|
|
201
209
|
this.sourceCreditGates.set(requestId, creditGate)
|
|
210
|
+
reqSpool.roll(() => {
|
|
211
|
+
creditGate!.abort()
|
|
212
|
+
this.sourceCreditGates.delete(requestId)
|
|
213
|
+
})
|
|
214
|
+
}
|
|
202
215
|
|
|
203
216
|
/* register credit/cancel handler (unconditional for cancel support) */
|
|
204
217
|
this.onResponse.set(`source-fetch-credit:${requestId}`, (creditParsed: SourceFetchCredit) => {
|
|
205
218
|
if (creditParsed.credit === 0) {
|
|
206
219
|
/* cancel signal from fetcher */
|
|
220
|
+
cancelledByFetcher = true
|
|
207
221
|
abortController.abort(new Error(`source fetch "${name}" cancelled by fetcher`))
|
|
208
222
|
return
|
|
209
223
|
}
|
|
@@ -238,22 +252,19 @@ export class SourceTrait<T extends APISchema = APISchema> extends ServiceTrait<T
|
|
|
238
252
|
const error = ensureError(err, `handler for source "${name}" failed`)
|
|
239
253
|
abortController.abort(error)
|
|
240
254
|
|
|
241
|
-
/*
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
255
|
+
/* on explicit fetcher cancellation, abort silently without emitting error responses */
|
|
256
|
+
if (!cancelledByFetcher) {
|
|
257
|
+
/* send error as nak response or as error chunk */
|
|
258
|
+
this.error(error)
|
|
259
|
+
if (ackSent)
|
|
260
|
+
await sendChunk(undefined, error.message, true).catch(() => {})
|
|
261
|
+
else
|
|
262
|
+
await sendResponse(error.message).catch(() => {})
|
|
263
|
+
}
|
|
247
264
|
}
|
|
248
265
|
finally {
|
|
249
266
|
/* cleanup resources */
|
|
250
|
-
|
|
251
|
-
if (creditGate) {
|
|
252
|
-
creditGate.abort()
|
|
253
|
-
this.sourceCreditGates.delete(requestId)
|
|
254
|
-
}
|
|
255
|
-
this.sourceControllers.delete(requestId)
|
|
256
|
-
this.onResponse.delete(`source-fetch-credit:${requestId}`)
|
|
267
|
+
await reqSpool.unroll()
|
|
257
268
|
}
|
|
258
269
|
})
|
|
259
270
|
spool.roll(() => { this.onRequest.delete(`source-fetch-request:${name}`) })
|