speechflow 0.9.0 → 0.9.2

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 (35) hide show
  1. package/README.md +30 -0
  2. package/dst/speechflow-node-deepgram.d.ts +10 -0
  3. package/dst/speechflow-node-deepgram.js +44 -23
  4. package/dst/speechflow-node-deepl.d.ts +10 -0
  5. package/dst/speechflow-node-deepl.js +30 -12
  6. package/dst/speechflow-node-device.d.ts +11 -0
  7. package/dst/speechflow-node-device.js +73 -14
  8. package/dst/speechflow-node-elevenlabs.d.ts +10 -0
  9. package/dst/speechflow-node-elevenlabs.js +14 -2
  10. package/dst/speechflow-node-ffmpeg.d.ts +11 -0
  11. package/dst/speechflow-node-ffmpeg.js +114 -0
  12. package/dst/speechflow-node-file.d.ts +9 -0
  13. package/dst/speechflow-node-file.js +71 -13
  14. package/dst/speechflow-node-gemma.d.ts +11 -0
  15. package/dst/speechflow-node-gemma.js +152 -0
  16. package/dst/speechflow-node-websocket.d.ts +11 -0
  17. package/dst/speechflow-node-websocket.js +34 -6
  18. package/dst/speechflow-node.d.ts +38 -0
  19. package/dst/speechflow-node.js +28 -10
  20. package/dst/speechflow.d.ts +1 -0
  21. package/dst/speechflow.js +128 -43
  22. package/etc/tsconfig.json +2 -0
  23. package/package.json +25 -11
  24. package/src/speechflow-node-deepgram.ts +55 -24
  25. package/src/speechflow-node-deepl.ts +38 -16
  26. package/src/speechflow-node-device.ts +88 -14
  27. package/src/speechflow-node-elevenlabs.ts +19 -2
  28. package/src/speechflow-node-ffmpeg.ts +122 -0
  29. package/src/speechflow-node-file.ts +76 -14
  30. package/src/speechflow-node-gemma.ts +169 -0
  31. package/src/speechflow-node-websocket.ts +52 -13
  32. package/src/speechflow-node.ts +43 -21
  33. package/src/speechflow.ts +144 -47
  34. package/dst/speechflow-util.js +0 -37
  35. package/src/speechflow-util.ts +0 -36
@@ -8,40 +8,98 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
8
8
  return (mod && mod.__esModule) ? mod : { "default": mod };
9
9
  };
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
+ /* standard dependencies */
11
12
  const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_stream_1 = __importDefault(require("node:stream"));
14
+ /* internal dependencies */
12
15
  const speechflow_node_1 = __importDefault(require("./speechflow-node"));
