speechflow 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/etc/stx.conf +54 -58
  3. package/package.json +25 -106
  4. package/{etc → speechflow-cli/etc}/eslint.mjs +1 -2
  5. package/speechflow-cli/etc/stx.conf +77 -0
  6. package/speechflow-cli/package.json +116 -0
  7. package/{src → speechflow-cli/src}/speechflow-node-a2a-gender.ts +148 -64
  8. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +217 -0
  9. package/{src → speechflow-cli/src}/speechflow-node-a2a-mute.ts +39 -11
  10. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +384 -0
  11. package/{src → speechflow-cli/src}/speechflow-node-a2a-wav.ts +27 -11
  12. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +313 -0
  13. package/{src → speechflow-cli/src}/speechflow-node-t2a-elevenlabs.ts +59 -12
  14. package/{src → speechflow-cli/src}/speechflow-node-t2a-kokoro.ts +11 -4
  15. package/{src → speechflow-cli/src}/speechflow-node-t2t-deepl.ts +9 -4
  16. package/{src → speechflow-cli/src}/speechflow-node-t2t-format.ts +2 -2
  17. package/{src → speechflow-cli/src}/speechflow-node-t2t-ollama.ts +1 -1
  18. package/{src → speechflow-cli/src}/speechflow-node-t2t-openai.ts +1 -1
  19. package/{src → speechflow-cli/src}/speechflow-node-t2t-sentence.ts +37 -20
  20. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +276 -0
  21. package/{src → speechflow-cli/src}/speechflow-node-t2t-transformers.ts +4 -3
  22. package/{src → speechflow-cli/src}/speechflow-node-x2x-filter.ts +9 -5
  23. package/{src → speechflow-cli/src}/speechflow-node-x2x-trace.ts +16 -8
  24. package/{src → speechflow-cli/src}/speechflow-node-xio-device.ts +12 -8
  25. package/{src → speechflow-cli/src}/speechflow-node-xio-file.ts +9 -3
  26. package/{src → speechflow-cli/src}/speechflow-node-xio-mqtt.ts +5 -2
  27. package/{src → speechflow-cli/src}/speechflow-node-xio-websocket.ts +12 -12
  28. package/{src → speechflow-cli/src}/speechflow-node.ts +7 -0
  29. package/{src → speechflow-cli/src}/speechflow-utils.ts +78 -44
  30. package/{src → speechflow-cli/src}/speechflow.ts +188 -53
  31. package/speechflow-ui-db/etc/eslint.mjs +106 -0
  32. package/speechflow-ui-db/etc/htmllint.json +55 -0
  33. package/speechflow-ui-db/etc/stx.conf +79 -0
  34. package/speechflow-ui-db/etc/stylelint.js +46 -0
  35. package/speechflow-ui-db/etc/stylelint.yaml +33 -0
  36. package/speechflow-ui-db/etc/tsc-client.json +30 -0
  37. package/speechflow-ui-db/etc/tsc.node.json +9 -0
  38. package/speechflow-ui-db/etc/vite-client.mts +63 -0
  39. package/speechflow-ui-db/package.d/htmllint-cli+0.0.7.patch +20 -0
  40. package/speechflow-ui-db/package.json +75 -0
  41. package/speechflow-ui-db/src/app-icon.ai +1989 -4
  42. package/speechflow-ui-db/src/app-icon.svg +26 -0
  43. package/speechflow-ui-db/src/app.styl +64 -0
  44. package/speechflow-ui-db/src/app.vue +221 -0
  45. package/speechflow-ui-db/src/index.html +23 -0
  46. package/speechflow-ui-db/src/index.ts +26 -0
  47. package/{dst/speechflow.d.ts → speechflow-ui-db/src/lib.d.ts} +5 -3
  48. package/speechflow-ui-db/src/tsconfig.json +3 -0
  49. package/speechflow-ui-st/etc/eslint.mjs +106 -0
  50. package/speechflow-ui-st/etc/htmllint.json +55 -0
  51. package/speechflow-ui-st/etc/stx.conf +79 -0
  52. package/speechflow-ui-st/etc/stylelint.js +46 -0
  53. package/speechflow-ui-st/etc/stylelint.yaml +33 -0
  54. package/speechflow-ui-st/etc/tsc-client.json +30 -0
  55. package/speechflow-ui-st/etc/tsc.node.json +9 -0
  56. package/speechflow-ui-st/etc/vite-client.mts +63 -0
  57. package/speechflow-ui-st/package.d/htmllint-cli+0.0.7.patch +20 -0
  58. package/speechflow-ui-st/package.json +79 -0
  59. package/speechflow-ui-st/src/app-icon.ai +1989 -4
  60. package/speechflow-ui-st/src/app-icon.svg +26 -0
  61. package/speechflow-ui-st/src/app.styl +64 -0
  62. package/speechflow-ui-st/src/app.vue +142 -0
  63. package/speechflow-ui-st/src/index.html +23 -0
  64. package/speechflow-ui-st/src/index.ts +26 -0
  65. package/speechflow-ui-st/src/lib.d.ts +9 -0
  66. package/speechflow-ui-st/src/tsconfig.json +3 -0
  67. package/dst/speechflow-node-a2a-ffmpeg.d.ts +0 -13
  68. package/dst/speechflow-node-a2a-ffmpeg.js +0 -153
  69. package/dst/speechflow-node-a2a-ffmpeg.js.map +0 -1
  70. package/dst/speechflow-node-a2a-gender.d.ts +0 -18
  71. package/dst/speechflow-node-a2a-gender.js +0 -271
  72. package/dst/speechflow-node-a2a-gender.js.map +0 -1
  73. package/dst/speechflow-node-a2a-meter.d.ts +0 -12
  74. package/dst/speechflow-node-a2a-meter.js +0 -155
  75. package/dst/speechflow-node-a2a-meter.js.map +0 -1
  76. package/dst/speechflow-node-a2a-mute.d.ts +0 -16
  77. package/dst/speechflow-node-a2a-mute.js +0 -91
  78. package/dst/speechflow-node-a2a-mute.js.map +0 -1
  79. package/dst/speechflow-node-a2a-vad.d.ts +0 -16
  80. package/dst/speechflow-node-a2a-vad.js +0 -285
  81. package/dst/speechflow-node-a2a-vad.js.map +0 -1
  82. package/dst/speechflow-node-a2a-wav.d.ts +0 -11
  83. package/dst/speechflow-node-a2a-wav.js +0 -195
  84. package/dst/speechflow-node-a2a-wav.js.map +0 -1
  85. package/dst/speechflow-node-a2t-deepgram.d.ts +0 -15
  86. package/dst/speechflow-node-a2t-deepgram.js +0 -255
  87. package/dst/speechflow-node-a2t-deepgram.js.map +0 -1
  88. package/dst/speechflow-node-t2a-elevenlabs.d.ts +0 -16
  89. package/dst/speechflow-node-t2a-elevenlabs.js +0 -195
  90. package/dst/speechflow-node-t2a-elevenlabs.js.map +0 -1
  91. package/dst/speechflow-node-t2a-kokoro.d.ts +0 -13
  92. package/dst/speechflow-node-t2a-kokoro.js +0 -149
  93. package/dst/speechflow-node-t2a-kokoro.js.map +0 -1
  94. package/dst/speechflow-node-t2t-deepl.d.ts +0 -15
  95. package/dst/speechflow-node-t2t-deepl.js +0 -142
  96. package/dst/speechflow-node-t2t-deepl.js.map +0 -1
  97. package/dst/speechflow-node-t2t-format.d.ts +0 -11
  98. package/dst/speechflow-node-t2t-format.js +0 -82
  99. package/dst/speechflow-node-t2t-format.js.map +0 -1
  100. package/dst/speechflow-node-t2t-ollama.d.ts +0 -13
  101. package/dst/speechflow-node-t2t-ollama.js +0 -247
  102. package/dst/speechflow-node-t2t-ollama.js.map +0 -1
  103. package/dst/speechflow-node-t2t-openai.d.ts +0 -13
  104. package/dst/speechflow-node-t2t-openai.js +0 -227
  105. package/dst/speechflow-node-t2t-openai.js.map +0 -1
  106. package/dst/speechflow-node-t2t-sentence.d.ts +0 -17
  107. package/dst/speechflow-node-t2t-sentence.js +0 -234
  108. package/dst/speechflow-node-t2t-sentence.js.map +0 -1
  109. package/dst/speechflow-node-t2t-subtitle.d.ts +0 -13
  110. package/dst/speechflow-node-t2t-subtitle.js +0 -278
  111. package/dst/speechflow-node-t2t-subtitle.js.map +0 -1
  112. package/dst/speechflow-node-t2t-transformers.d.ts +0 -14
  113. package/dst/speechflow-node-t2t-transformers.js +0 -265
  114. package/dst/speechflow-node-t2t-transformers.js.map +0 -1
  115. package/dst/speechflow-node-x2x-filter.d.ts +0 -11
  116. package/dst/speechflow-node-x2x-filter.js +0 -117
  117. package/dst/speechflow-node-x2x-filter.js.map +0 -1
  118. package/dst/speechflow-node-x2x-trace.d.ts +0 -11
  119. package/dst/speechflow-node-x2x-trace.js +0 -111
  120. package/dst/speechflow-node-x2x-trace.js.map +0 -1
  121. package/dst/speechflow-node-xio-device.d.ts +0 -13
  122. package/dst/speechflow-node-xio-device.js +0 -226
  123. package/dst/speechflow-node-xio-device.js.map +0 -1
  124. package/dst/speechflow-node-xio-file.d.ts +0 -11
  125. package/dst/speechflow-node-xio-file.js +0 -210
  126. package/dst/speechflow-node-xio-file.js.map +0 -1
  127. package/dst/speechflow-node-xio-mqtt.d.ts +0 -13
  128. package/dst/speechflow-node-xio-mqtt.js +0 -185
  129. package/dst/speechflow-node-xio-mqtt.js.map +0 -1
  130. package/dst/speechflow-node-xio-websocket.d.ts +0 -13
  131. package/dst/speechflow-node-xio-websocket.js +0 -278
  132. package/dst/speechflow-node-xio-websocket.js.map +0 -1
  133. package/dst/speechflow-node.d.ts +0 -65
  134. package/dst/speechflow-node.js +0 -180
  135. package/dst/speechflow-node.js.map +0 -1
  136. package/dst/speechflow-utils.d.ts +0 -69
  137. package/dst/speechflow-utils.js +0 -486
  138. package/dst/speechflow-utils.js.map +0 -1
  139. package/dst/speechflow.js +0 -768
  140. package/dst/speechflow.js.map +0 -1
  141. package/src/speechflow-node-a2a-meter.ts +0 -130
  142. package/src/speechflow-node-a2a-vad.ts +0 -285
  143. package/src/speechflow-node-a2t-deepgram.ts +0 -234
  144. package/src/speechflow-node-t2t-subtitle.ts +0 -149
  145. /package/{etc → speechflow-cli/etc}/biome.jsonc +0 -0
  146. /package/{etc → speechflow-cli/etc}/oxlint.jsonc +0 -0
  147. /package/{etc → speechflow-cli/etc}/speechflow.bat +0 -0
  148. /package/{etc → speechflow-cli/etc}/speechflow.sh +0 -0
  149. /package/{etc → speechflow-cli/etc}/speechflow.yaml +0 -0
  150. /package/{etc → speechflow-cli/etc}/tsconfig.json +0 -0
  151. /package/{package.d → speechflow-cli/package.d}/@ericedouard+vad-node-realtime+0.2.0.patch +0 -0
  152. /package/{src → speechflow-cli/src}/lib.d.ts +0 -0
  153. /package/{src → speechflow-cli/src}/speechflow-logo.ai +0 -0
  154. /package/{src → speechflow-cli/src}/speechflow-logo.svg +0 -0
  155. /package/{src → speechflow-cli/src}/speechflow-node-a2a-ffmpeg.ts +0 -0
  156. /package/{tsconfig.json → speechflow-cli/tsconfig.json} +0 -0
