@technoculture/data-bridge 0.1.1 → 0.1.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 (50) hide show
  1. package/CMakeLists.txt +10 -2
  2. package/deps/include/data_bridge/config.hpp +69 -0
  3. package/deps/include/data_bridge/protocol/crc16.hpp +8 -0
  4. package/deps/include/data_bridge/protocol/crc32.hpp +19 -0
  5. package/deps/include/data_bridge/protocol/packet.hpp +175 -0
  6. package/deps/include/data_bridge/protocol/reassembler.hpp +80 -0
  7. package/deps/include/data_bridge/transport/factory.hpp +0 -0
  8. package/deps/include/data_bridge/transport/iserial_port.hpp +15 -0
  9. package/deps/include/data_bridge/transport/serial_port.hpp +28 -0
  10. package/deps/src/CMakeLists.txt +18 -0
  11. package/deps/src/protocol/crc16.cpp +19 -0
  12. package/deps/src/protocol/packet.cpp +1 -0
  13. package/deps/src/transport/platform/linux/linux_serial.cpp +53 -0
  14. package/deps/src/transport/platform/windows/windows_serial.cpp +51 -0
  15. package/dist/index.d.mts +41 -72
  16. package/dist/index.d.ts +41 -72
  17. package/dist/index.js +196 -102
  18. package/dist/index.mjs +194 -103
  19. package/lib/index.ts +12 -160
  20. package/lib/native.ts +71 -0
  21. package/lib/reliable.ts +234 -0
  22. package/lib/resilient.ts +30 -6
  23. package/package.json +6 -4
  24. package/prebuilds/darwin-arm64/Release/data_bridge_node.node +0 -0
  25. package/src/addon.cpp +248 -137
  26. package/prebuilds/darwin-arm64/.ninja_deps +0 -0
  27. package/prebuilds/darwin-arm64/.ninja_log +0 -6
  28. package/prebuilds/darwin-arm64/CMakeCache.txt +0 -398
  29. package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeCCompiler.cmake +0 -84
  30. package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeCXXCompiler.cmake +0 -104
  31. package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeDetermineCompilerABI_C.bin +0 -0
  32. package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeDetermineCompilerABI_CXX.bin +0 -0
  33. package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CMakeSystem.cmake +0 -15
  34. package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdC/CMakeCCompilerId.c +0 -905
  35. package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdC/a.out +0 -0
  36. package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdC/apple-sdk.c +0 -1
  37. package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdCXX/CMakeCXXCompilerId.cpp +0 -920
  38. package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdCXX/a.out +0 -0
  39. package/prebuilds/darwin-arm64/CMakeFiles/4.0.3/CompilerIdCXX/apple-sdk.cpp +0 -1
  40. package/prebuilds/darwin-arm64/CMakeFiles/CMakeConfigureLog.yaml +0 -531
  41. package/prebuilds/darwin-arm64/CMakeFiles/InstallScripts.json +0 -7
  42. package/prebuilds/darwin-arm64/CMakeFiles/TargetDirectories.txt +0 -3
  43. package/prebuilds/darwin-arm64/CMakeFiles/cmake.check_cache +0 -1
  44. package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/Users/satyamtiwary/Documents/Python-Things/data-bridge/src/protocol/crc16.cpp.o +0 -0
  45. package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/Users/satyamtiwary/Documents/Python-Things/data-bridge/src/transport/platform/linux/linux_serial.cpp.o +0 -0
  46. package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/src/addon.cpp.o +0 -0
  47. package/prebuilds/darwin-arm64/CMakeFiles/data_bridge_node.dir/src/serial_wrapper.cpp.o +0 -0
  48. package/prebuilds/darwin-arm64/CMakeFiles/rules.ninja +0 -64
  49. package/prebuilds/darwin-arm64/build.ninja +0 -192
  50. package/prebuilds/darwin-arm64/cmake_install.cmake +0 -61
