@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
package/dist/index.d.ts CHANGED
@@ -1,5 +1,45 @@
1
1
  import { EventEmitter } from 'events';
2
2
 
3
+ interface DataBridgeOptions {
4
+ baudRate?: number;
5
+ maxRetries?: number;
6
+ ackTimeoutMs?: number;
7
+ fragmentSize?: number;
8
+ }
9
+ /**
10
+ * ReliableDataBridge
11
+ *
12
+ * Implements Stop-and-Wait ARQ with fragmentation/reassembly.
13
+ * Mirrors the Python bindings.wrapper.DataBridge logic.
14
+ */
15
+ declare class ReliableDataBridge extends EventEmitter {
16
+ private serial;
17
+ private reassembler;
18
+ private isOpen_;
19
+ private options;
20
+ private seqId;
21
+ private rxBuffer;
22
+ private pendingAcks;
23
+ constructor(options?: DataBridgeOptions);
24
+ static open(port: string, baudOrOptions?: number | DataBridgeOptions, cb?: (data: Buffer) => void): Promise<ReliableDataBridge>;
25
+ open(port: string, baud?: number): Promise<boolean>;
26
+ close(): Promise<void>;
27
+ get isOpen(): boolean;
28
+ send(data: string | Buffer): Promise<void>;
29
+ private sendWithRetry;
30
+ private onData;
31
+ }
32
+
33
+ interface SerialPort {
34
+ open(port: string, baud: number, callback: (data: Buffer) => void): boolean;
35
+ write(data: Buffer): number;
36
+ close(): boolean;
37
+ isOpen(): boolean;
38
+ }
39
+ declare const SerialPort: {
40
+ new (): SerialPort;
41
+ };
42
+
3
43
  /**
4
44
  * ResilientDataBridge - Connection-resilient wrapper
5
45
  *
@@ -74,75 +114,4 @@ declare class ResilientDataBridge extends EventEmitter {
74
114
  emit<K extends keyof ResilientEvents>(event: K, ...args: Parameters<ResilientEvents[K]>): boolean;
75
115
  }
76
116
 
77
- /**
78
- * Data Bridge - Guaranteed Reliable Serial Communication
79
- *
80
- * TypeScript wrapper for the native Node-API addon.
81
- * Provides a clean, async-friendly API for Electron applications.
82
- */
83
-
84
- interface DataBridgeOptions {
85
- baudRate?: number;
86
- }
87
- interface DataBridgeEvents {
88
- data: (data: Buffer) => void;
89
- error: (error: Error) => void;
90
- close: () => void;
91
- }
92
- /**
93
- * DataBridge provides guaranteed reliable serial communication.
94
- *
95
- * Features:
96
- * - Automatic retransmission on packet loss
97
- * - CRC32 integrity checking
98
- * - COBS framing for robust delimitation
99
- * - Fragmentation for large messages
100
- *
101
- * @example
102
- * ```typescript
103
- * const bridge = await DataBridge.open('/dev/ttyUSB0');
104
- *
105
- * bridge.on('data', (data) => {
106
- * console.log('Received:', data.toString());
107
- * });
108
- *
109
- * await bridge.send('Hello, World!');
110
- * await bridge.close();
111
- * ```
112
- */
113
- declare class DataBridge extends EventEmitter {
114
- private native;
115
- private _isOpen;
116
- private constructor();
117
- /**
118
- * Open a serial port with guaranteed reliable communication.
119
- *
120
- * @param port - Serial port path (e.g., '/dev/ttyUSB0' or 'COM3')
121
- * @param options - Configuration options
122
- * @returns Promise resolving to a DataBridge instance
123
- */
124
- static open(port: string, options?: DataBridgeOptions): Promise<DataBridge>;
125
- /**
126
- * Send data with guaranteed delivery.
127
- *
128
- * The data will be fragmented if necessary, checksummed, and
129
- * retransmitted until acknowledged by the receiver.
130
- *
131
- * @param data - Data to send (Buffer or string)
132
- * @returns Promise resolving when data is acknowledged
133
- */
134
- send(data: Buffer | string): Promise<void>;
135
- /**
136
- * Close the serial port.
137
- */
138
- close(): Promise<void>;
139
- /**
140
- * Check if the port is currently open.
141
- */
142
- get isOpen(): boolean;
143
- on<K extends keyof DataBridgeEvents>(event: K, listener: DataBridgeEvents[K]): this;
144
- once<K extends keyof DataBridgeEvents>(event: K, listener: DataBridgeEvents[K]): this;
145
- emit<K extends keyof DataBridgeEvents>(event: K, ...args: Parameters<DataBridgeEvents[K]>): boolean;
146
- }
147
-
148
- export { DataBridge, type DataBridgeEvents, type DataBridgeOptions, ResilientDataBridge, type ResilientEvents, type ResilientOptions, DataBridge as default };
117
+ export { ReliableDataBridge as DataBridge, type DataBridgeOptions, SerialPort as RawSerialPort, ResilientDataBridge, type ResilientEvents, type ResilientOptions };
package/dist/index.js CHANGED
@@ -30,17 +30,194 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // lib/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- DataBridge: () => DataBridge,
34
- ResilientDataBridge: () => ResilientDataBridge,
35
- default: () => index_default
33
+ DataBridge: () => ReliableDataBridge,
34
+ RawSerialPort: () => SerialPort,
35
+ ResilientDataBridge: () => ResilientDataBridge
36
36
  });