13
- class SpeechFlowNodeDevice extends speechflow_node_1.default {
16
+ /* SpeechFlow node for file access */
17
+ class SpeechFlowNodeFile extends speechflow_node_1.default {
18
+ /* declare official node name */
19
+ static name = "file";
20
+ /* construct node */
14
21
  constructor(id, opts, args) {
15
22
  super(id, opts, args);
23
+ /* declare node configuration parameters */
16
24
  this.configure({
17
25
  path: { type: "string", pos: 0 },
18
26
  mode: { type: "string", pos: 1, val: "r", match: /^(?:r|w|rw)$/ },
19
27
  type: { type: "string", pos: 2, val: "audio", match: /^(?:audio|text)$/ }
20
28
  });
29
+ /* declare node input/output format */
30
+ if (this.params.mode === "rw") {
31
+ this.input = this.params.type;
32
+ this.output = this.params.type;
33
+ }
34
+ else if (this.params.mode === "r") {
35
+ this.input = "none";
36
+ this.output = this.params.type;
37
+ }
38
+ else if (this.params.mode === "w") {
39
+ this.input = this.params.type;
40
+ this.output = "none";
41
+ }
21
42
  }
43
+ /* open node */
22
44
  async open() {
23
- if (this.params.mode === "r") {
24
- this.output = this.params.type;
25
- if (this.params.path === "-")
45
+ const encoding = this.params.type === "text" ? this.config.textEncoding : "binary";
46
+ if (this.params.mode === "rw") {
47
+ if (this.params.path === "-") {
48
+ /* standard I/O */
49
+ process.stdin.setEncoding(encoding);
50
+ process.stdout.setEncoding(encoding);
51
+ this.stream = node_stream_1.default.Duplex.from({
52
+ readable: process.stdin,
53
+ writable: process.stdout
54
+ });
55
+ }
56
+ else {
57
+ /* file I/O */
58
+ this.stream = node_stream_1.default.Duplex.from({
59
+ readable: node_fs_1.default.createReadStream(this.params.path, { encoding }),
60
+ writable: node_fs_1.default.createWriteStream(this.params.path, { encoding })
61
+ });
62
+ }
63
+ }
64
+ else if (this.params.mode === "r") {
65
+ if (this.params.path === "-") {
66
+ /* standard I/O */
67
+ process.stdin.setEncoding(encoding);
26
68
  this.stream = process.stdin;
27
- else
28
- this.stream = node_fs_1.default.createReadStream(this.params.path, { encoding: this.params.type === "text" ? this.config.textEncoding : "binary" });
69
+ }
70
+ else {
71
+ /* file I/O */
72
+ this.stream = node_fs_1.default.createReadStream(this.params.path, { encoding });
73
+ }
29
74
  }
30
75
  else if (this.params.mode === "w") {
31
- this.input = this.params.type;
32
- if (this.params.path === "-")
76
+ if (this.params.path === "-") {
77
+ /* standard I/O */
78
+ process.stdout.setEncoding(encoding);
33
79
  this.stream = process.stdout;
34
- else
35
- this.stream = node_fs_1.default.createWriteStream(this.params.path, { encoding: this.params.type === "text" ? this.config.textEncoding : "binary" });
80
+ }
81
+ else {
82
+ /* file I/O */
83
+ this.stream = node_fs_1.default.createWriteStream(this.params.path, { encoding });
84
+ }
36
85
  }
37
86
  else
38
87
  throw new Error(`invalid file mode "${this.params.mode}"`);
39
88
  }
89
+ /* close node */
40
90
  async close() {
41
- if (this.stream !== null && this.params.path !== "-") {
42
- this.stream.destroy();
91
+ /* shutdown stream */
92
+ if (this.stream !== null) {
93
+ await new Promise((resolve) => {
94
+ if (this.stream instanceof node_stream_1.default.Writable || this.stream instanceof node_stream_1.default.Duplex)
95
+ this.stream.end(() => { resolve(); });
96
+ else
97
+ resolve();
98
+ });
99
+ if (this.params.path !== "-")
100
+ this.stream.destroy();
43
101
  this.stream = null;
44
102
  }
45
103
  }
46
104
  }
47
- exports.default = SpeechFlowNodeDevice;
105
+ exports.default = SpeechFlowNodeFile;
@@ -0,0 +1,11 @@
1
+ import SpeechFlowNode from "./speechflow-node";
2
+ export default class SpeechFlowNodeGemma extends SpeechFlowNode {
3
+ static name: string;
4
+ private ollama;
5
+ private setup;
6
+ constructor(id: string, opts: {
7
+ [id: string]: any;
8
+ }, args: any[]);
9
+ open(): Promise<void>;
10
+ close(): Promise<void>;
11
+ }
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ /*
3
+ ** SpeechFlow - Speech Processing Flow Graph
4
+ ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
5
+ ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
6
+ */
7
+ var __importDefault = (this && this.__importDefault) || function (mod) {
8
+ return (mod && mod.__esModule) ? mod : { "default": mod };
9
+ };
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ /* standard dependencies */
12
+ const node_stream_1 = __importDefault(require("node:stream"));
13
+ const node_events_1 = require("node:events");
14
+ /* external dependencies */
15
+ const ollama_1 = require("ollama");
16
+ /* internal dependencies */
17
+ const speechflow_node_1 = __importDefault(require("./speechflow-node"));
18
+ /* SpeechFlow node for Gemma/Ollama text-to-text translation */
19
+ class SpeechFlowNodeGemma extends speechflow_node_1.default {
20
+ /* declare official node name */
21
+ static name = "gemma";
22
+ /* internal state */
23
+ ollama = null;
24
+ /* internal LLM setup */
25
+ setup = {
26
+ /* English (EN) to German (DE) translation */
27
+ "en-de": {
28
+ systemPrompt: "You are a translator.\n" +
29
+ "Output only the requested text.\n" +
30
+ "Do not use markdown.\n" +
31
+ "Do not chat.\n" +
32
+ "Do not show any explanations.\n" +
33
+ "Do not show any introduction.\n" +
34
+ "Do not show any preamble.\n" +
35
+ "Do not show any prolog.\n" +
36
+ "Do not show any epilog.\n" +
37
+ "Get to the point.\n" +
38
+ "Directly translate text from Enlish (EN) to German (DE) language.\n",
39
+ chat: [
40
+ { role: "user", content: "I love my wife." },
41
+ { role: "system", content: "Ich liebe meine Frau." },
42
+ { role: "user", content: "The weather is wonderful." },
43
+ { role: "system", content: "Das Wetter ist wunderschön." },
44
+ { role: "user", content: "The live is awesome." },
45
+ { role: "system", content: "Das Leben ist einfach großartig." }
46
+ ]
47
+ },
48
+ /* German (DE) to English (EN) translation */
49
+ "de-en": {
50
+ systemPrompt: "You are a translator.\n" +
51
+ "Output only the requested text.\n" +
52
+ "Do not use markdown.\n" +
53
+ "Do not chat.\n" +
54
+ "Do not show any explanations. \n" +
55
+ "Do not show any introduction.\n" +
56
+ "Do not show any preamble. \n" +
57
+ "Do not show any prolog. \n" +
58
+ "Do not show any epilog. \n" +
59
+ "Get to the point.\n" +
60
+ "Directly translate text from German (DE) to English (EN) language.\n",
61
+ chat: [
62
+ { role: "user", content: "Ich liebe meine Frau." },
63
+ { role: "system", content: "I love my wife." },
64
+ { role: "user", content: "Das Wetter ist wunderschön." },
65
+ { role: "system", content: "The weather is wonderful." },
66
+ { role: "user", content: "Das Leben ist einfach großartig." },
67
+ { role: "system", content: "The live is awesome." }
68
+ ]
69
+ }
70
+ };
71
+ /* construct node */
72
+ constructor(id, opts, args) {
73
+ super(id, opts, args);
74
+ /* declare node configuration parameters */
75
+ this.configure({
76
+ api: { type: "string", val: "http://127.0.0.1:11434", match: /^https?:\/\/.+?:\d+$/ },
77
+ src: { type: "string", pos: 0, val: "de", match: /^(?:de|en)$/ },
78
+ dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en)$/ }
79
+ });
80
+ /* sanity check situation */
81
+ if (this.params.src === this.params.dst)
82
+ throw new Error("source and destination languages cannot be the same");
83
+ /* declare node input/output format */
84
+ this.input = "text";
85
+ this.output = "text";
86
+ }
87
+ /* open node */
88
+ async open() {
89
+ /* instantiate Ollama API */
90
+ this.ollama = new ollama_1.Ollama({ host: this.params.api });
91
+ /* provide text-to-text translation */
92
+ const translate = async (text) => {
93
+ const key = `${this.params.src}-${this.params.dst}`;
94
+ const cfg = this.setup[key];
95
+ const response = await this.ollama.chat({
96
+ model: "gemma3:4b-it-q4_K_M",
97
+ messages: [
98
+ { role: "system", content: cfg.systemPrompt },
99
+ ...cfg.chat,
100
+ { role: "user", content: text }
101
+ ],
102
+ keep_alive: "10m",
103
+ options: {
104
+ repeat_penalty: 1.1,
105
+ temperature: 0.7,
106
+ seed: 1,
107
+ top_k: 10,
108
+ top_p: 0.5
109
+ }
110
+ });
111
+ return response.message.content;
112
+ };
113
+ /* establish a duplex stream and connect it to Ollama */
114
+ const queue = new node_events_1.EventEmitter();
115
+ this.stream = new node_stream_1.default.Duplex({
116
+ write(chunk, encoding, callback) {
117
+ const data = chunk.toString();
118
+ if (data === "") {
119
+ queue.emit("result", "");
120
+ callback();
121
+ }
122
+ else {
123
+ translate(data).then((result) => {
124
+ queue.emit("result", result);
125
+ callback();
126
+ }).catch((err) => {
127
+ callback(err);
128
+ });
129
+ }
130
+ },
131
+ read(size) {
132
+ queue.once("result", (result) => {
133
+ this.push(result);
134
+ });
135
+ }
136
+ });
137
+ }
138
+ /* close node */
139
+ async close() {
140
+ /* close stream */
141
+ if (this.stream !== null) {
142
+ this.stream.destroy();
143
+ this.stream = null;
144
+ }
145
+ /* shutdown Ollama */
146
+ if (this.ollama !== null) {
147
+ this.ollama.abort();
148
+ this.ollama = null;
149
+ }
150
+ }
151
+ }
152
+ exports.default = SpeechFlowNodeGemma;
@@ -0,0 +1,11 @@
1
+ import SpeechFlowNode from "./speechflow-node";
2
+ export default class SpeechFlowNodeWebsocket extends SpeechFlowNode {
3
+ static name: string;
4
+ private server;
5
+ private client;
6
+ constructor(id: string, opts: {
7
+ [id: string]: any;
8
+ }, args: any[]);
9
+ open(): Promise<void>;
10
+ close(): Promise<void>;
11
+ }
@@ -8,25 +8,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
8
8
  return (mod && mod.__esModule) ? mod : { "default": mod };
9
9
  };
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
+ /* standard dependencies */
11
12
  const node_stream_1 = __importDefault(require("node:stream"));