@@ -0,0 +1,234 @@
1
+
2
+ import { EventEmitter } from 'events';
3
+ import { SerialPort, Packet, Reassembler, Frame } from './native';
4
+
5
+ export interface DataBridgeOptions {
6
+ baudRate?: number;
7
+ maxRetries?: number;
8
+ ackTimeoutMs?: number;
9
+ fragmentSize?: number;
10
+ }
11
+
12
+ /**
13
+ * ReliableDataBridge
14
+ *
15
+ * Implements Stop-and-Wait ARQ with fragmentation/reassembly.
16
+ * Mirrors the Python bindings.wrapper.DataBridge logic.
17
+ */
18
+ export class ReliableDataBridge extends EventEmitter {
19
+ private serial: SerialPort;
20
+ private reassembler: Reassembler;
21
+ private isOpen_ = false;
22
+
23
+ // Config
24
+ private options: Required<DataBridgeOptions>;
25
+
26
+ // State
27
+ private seqId = 0;
28
+ private rxBuffer = Buffer.alloc(0);
29
+
30
+ // ARQ State
31
+ // We only process one send at a time (Stop-and-Wait)
32
+ // Map of seqId -> { resolve, reject } (though we only have one really)
33
+ private pendingAcks = new Map<number, (fragId: number) => void>();
34
+
35
+ constructor(options: DataBridgeOptions = {}) {
36
+ super();
37
+ this.serial = new SerialPort();
38
+ this.reassembler = new Reassembler();
39
+
40
+ this.options = {
41
+ baudRate: options.baudRate ?? 115200,
42
+ maxRetries: options.maxRetries ?? 10,
43
+ ackTimeoutMs: options.ackTimeoutMs ?? 500,
44
+ fragmentSize: options.fragmentSize ?? 200
45
+ };
46
+ }
47
+
48
+ static async open (port: string, baudOrOptions?: number | DataBridgeOptions, cb?: (data: Buffer) => void): Promise<ReliableDataBridge> {
49
+ let opts: DataBridgeOptions = {};
50
+ if (typeof baudOrOptions === 'number')
51
+ {
52
+ opts.baudRate = baudOrOptions;
53
+ } else if (baudOrOptions)
54
+ {
55
+ opts = baudOrOptions;
56
+ }
57
+
58
+ const bridge = new ReliableDataBridge(opts);
59
+ if (cb) bridge.on('data', cb);
60
+
61
+ await bridge.open(port);
62
+ return bridge;
63
+ }
64
+
65
+ async open (port: string, baud?: number): Promise<boolean> {
66
+ if (this.isOpen_) return true;
67
+
68
+ // Backward compatibility: override baud from options if provided
69
+ const baudRate = baud ?? this.options.baudRate;
70
+
71
+ const success = this.serial.open(port, baudRate, (data) => {
72
+ this.onData(data);
73
+ });
74
+
75
+ if (success)
76
+ {
77
+ this.isOpen_ = true;
78
+ return true;
79
+ }
80
+ throw new Error(`Failed to open ${port}`);
81
+ }
82
+
83
+ async close (): Promise<void> {
84
+ if (this.isOpen_)
85
+ {
86
+ this.serial.close();
87
+ this.isOpen_ = false;
88
+ this.emit('close');
89
+ }
90
+ }
91
+
92
+ get isOpen (): boolean {
93
+ return this.isOpen_;
94
+ }
95
+
96
+ async send (data: string | Buffer): Promise<void> {
97
+ if (!this.isOpen_) throw new Error("Port not open");
98
+
99
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
100
+
101
+ // Fragment
102
+ const fragments: Buffer[] = [];
103
+ for (let i = 0; i < buf.length; i += this.options.fragmentSize)
104
+ {
105
+ fragments.push(buf.subarray(i, i + this.options.fragmentSize));
106
+ }
107
+
108
+ const totalFrags = fragments.length;
109
+ const seq = this.seqId;
110
+ this.seqId = (this.seqId + 1) % 256;
111
+
112
+ for (let fragId = 0; fragId < totalFrags; fragId++)
113
+ {
114
+ const payload = fragments[fragId];
115
+
116
+ const packet = Packet.serialize(
117
+ Packet.TYPE_DATA,
118
+ seq,
119
+ payload,
120
+ fragId,
121
+ totalFrags
122
+ );
123
+
124
+ await this.sendWithRetry(packet, seq, fragId);
125
+ }
126
+ }
127
+
128
+ private async sendWithRetry (packet: Buffer, seq: number, fragId: number): Promise<void> {
129
+ let retries = 0;
130
+
131
+ while (retries <= this.options.maxRetries)
132
+ {
133
+ // Prepare waiter
134
+ const ackPromise = new Promise<void>((resolve, reject) => {
135
+ const handler = (ackedFragId: number) => {
136
+ if (ackedFragId === fragId) resolve();
137
+ };
138
+ this.pendingAcks.set(seq, handler);
139
+
140
+ // Timeout
141
+ setTimeout(() => {
142
+ if (this.pendingAcks.has(seq))
143
+ {
144
+ this.pendingAcks.delete(seq);
145
+ reject(new Error("Timeout"));
146
+ }
147
+ }, this.options.ackTimeoutMs);
148
+ });
149
+
150
+ // Send
151
+ this.serial.write(packet);
152
+
153
+ try
154
+ {
155
+ await ackPromise;
156
+ return; // Success
157
+ } catch (e)
158
+ {
159
+ retries++;
160
+ // console.log(`[Reliable] Retry ${retries} for seq=${seq} frag=${fragId}`);
161
+ }
162
+ }
163
+
164
+ throw new Error(`Send failed after ${this.options.maxRetries} retries`);
165
+ }
166
+
167
+ private onData (chunk: Buffer) {
168
+ this.rxBuffer = Buffer.concat([this.rxBuffer, chunk]);
169
+
170
+ while (true)
171
+ {
172
+ const { frame, remaining } = Packet.deserialize(this.rxBuffer);
173
+
174
+ if (!frame.valid)
175
+ {
176
+ if (remaining.length === this.rxBuffer.length)
177
+ {
178
+ break;
179
+ }
180
+ // Else we consumed some garbage, continue loop with remaining
181
+ this.rxBuffer = Buffer.from(remaining);
182
+ continue;
183
+ }
184
+
185
+ // Valid frame
186
+ this.rxBuffer = Buffer.from(remaining);
187
+
188
+ if (frame.header.type === Packet.TYPE_ACK)
189
+ {
190
+ const seq = frame.header.seq_id;
191
+ const frag = frame.header.fragment_id;
192
+
193
+ if (this.pendingAcks.has(seq))
194
+ {
195
+ this.pendingAcks.get(seq)!(frag);
196
+ }
197
+ } else if (frame.header.type === Packet.TYPE_DATA)
198
+ {
199
+ let shouldAck = false;
200
+
201
+ if (this.reassembler.processFragment(frame))
202
+ {
203
+ shouldAck = true;
204
+ if (this.reassembler.isComplete(frame))
205
+ {
206
+ try
207
+ {
208
+ const data = this.reassembler.getData();
209
+ this.emit('data', data);
210
+ } catch (e)
211
+ {
212
+ console.error("Error in data emission:", e);
213
+ }
214
+ }
215
+ } else if (this.reassembler.isDuplicate(frame))
216
+ {
217
+ shouldAck = true;
218
+ }
219
+
220
+ if (shouldAck)
221
+ {
222
+ const ackParams = Packet.serialize(
223
+ Packet.TYPE_ACK,
224
+ frame.header.seq_id,
225
+ Buffer.alloc(0),
226
+ frame.header.fragment_id,
227
+ frame.header.total_frags
228
+ );
229
+ this.serial.write(ackParams);
230
+ }
231
+ }
232
+ }
233
+ }
234
+ }
package/lib/resilient.ts CHANGED
@@ -38,6 +38,7 @@ interface QueuedMessage {
38
38
  }