37
37
  module.exports = __toCommonJS(index_exports);
38
- var import_events2 = require("events");
38
+
39
+ // lib/reliable.ts
40
+ var import_events = require("events");
41
+
42
+ // lib/native.ts
39
43
  var import_path = __toESM(require("path"));
44
+ function loadAddon() {
45
+ const possiblePaths = [
46
+ `../prebuilds/${process.platform}-${process.arch}/data_bridge_node.node`,
47
+ "../build/Release/data_bridge_node.node"
48
+ ];
49
+ for (const p of possiblePaths) {
50
+ try {
51
+ return require(import_path.default.join(__dirname, p));
52
+ } catch {
53
+ continue;
54
+ }
55
+ }
56
+ throw new Error("Failed to load native addon. Run `npm run build` first.");
57
+ }
58
+ var addon = loadAddon();
59
+ var SerialPort = addon.SerialPort;
60
+ var Packet = addon.Packet;
61
+ var Reassembler = addon.Reassembler;
62
+
63
+ // lib/reliable.ts
64
+ var ReliableDataBridge = class _ReliableDataBridge extends import_events.EventEmitter {
65
+ serial;
66
+ reassembler;
67
+ isOpen_ = false;
68
+ // Config
69
+ options;
70
+ // State
71
+ seqId = 0;
72
+ rxBuffer = Buffer.alloc(0);
73
+ // ARQ State
74
+ // We only process one send at a time (Stop-and-Wait)
75
+ // Map of seqId -> { resolve, reject } (though we only have one really)
76
+ pendingAcks = /* @__PURE__ */ new Map();
77
+ constructor(options = {}) {
78
+ super();
79
+ this.serial = new SerialPort();
80
+ this.reassembler = new Reassembler();
81
+ this.options = {
82
+ baudRate: options.baudRate ?? 115200,
83
+ maxRetries: options.maxRetries ?? 10,
84
+ ackTimeoutMs: options.ackTimeoutMs ?? 500,
85
+ fragmentSize: options.fragmentSize ?? 200
86
+ };
87
+ }
88
+ static async open(port, baudOrOptions, cb) {
89
+ let opts = {};
90
+ if (typeof baudOrOptions === "number") {
91
+ opts.baudRate = baudOrOptions;
92
+ } else if (baudOrOptions) {
93
+ opts = baudOrOptions;
94
+ }
95
+ const bridge = new _ReliableDataBridge(opts);
96
+ if (cb) bridge.on("data", cb);
97
+ await bridge.open(port);
98
+ return bridge;
99
+ }
100
+ async open(port, baud) {
101
+ if (this.isOpen_) return true;
102
+ const baudRate = baud ?? this.options.baudRate;
103
+ const success = this.serial.open(port, baudRate, (data) => {
104
+ this.onData(data);
105
+ });
106
+ if (success) {
107
+ this.isOpen_ = true;
108
+ return true;
109
+ }
110
+ throw new Error(`Failed to open ${port}`);
111
+ }
112
+ async close() {
113
+ if (this.isOpen_) {
114
+ this.serial.close();
115
+ this.isOpen_ = false;
116
+ this.emit("close");
117
+ }
118
+ }
119
+ get isOpen() {
120
+ return this.isOpen_;
121
+ }
122
+ async send(data) {
123
+ if (!this.isOpen_) throw new Error("Port not open");
124
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
125
+ const fragments = [];
126
+ for (let i = 0; i < buf.length; i += this.options.fragmentSize) {
127
+ fragments.push(buf.subarray(i, i + this.options.fragmentSize));
128
+ }
129
+ const totalFrags = fragments.length;
130
+ const seq = this.seqId;
131
+ this.seqId = (this.seqId + 1) % 256;
132
+ for (let fragId = 0; fragId < totalFrags; fragId++) {
133
+ const payload = fragments[fragId];
134
+ const packet = Packet.serialize(
135
+ Packet.TYPE_DATA,
136
+ seq,
137
+ payload,
138
+ fragId,
139
+ totalFrags
140
+ );
141
+ await this.sendWithRetry(packet, seq, fragId);
142
+ }
143
+ }
144
+ async sendWithRetry(packet, seq, fragId) {
145
+ let retries = 0;
146
+ while (retries <= this.options.maxRetries) {
147
+ const ackPromise = new Promise((resolve, reject) => {
148
+ const handler = (ackedFragId) => {
149
+ if (ackedFragId === fragId) resolve();
150
+ };
151
+ this.pendingAcks.set(seq, handler);
152
+ setTimeout(() => {
153
+ if (this.pendingAcks.has(seq)) {
154
+ this.pendingAcks.delete(seq);
155
+ reject(new Error("Timeout"));
156
+ }
157
+ }, this.options.ackTimeoutMs);
158
+ });
159
+ this.serial.write(packet);
160
+ try {
161
+ await ackPromise;
162
+ return;
163
+ } catch (e) {
164
+ retries++;
165
+ }
166
+ }
167
+ throw new Error(`Send failed after ${this.options.maxRetries} retries`);
168
+ }
169
+ onData(chunk) {
170
+ this.rxBuffer = Buffer.concat([this.rxBuffer, chunk]);
171
+ while (true) {
172
+ const { frame, remaining } = Packet.deserialize(this.rxBuffer);
173
+ if (!frame.valid) {
174
+ if (remaining.length === this.rxBuffer.length) {
175
+ break;
176
+ }
177
+ this.rxBuffer = Buffer.from(remaining);
178
+ continue;
179
+ }
180
+ this.rxBuffer = Buffer.from(remaining);
181
+ if (frame.header.type === Packet.TYPE_ACK) {
182
+ const seq = frame.header.seq_id;
183
+ const frag = frame.header.fragment_id;
184
+ if (this.pendingAcks.has(seq)) {
185
+ this.pendingAcks.get(seq)(frag);
186
+ }
187
+ } else if (frame.header.type === Packet.TYPE_DATA) {
188
+ let shouldAck = false;
189
+ if (this.reassembler.processFragment(frame)) {
190
+ shouldAck = true;
191
+ if (this.reassembler.isComplete(frame)) {
192
+ try {
193
+ const data = this.reassembler.getData();
194
+ this.emit("data", data);
195
+ } catch (e) {
196
+ console.error("Error in data emission:", e);
197
+ }
198
+ }
199
+ } else if (this.reassembler.isDuplicate(frame)) {
200
+ shouldAck = true;
201
+ }
202
+ if (shouldAck) {
203
+ const ackParams = Packet.serialize(
204
+ Packet.TYPE_ACK,
205
+ frame.header.seq_id,
206
+ Buffer.alloc(0),
207
+ frame.header.fragment_id,
208
+ frame.header.total_frags
209
+ );
210
+ this.serial.write(ackParams);
211
+ }
212
+ }
213
+ }
214
+ }
215
+ };
40
216
 