13
+ /* external dependencies */
12
14
  const ws_1 = __importDefault(require("ws"));
13
15
  const reconnecting_websocket_1 = __importDefault(require("@opensumi/reconnecting-websocket"));
16
+ /* internal dependencies */
14
17
  const speechflow_node_1 = __importDefault(require("./speechflow-node"));
18
+ /* SpeechFlow node for Websocket networking */
15
19
  class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
20
+ /* declare official node name */
21
+ static name = "websocket";
22
+ /* internal state */
16
23
  server = null;
17
24
  client = null;
25
+ /* construct node */
18
26
  constructor(id, opts, args) {
19
27
  super(id, opts, args);
28
+ /* declare node configuration parameters */
20
29
  this.configure({
21
30
  listen: { type: "string", val: "", match: /^(?:|ws:\/\/(.+?):(\d+))$/ },
22
31
  connect: { type: "string", val: "", match: /^(?:|ws:\/\/(.+?):(\d+)(?:\/.*)?)$/ },
23
32
  type: { type: "string", val: "text", match: /^(?:audio|text)$/ }
24
33
  });
34
+ /* sanity check usage */
35
+ if (this.params.listen !== "" && this.params.connect !== "")
36
+ throw new Error("Websocket node cannot listen and connect at the same time");
37
+ else if (this.params.listen === "" && this.params.connect === "")
38
+ throw new Error("Websocket node requires either listen or connect mode");
39
+ /* declare node input/output format */
40
+ if (this.params.listen !== "") {
41
+ this.input = "none";
42
+ this.output = this.params.type;
43
+ }
44
+ else if (this.params.connect !== "") {
45
+ this.input = this.params.type;
46
+ this.output = "none";
47
+ }
25
48
  }