39
39
 
40
40
  export class ResilientDataBridge extends EventEmitter {
41
+ // Use DataBridge type from index (which is reliable now)
41
42
  private bridge: DataBridge | null = null;
42
43
  private port: string;
43
44
  private options: Required<ResilientOptions>;
@@ -53,6 +54,9 @@ export class ResilientDataBridge extends EventEmitter {
53
54
  this.port = port;
54
55
  this.options = {
55
56
  baudRate: options.baudRate ?? 115200,
57
+ maxRetries: options.maxRetries ?? 10,
58
+ ackTimeoutMs: options.ackTimeoutMs ?? 500,
59
+ fragmentSize: options.fragmentSize ?? 200,
56
60
  reconnect: options.reconnect ?? true,
57
61
  reconnectDelay: options.reconnectDelay ?? 1000,
58
62
  maxReconnectDelay: options.maxReconnectDelay ?? 30000,
@@ -75,9 +79,11 @@ export class ResilientDataBridge extends EventEmitter {
75
79
  private async connect (): Promise<void> {
76
80
  try
77
81
  {
78
- this.bridge = await DataBridge.open(this.port, {
79
- baudRate: this.options.baudRate,
80
- });
82
+ // Pass full options to reliable bridge
83
+ this.bridge = new DataBridge(this.options);
84
+
85
+ // Open using just the port (options passed in constructor)
86
+ await this.bridge.open(this.port);
81
87
 
82
88
  this.isConnected = true;
83
89
  this.reconnectAttempt = 0;
@@ -88,8 +94,23 @@ export class ResilientDataBridge extends EventEmitter {
88
94
  });
89
95
 
90
96
  // Handle errors and detect disconnection
97
+ // Note: ReliableDataBridge might not emit error on single failure (it retries),
98
+ // but it will if retries exhausted or port error.
91
99
  this.bridge.on('error', (err) => {
92
100
  this.emit('error', err);
101
+ // If error is fatal (port closed/lost), handle disconnect
102
+ // ReliableDataBridge throws/emits error if max retries reached.
103
+ // We need to assume that might mean broken link or just timeouts?
104
+ // Actually SerialPort wrapper might throw/emit if underlying handle dies.
105
+ // For simplicity, treat any error as triggers for potential reconnection logic or check isOpen
106
+ if (!this.bridge?.isOpen)
107
+ {
108
+ this.handleDisconnect();
109
+ }
110
+ });
111
+
112
+ // Check if closed
113
+ this.bridge.on('close', () => {
93
114
  this.handleDisconnect();
94
115
  });
95
116
 
@@ -117,7 +138,7 @@ export class ResilientDataBridge extends EventEmitter {
117
138
  if (!this.isConnected) return;
118
139
 
119
140
  this.isConnected = false;
120
- this.bridge = null;
141
+ this.bridge = null; // Clean up old instance
121
142
  this.emit('disconnect');
122
143
 
123
144
  if (this.shouldReconnect && this.options.reconnect)
@@ -166,7 +187,7 @@ export class ResilientDataBridge extends EventEmitter {
166
187
  async send (data: Buffer | string): Promise<void> {
167
188
  const buffer = typeof data === 'string' ? Buffer.from(data) : data;
168
189
 
169
- if (this.isConnected && this.bridge)
190
+ if (this.isConnected && this.bridge && this.bridge.isOpen)
170
191
  {
171
192
  // Connected - send immediately
172
193
  try
@@ -174,7 +195,9 @@ export class ResilientDataBridge extends EventEmitter {
174
195
  await this.bridge.send(buffer);
175
196
  } catch (err)
176
197
  {
177
- // Send failed - queue it and handle disconnect
198
+ // Send failed (retries exhausted or port error)
199
+ // Queue it and trigger reconnect check
200
+ this.handleDisconnect(); // Force reconnect cycle logic
178
201
  return this.queueMessage(buffer);
179
202
  }
180
203
  } else
@@ -274,3 +297,4 @@ export class ResilientDataBridge extends EventEmitter {
274
297
  }
275
298
 
276
299
  export default ResilientDataBridge;
300
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@technoculture/data-bridge",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Guaranteed reliable serial communication for Node.js and Electron",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -18,13 +18,15 @@
18
18
  "build:native": "cmake-js compile",
19
19
  "build:ts": "tsup lib/index.ts --format cjs,esm --dts",
20
20
  "prebuild": "cmake-js compile -O prebuilds/$(node -p \"process.platform + '-' + process.arch\")",
21
+ "prepack": "node scripts/copy_deps.js && npm run build:ts",
21
22
  "prepublishOnly": "npm run build:ts",
22
23
  "test": "vitest run",
23
- "clean": "cmake-js clean && rm -rf dist prebuilds"
24
+ "clean": "cmake-js clean && rm -rf dist prebuilds deps"
24
25
  },
25
26
  "files": [
26
27
  "dist",
27
- "prebuilds",
28
+ "prebuilds/**/*.node",
29
+ "deps",
28
30
  "src",
29
31
  "lib",
30
32
  "CMakeLists.txt"
@@ -60,7 +62,7 @@
60
62
  },
61
63
  "repository": {
62
64
  "type": "git",
63
- "url": "https://github.com/aspect-labs/data-bridge"
65
+ "url": "https://github.com/technoculture/data-bridge"
64
66
  },
65
67
  "keywords": [
66
68
  "serial",