41
217
  // lib/resilient.ts
42
- var import_events = require("events");
43
- var ResilientDataBridge = class _ResilientDataBridge extends import_events.EventEmitter {
218
+ var import_events2 = require("events");
219
+ var ResilientDataBridge = class _ResilientDataBridge extends import_events2.EventEmitter {
220
+ // Use DataBridge type from index (which is reliable now)
44
221
  bridge = null;
45
222
  port;
46
223
  options;
@@ -55,6 +232,9 @@ var ResilientDataBridge = class _ResilientDataBridge extends import_events.Event
55
232
  this.port = port;
56
233
  this.options = {
57
234
  baudRate: options.baudRate ?? 115200,
235
+ maxRetries: options.maxRetries ?? 10,
236
+ ackTimeoutMs: options.ackTimeoutMs ?? 500,
237
+ fragmentSize: options.fragmentSize ?? 200,
58
238
  reconnect: options.reconnect ?? true,
59
239
  reconnectDelay: options.reconnectDelay ?? 1e3,
60
240
  maxReconnectDelay: options.maxReconnectDelay ?? 3e4,
@@ -74,9 +254,8 @@ var ResilientDataBridge = class _ResilientDataBridge extends import_events.Event
74
254
  }
75
255
  async connect() {
76
256
  try {
77
- this.bridge = await DataBridge.open(this.port, {
78
- baudRate: this.options.baudRate
79
- });
257
+ this.bridge = new ReliableDataBridge(this.options);
258
+ await this.bridge.open(this.port);
80
259
  this.isConnected = true;
81
260
  this.reconnectAttempt = 0;
82
261
  this.bridge.on("data", (data) => {
@@ -84,6 +263,11 @@ var ResilientDataBridge = class _ResilientDataBridge extends import_events.Event
84
263
  });
85
264
  this.bridge.on("error", (err) => {
86
265
  this.emit("error", err);
266
+ if (!this.bridge?.isOpen) {
267
+ this.handleDisconnect();
268
+ }
269
+ });
270
+ this.bridge.on("close", () => {
87
271
  this.handleDisconnect();
88
272
  });
89
273
  if (this.isReconnecting) {
@@ -139,10 +323,11 @@ var ResilientDataBridge = class _ResilientDataBridge extends import_events.Event
139
323
  */
140
324
  async send(data) {
141
325
  const buffer = typeof data === "string" ? Buffer.from(data) : data;
142
- if (this.isConnected && this.bridge) {
326
+ if (this.isConnected && this.bridge && this.bridge.isOpen) {
143
327
  try {
144
328
  await this.bridge.send(buffer);
145
329
  } catch (err) {
330
+ this.handleDisconnect();
146
331
  return this.queueMessage(buffer);
147
332
  }
148
333
  } else {
@@ -215,100 +400,9 @@ var ResilientDataBridge = class _ResilientDataBridge extends import_events.Event
215
400
  return super.emit(event, ...args);
216
401
  }
217
402
  };
218
-
219
- // lib/index.ts
220
- var addon;
221
- function loadAddon() {
222
- const possiblePaths = [
223
- // Prebuilt binaries
224
- `../prebuilds/${process.platform}-${process.arch}/data_bridge_node.node`,
225
- // Local build
226
- "../build/Release/data_bridge_node.node"
227
- ];
228
- for (const p of possiblePaths) {
229
- try {
230
- return require(import_path.default.join(__dirname, p));
231
- } catch {
232
- continue;
233
- }
234
- }
235
- throw new Error("Failed to load native addon. Run `npm run build` first.");
236
- }
237
- addon = loadAddon();
238
- var DataBridge = class _DataBridge extends import_events2.EventEmitter {
239
- native;
240
- _isOpen = false;
241
- constructor() {
242
- super();
243
- this.native = new addon.DataBridge();
244
- }
245
- /**
246
- * Open a serial port with guaranteed reliable communication.
247
- *
248
- * @param port - Serial port path (e.g., '/dev/ttyUSB0' or 'COM3')
249
- * @param options - Configuration options
250
- * @returns Promise resolving to a DataBridge instance
251
- */
252
- static async open(port, options = {}) {
253
- const instance = new _DataBridge();
254
- const baud = options.baudRate ?? 115200;
255
- const onData = (data) => {
256
- instance.emit("data", data);
257
- };
258
- try {
259
- await instance.native.open(port, baud, onData);
260
- instance._isOpen = true;
261
- return instance;
262
- } catch (err) {
263
- throw new Error(`Failed to open ${port}: ${err instanceof Error ? err.message : err}`);
264
- }
265
- }
266
- /**
267
- * Send data with guaranteed delivery.
268
- *
269
- * The data will be fragmented if necessary, checksummed, and
270
- * retransmitted until acknowledged by the receiver.
271
- *
272
- * @param data - Data to send (Buffer or string)
273
- * @returns Promise resolving when data is acknowledged
274
- */
275
- async send(data) {
276
- if (!this._isOpen) {
277
- throw new Error("Port not open");
278
- }
279
- const buffer = typeof data === "string" ? Buffer.from(data) : data;
280
- await this.native.send(buffer);
281
- }
282
- /**
283
- * Close the serial port.
284
- */
285
- async close() {
286
- if (this._isOpen) {
287
- this.native.close();
288
- this._isOpen = false;
289
- this.emit("close");
290
- }
291
- }
292
- /**
293
- * Check if the port is currently open.
294
- */
295
- get isOpen() {
296
- return this._isOpen && this.native.isOpen();
297
- }
298
- // Type-safe event emitter methods
299
- on(event, listener) {
300
- return super.on(event, listener);
301
- }
302
- once(event, listener) {
303
- return super.once(event, listener);
304
- }
305
- emit(event, ...args) {
306
- return super.emit(event, ...args);
307
- }
308
- };
309
- var index_default = DataBridge;
310
403
  // Annotate the CommonJS export names for ESM import in node:
311
404
  0 && (module.exports = {
312
405
  DataBridge,
406
+ RawSerialPort,
313
407
  ResilientDataBridge
314
408
  });