49
+ /* open node */
26
50
  async open() {
27
- this.input = this.params.type;
28
- this.output = this.params.type;
29
51
  if (this.params.listen !== "") {
52
+ /* listen locally on a Websocket port */
30
53
  const url = new URL(this.params.listen);
31
54
  let websocket = null;
32
55
  const server = new ws_1.default.WebSocketServer({
@@ -49,6 +72,7 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
49
72
  this.log("error", `error on URL ${this.params.listen}: ${error.message}`);
50
73
  websocket = null;
51
74
  });
75
+ const textEncoding = this.config.textEncoding;
52
76
  this.stream = new node_stream_1.default.Duplex({
53
77
  write(chunk, encoding, callback) {
54
78
  const data = chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength);
@@ -66,7 +90,7 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
66
90
  read(size) {
67
91
  if (websocket !== null) {
68
92
  websocket.once("message", (data, isBinary) => {
69
- this.push(data, isBinary ? "binary" : "utf8");
93
+ this.push(data, isBinary ? "binary" : textEncoding);
70
94
  });
71
95
  }
72
96
  else
@@ -75,6 +99,7 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
75
99
  });
76
100
  }
77
101
  else if (this.params.connect !== "") {
102
+ /* connect remotely to a Websocket port */
78
103
  this.client = new reconnecting_websocket_1.default(this.params.connect, [], {
79
104
  WebSocket: ws_1.default,
80
105
  WebSocketOptions: {},
@@ -95,6 +120,7 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
95
120
  });
96
121
  const client = this.client;
97
122
  client.binaryType = "arraybuffer";
123
+ const textEncoding = this.config.textEncoding;
98
124
  this.stream = new node_stream_1.default.Duplex({
99
125
  write(chunk, encoding, callback) {
100
126
  const data = chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength);
@@ -111,7 +137,7 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
111
137
  if (ev.data instanceof ArrayBuffer)
112
138
  this.push(ev.data, "binary");
113
139
  else
114
- this.push(ev.data, "utf8");
140
+ this.push(ev.data, textEncoding);
115
141
  }, { once: true });
116
142
  }
117
143
  else
@@ -119,10 +145,10 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
119
145
  }
120
146
  });