@@ -64,15 +64,15 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
64
64
  const url = new URL(this.params.listen)
65
65
  const websockets = new Set<ws.WebSocket>()
66
66
  const chunkQueue = new utils.SingleQueue<SpeechFlowChunk>()
67
- const server = new ws.WebSocketServer({
67
+ this.server = new ws.WebSocketServer({
68
68
  host: url.hostname,
69
69
  port: Number.parseInt(url.port),
70
70
  path: url.pathname
71
71
  })
72
- server.on("listening", () => {
72
+ this.server.on("listening", () => {
73
73
  this.log("info", `listening on URL ${this.params.listen}`)
74
74
  })
75
- server.on("connection", (ws, request) => {
75
+ this.server.on("connection", (ws, request) => {
76
76
  const peer = `${request.socket.remoteAddress}:${request.socket.remotePort}`
77
77
  this.log("info", `connection opened on URL ${this.params.listen} by peer ${peer}`)
78
78
  websockets.add(ws)
@@ -105,7 +105,7 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
105
105
  chunkQueue.write(chunk)
106
106
  })
107
107
  })
108
- server.on("error", (error) => {
108
+ this.server.on("error", (error) => {
109
109
  this.log("error", `error of some connection on URL ${this.params.listen}: ${error.message}`)
110
110
  })
111
111
  const type = this.params.type
@@ -124,7 +124,7 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
124
124
  callback(new Error("still no Websocket connections available"))
125
125
  else {
126
126
  const data = utils.streamChunkEncode(chunk)
127
- const results = []
127
+ const results: Promise<void>[] = []
128
128
  for (const websocket of websockets.values()) {
129
129
  results.push(new Promise<void>((resolve, reject) => {
130
130
  websocket.send(data, (error) => {
@@ -175,12 +175,12 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
175
175
  const chunkQueue = new utils.SingleQueue<SpeechFlowChunk>()
176
176
  this.client.addEventListener("message", (ev: MessageEvent) => {
177
177
  if (this.params.mode === "w") {
178
- this.log("warning", `connection to URL ${this.params.listen}: ` +
178
+ this.log("warning", `connection to URL ${this.params.connect}: ` +
179
179
  "received remote data on write-only node")
180
180
  return
181
181
  }
182
182
  if (!(ev.data instanceof ArrayBuffer)) {
183
- this.log("warning", `connection to URL ${this.params.listen}: ` +
183
+ this.log("warning", `connection to URL ${this.params.connect}: ` +
184
184
  "received non-binary message")
185
185
  return
186
186
  }
@@ -204,15 +204,15 @@ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
204
204
  callback(new Error(`written chunk is not of ${type} type`))
205
205
  else if (!client.OPEN)
206
206
  callback(new Error("still no Websocket connection available"))
207
- const data = utils.streamChunkEncode(chunk)
208
- client.send(data)
209
- callback()
207
+ else {
208
+ const data = utils.streamChunkEncode(chunk)
209
+ client.send(data)
210
+ callback()
211
+ }
210
212
  },
211
213
  read (size: number) {
212
214
  if (mode === "w")
213
215
  throw new Error("read operation on write-only node")
214
- if (!client.OPEN)
215
- throw new Error("still no Websocket connection available")
216
216
  chunkQueue.read().then((chunk) => {
217
217
  this.push(chunk, "binary")
218
218
  })
@@ -96,6 +96,13 @@ export default class SpeechFlowNode extends Events.EventEmitter {
96
96
  this.emit("send-response", args)
97
97
  }
98
98
 
99
+ /* emit dashboard information */
100
+ dashboardInfo (type: "audio", id: string, kind: "final" | "intermediate", value: number): void
101
+ dashboardInfo (type: "text", id: string, kind: "final" | "intermediate", value: string): void
102
+ dashboardInfo (type: "audio" | "text", id: string, kind: "final" | "intermediate", value: number | string): void {
103
+ this.emit("dashboard-info", { type, id, kind, value })
104
+ }
105
+
99
106
  /* INTERNAL: utility function: create "params" attribute from constructor of sub-classes */
100
107
  configure (spec: { [ id: string ]: { type: string, pos?: number, val?: any, match?: RegExp | ((x: any) => boolean) } }) {
101
108
  for (const name of Object.keys(spec)) {
@@ -24,11 +24,19 @@ export function audioBufferDuration (
24
24
  channels = 1,
25
25
  littleEndian = true
26
26
  ) {
27
+ /* sanity check parameters */
27
28
  if (!Buffer.isBuffer(buffer))
28
29
  throw new Error("invalid input (Buffer expected)")
29
30
  if (littleEndian !== true)
30
31
  throw new Error("only Little Endian supported")
32
+ if (sampleRate <= 0)
33
+ throw new Error("sample rate must be positive")
34
+ if (bitDepth <= 0 || bitDepth % 8 !== 0)
35
+ throw new Error("bit depth must be positive and multiple of 8")
36
+ if (channels <= 0)
37
+ throw new Error("channels must be positive")
31
38
 
39
+ /* calculate duration */
32
40
  const bytesPerSample = bitDepth / 8
33
41
  const totalSamples = buffer.length / (bytesPerSample * channels)
34
42
  return totalSamples / sampleRate
@@ -40,12 +48,23 @@ export function audioArrayDuration (
40
48
  sampleRate = 48000,
41
49
  channels = 1
42
50
  ) {
51
+ /* sanity check parameters */
52
+ if (arr.length === 0)
53
+ return 0
54
+ if (sampleRate <= 0)
55
+ throw new Error("sample rate must be positive")
56
+ if (channels <= 0)
57
+ throw new Error("channels must be positive")
58
+
59
+ /* calculate duration */
43
60
  const totalSamples = arr.length / channels
44
61
  return totalSamples / sampleRate
45
62
  }
46
63
 
47
64
  /* helper function: convert Buffer in PCM/I16 to Float32Array in PCM/F32 format */
48
65
  export function convertBufToF32 (buf: Buffer, littleEndian = true) {
66
+ if (buf.length % 2 !== 0)
67
+ throw new Error("buffer length must be even for 16-bit samples")
49
68
  const dataView = new DataView(buf.buffer)
50
69
  const arr = new Float32Array(buf.length / 2)
51
70
  for (let i = 0; i < arr.length; i++)
@@ -55,9 +74,15 @@ export function convertBufToF32 (buf: Buffer, littleEndian = true) {
55
74
 
56
75
  /* helper function: convert Float32Array in PCM/F32 to Buffer in PCM/I16 format */
57
76
  export function convertF32ToBuf (arr: Float32Array) {
77
+ if (arr.length === 0)
78
+ return Buffer.alloc(0)
58
79
  const int16Array = new Int16Array(arr.length)
59
- for (let i = 0; i < arr.length; i++)
60
- int16Array[i] = Math.max(-32768, Math.min(32767, Math.round(arr[i] * 32768)))
80
+ for (let i = 0; i < arr.length; i++) {
81
+ let sample = arr[i]
82
+ if (Number.isNaN(sample))
83
+ sample = 0
84
+ int16Array[i] = Math.max(-32768, Math.min(32767, Math.round(sample * 32768)))
85
+ }
61
86
  return Buffer.from(int16Array.buffer)
62
87
  }
63
88
 
@@ -274,26 +299,19 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
274
299
  }
275
300
  position (index?: number): number {
276
301
  if (index !== undefined) {
277
- this.index = index
278
- if (this.index < 0)
279
- this.index = 0
280
- else if (this.index >= this.queue.elements.length)
281
- this.index = this.queue.elements.length
302
+ this.index = Math.max(0, Math.min(index, this.queue.elements.length))
282
303
  this.emit("position", this.index)
283
304
  }
284
305
  return this.index
285
306
  }
286
307
  walk (num: number) {
287
- if (num > 0) {
288
- for (let i = 0; i < num && this.index < this.queue.elements.length; i++)
289
- this.index++
308
+ const indexOld = this.index
309
+ if (num > 0)
310
+ this.index = Math.min(this.index + num, this.queue.elements.length)
311
+ else if (num < 0)
312
+ this.index = Math.max(this.index + num, 0)
313
+ if (this.index !== indexOld)
290
314
  this.emit("position", { start: this.index })
291
- }
292
- else if (num < 0) {
293
- for (let i = 0; i < Math.abs(num) && this.index > 0; i++)
294
- this.index--
295
- this.emit("position", { start: this.index })
296
- }
297
315
  }
298
316
  walkForwardUntil (type: T["type"]) {
299
317
  while (this.index < this.queue.elements.length
@@ -330,12 +348,7 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
330
348
  peek (position?: number) {
331
349
  if (position === undefined)
332
350
  position = this.index
333
- else {
334
- if (position < 0)
335
- position = 0
336
- else if (position > this.queue.elements.length)
337
- position = this.queue.elements.length
338
- }
351
+ position = Math.max(0, Math.min(position, this.queue.elements.length))
339
352
  const element = this.queue.elements[position]
340
353
  this.queue.emit("read", { start: position, end: position })
341
354
  return element
@@ -351,11 +364,8 @@ export class QueuePointer<T extends QueueElement> extends EventEmitter {
351
364
  let slice: T[]
352
365
  const start = this.index
353
366
  if (size !== undefined) {
354
- if (size < 0)
355
- size = 0
356
- else if (size > this.queue.elements.length - this.index)
357
- size = this.queue.elements.length - this.index
358
- slice = this.queue.elements.slice(this.index, size)
367
+ size = Math.max(0, Math.min(size, this.queue.elements.length - this.index))
368
+ slice = this.queue.elements.slice(this.index, this.index + size)
359
369
  this.index += size
360
370
  }
361
371
  else {
@@ -415,45 +425,58 @@ export class Queue<T extends QueueElement> extends EventEmitter {
415
425
  min = pointer.position()
416
426
 
417
427
  /* trim the maximum amount of first elements */
418
- this.elements.splice(0, min)
428
+ if (min > 0) {
429
+ this.elements.splice(0, min)
419
430
 
420
- /* shift all pointers */
421
- for (const pointer of this.pointers.values())
422
- pointer.position(pointer.position() - min)
431
+ /* shift all pointers */
432
+ for (const pointer of this.pointers.values())
433
+ pointer.position(pointer.position() - min)
434
+ }
423
435
  }
424
436
  }
425
437
 
426
438
  /* utility class for wrapping a custom stream into a regular Transform stream */
427
439
  export class StreamWrapper extends Stream.Transform {
428
440
  private foreignStream: any
441
+ private onData = (chunk: any) => { this.push(chunk) }
442
+ private onError = (err: Error) => { this.emit("error", err) }
443
+ private onEnd = () => { this.push(null) }
429
444
  constructor (foreignStream: any, options: Stream.TransformOptions = {}) {
430
445
  options.readableObjectMode = true
431
446
  options.writableObjectMode = true
432
447
  super(options)
433
448
  this.foreignStream = foreignStream
434
- this.foreignStream.on("data", (chunk: any) => {
435
- this.push(chunk)
436
- })
437
- this.foreignStream.on("error", (err: Error) => {
438
- this.emit("error", err)
439
- })
440
- this.foreignStream.on("end", () => {
441
- this.push(null)
442
- })
449
+ if (typeof this.foreignStream.on === "function") {
450
+ this.foreignStream.on("data", this.onData)
451
+ this.foreignStream.on("error", this.onError)
452
+ this.foreignStream.on("end", this.onEnd)
453
+ }
443
454
  }
444
455
  _transform (chunk: any, encoding: BufferEncoding, callback: Stream.TransformCallback): void {
456
+ if (this.destroyed) {
457
+ callback(new Error("stream already destroyed"))
458
+ return
459
+ }
445
460
  try {
446
- const canContinue = this.foreignStream.write(chunk)
447
- if (canContinue)
448
- callback()
461
+ if (typeof this.foreignStream.write === "function") {
462
+ const canContinue = this.foreignStream.write(chunk)
463
+ if (canContinue)
464
+ callback()
465
+ else
466
+ this.foreignStream.once("drain", callback)
467
+ }
449
468
  else
450
- this.foreignStream.once("drain", callback)
469
+ throw new Error("foreign stream lacks write method")
451
470
  }
452
471
  catch (err) {
453
472
  callback(err as Error)
454
473
  }
455
474
  }
456
475
  _flush (callback: Stream.TransformCallback): void {
476
+ if (this.destroyed) {
477
+ callback(new Error("stream already destroyed"))
478
+ return
479
+ }
457
480
  try {
458
481
  if (typeof this.foreignStream.end === "function")
459
482
  this.foreignStream.end()
@@ -463,6 +486,14 @@ export class StreamWrapper extends Stream.Transform {
463
486
  callback(err as Error)
464
487
  }
465
488
  }
489
+ _destroy (error: Error | null, callback: Stream.TransformCallback): void {
490
+ if (typeof this.foreignStream.removeListener === "function") {
491
+ this.foreignStream.removeListener("data", this.onData)
492
+ this.foreignStream.removeListener("error", this.onError)
493
+ this.foreignStream.removeListener("end", this.onEnd)
494
+ }
495
+ super._destroy(error, callback)
496
+ }
466
497
  }
467
498
 
468
499
  /* meta store */
@@ -485,4 +516,7 @@ export class TimeStore<T> extends EventEmitter {
485
516
  if (interval.low < before && interval.high < before)
486
517
  this.tree.remove(interval)
487
518
  }
519
+ clear (): void {
520
+ this.tree = new IntervalTree.IntervalTree<TimeStoreInterval<T>>()
521
+ }
488
522
  }