121
147
  }
122
- else
123
- throw new Error("neither listen nor connect mode requested");
124
148
  }
149
+ /* close node */
125
150
  async close() {
151
+ /* close Websocket server */
126
152
  if (this.server !== null) {
127
153
  await new Promise((resolve, reject) => {
128
154
  this.server.close((error) => {
@@ -134,10 +160,12 @@ class SpeechFlowNodeWebsocket extends speechflow_node_1.default {
134
160
  });
135
161
  this.server = null;
136
162
  }
163
+ /* close Websocket client */
137
164
  if (this.client !== null) {
138
165
  this.client.close();
139
166
  this.client = null;
140
167
  }
168
+ /* close stream */
141
169
  if (this.stream !== null) {
142
170
  this.stream.destroy();
143
171
  this.stream = null;
@@ -0,0 +1,38 @@
1
+ import Events from "node:events";
2
+ import Stream from "node:stream";
3
+ export default class SpeechFlowNode extends Events.EventEmitter {
4
+ id: string;
5
+ private opts;
6
+ private args;
7
+ config: {
8
+ readonly audioChannels: 1;
9
+ readonly audioBitDepth: 16;
10
+ readonly audioLittleEndian: true;
11
+ readonly audioSampleRate: 48000;
12
+ readonly textEncoding: "utf8";
13
+ };
14
+ input: string;
15
+ output: string;
16
+ params: {
17
+ [id: string]: any;
18
+ };
19
+ stream: Stream.Writable | Stream.Readable | Stream.Duplex | null;
20
+ connectionsIn: Set<SpeechFlowNode>;
21
+ connectionsOut: Set<SpeechFlowNode>;
22
+ constructor(id: string, opts: {
23
+ [id: string]: any;
24
+ }, args: any[]);
25
+ configure(spec: {
26
+ [id: string]: {
27
+ type: string;
28
+ pos?: number;
29
+ val?: any;
30
+ match?: RegExp;
31
+ };
32
+ }): void;
33
+ connect(other: SpeechFlowNode): void;
34
+ disconnect(other: SpeechFlowNode): void;
35
+ log(level: string, msg: string, data?: any): void;
36
+ open(): Promise<void>;
37
+ close(): Promise<void>;
38
+ }
@@ -8,11 +8,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
8
8
  return (mod && mod.__esModule) ? mod : { "default": mod };
9
9
  };
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
+ /* standard dependencies */
11
12
  const node_events_1 = __importDefault(require("node:events"));
13
+ /* the base class for all SpeechFlow nodes */
12
14
  class SpeechFlowNode extends node_events_1.default.EventEmitter {
13
15
  id;
14
16
  opts;
15
17
  args;
18
+ /* general constant configuration (for reference) */
16
19
  config = {
17
20
  audioChannels: 1, /* audio mono channel */
18
21
  audioBitDepth: 16, /* audio PCM 16-bit integer */
@@ -20,58 +23,73 @@ class SpeechFlowNode extends node_events_1.default.EventEmitter {
20
23
  audioSampleRate: 48000, /* audio 48kHz sample rate */
21
24
  textEncoding: "utf8" /* UTF-8 text encoding */
22
25
  };
26
+ /* announced information */
23
27
  input = "none";
24
28
  output = "none";
25
29
  params = {};
26
30
  stream = null;
27
31
  connectionsIn = new Set();
28
32
  connectionsOut = new Set();
33
+ /* the default constructor */
29
34
  constructor(id, opts, args) {
30
35
  super();
31
36
  this.id = id;
32
37
  this.opts = opts;
33
38
  this.args = args;
34
39
  }
40
+ /* INTERNAL: utility function: create "params" attribute from constructor of sub-classes */
35
41
  configure(spec) {
36
42
  for (const name of Object.keys(spec)) {
37
43
  if (this.opts[name] !== undefined) {
44
+ /* named parameter */
38
45
  if (typeof this.opts[name] !== spec[name].type)
39
- throw new Error(`invalid type of option "${name}"`);
40
- if ("match" in spec[name] && this.opts[name].match(spec[name].match) === null)
41
- throw new Error(`invalid value of option "${name}" (has to match ${spec[name].match})`);
46
+ throw new Error(`invalid type of named parameter "${name}" ` +
47
+ `(has to be ${spec[name].type})`);
48
+ if ("match" in spec[name]
49
+ && this.opts[name].match(spec[name].match) === null)
50
+ throw new Error(`invalid value of named parameter "${name}" ` +
51
+ `(has to match ${spec[name].match})`);
42
52
  this.params[name] = this.opts[name];
43
53
  }
44
54
  else if (this.opts[name] === undefined
45
55
  && "pos" in spec[name]
56
+ && typeof spec[name].pos === "number"
46
57
  && spec[name].pos < this.args.length) {
58
+ /* positional argument */
47
59
  if (typeof this.args[spec[name].pos] !== spec[name].type)
48
- throw new Error(`invalid type of argument "${name}"`);
49
- if ("match" in spec[name] && this.args[spec[name].pos].match(spec[name].match) === null)
50
- throw new Error(`invalid value of option "${name}" (has to match ${spec[name].match})`);
60
+ throw new Error(`invalid type of positional parameter "${name}" ` +
61
+ `(has to be ${spec[name].type})`);
62
+ if ("match" in spec[name]
63
+ && this.args[spec[name].pos].match(spec[name].match) === null)
64
+ throw new Error(`invalid value of positional parameter "${name}" ` +
65
+ `(has to match ${spec[name].match})`);
51
66
  this.params[name] = this.args[spec[name].pos];
52
67
  }
53
68
  else if ("val" in spec[name] && spec[name].val !== undefined)
69
+ /* default argument */
54
70
  this.params[name] = spec[name].val;
55
71
  else
56
72
  throw new Error(`required parameter "${name}" not given`);
57
73
  }
58
74
  }
75
+ /* connect node to another one */
59
76
  connect(other) {
60
77
  this.connectionsOut.add(other);
61
78
  other.connectionsIn.add(this);
62
79
  }
80
+ /* disconnect node from another one */
63
81
  disconnect(other) {
64
82
  if (!this.connectionsOut.has(other))
65
83
  throw new Error("invalid node: not connected to this node");
66
84
  this.connectionsOut.delete(other);
67
85
  other.connectionsIn.delete(this);
68
86
  }
87
+ /* internal log function */
69
88
  log(level, msg, data) {
70
89
  this.emit("log", level, msg, data);
71
90
  }
72
- async open() {
73
- }
74
- async close() {
75
- }
91
+ /* default implementation for open/close operations */
92
+ async open() { }
93
+ async close() { }
76
94
  }
77
95
  exports.default = SpeechFlowNode;
@@ -0,0 +1 @@
1
+ export {};