njs-modbus 3.1.0 → 3.2.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 (44) hide show
  1. package/README.md +20 -0
  2. package/README.zh-CN.md +20 -0
  3. package/dist/index.cjs +878 -390
  4. package/dist/index.d.ts +96 -30
  5. package/dist/index.mjs +878 -391
  6. package/dist/utils.cjs +439 -0
  7. package/dist/utils.d.ts +161 -0
  8. package/dist/utils.mjs +425 -0
  9. package/package.json +16 -3
  10. package/dist/src/error-code.d.ts +0 -17
  11. package/dist/src/index.d.ts +0 -7
  12. package/dist/src/layers/application/abstract-application-layer.d.ts +0 -26
  13. package/dist/src/layers/application/ascii-application-layer.d.ts +0 -23
  14. package/dist/src/layers/application/index.d.ts +0 -6
  15. package/dist/src/layers/application/rtu-application-layer.d.ts +0 -34
  16. package/dist/src/layers/application/tcp-application-layer.d.ts +0 -16
  17. package/dist/src/layers/physical/abstract-physical-layer.d.ts +0 -50
  18. package/dist/src/layers/physical/index.d.ts +0 -12
  19. package/dist/src/layers/physical/serial-physical-layer.d.ts +0 -70
  20. package/dist/src/layers/physical/tcp-client-physical-layer.d.ts +0 -19
  21. package/dist/src/layers/physical/tcp-physical-connection.d.ts +0 -16
  22. package/dist/src/layers/physical/tcp-server-physical-layer.d.ts +0 -28
  23. package/dist/src/layers/physical/udp-client-physical-layer.d.ts +0 -33
  24. package/dist/src/layers/physical/udp-server-physical-layer.d.ts +0 -50
  25. package/dist/src/layers/physical/utils.d.ts +0 -39
  26. package/dist/src/layers/physical/vars.d.ts +0 -11
  27. package/dist/src/master/index.d.ts +0 -3
  28. package/dist/src/master/master-session.d.ts +0 -18
  29. package/dist/src/master/master.d.ts +0 -140
  30. package/dist/src/slave/index.d.ts +0 -2
  31. package/dist/src/slave/slave.d.ts +0 -119
  32. package/dist/src/types.d.ts +0 -54
  33. package/dist/src/utils/bitsToMs.d.ts +0 -13
  34. package/dist/src/utils/callback.d.ts +0 -8
  35. package/dist/src/utils/checkRange.d.ts +0 -1
  36. package/dist/src/utils/crc.d.ts +0 -1
  37. package/dist/src/utils/index.d.ts +0 -11
  38. package/dist/src/utils/isUint8.d.ts +0 -8
  39. package/dist/src/utils/lrc.d.ts +0 -1
  40. package/dist/src/utils/predictRtuFrameLength.d.ts +0 -17
  41. package/dist/src/utils/promisify-cb.d.ts +0 -4
  42. package/dist/src/utils/rtu-timing.d.ts +0 -63
  43. package/dist/src/utils/whitelist.d.ts +0 -11
  44. package/dist/src/vars.d.ts +0 -49
package/dist/index.d.ts CHANGED
@@ -44,10 +44,30 @@ interface DeviceIdentification {
44
44
  */
45
45
  interface CustomFunctionCode {
46
46
  fc: number;
47
- /** Predict total RTU frame length for an incoming request (slave-side framing). */
48
- predictRequestLength: (buffer: Buffer) => number | null;
49
- /** Predict total RTU frame length for an incoming response (master-side framing). */
50
- predictResponseLength: (buffer: Buffer) => number | null;
47
+ /**
48
+ * Predict total RTU frame length for an incoming request (slave-side framing).
49
+ *
50
+ * @param buffer The raw frame buffer (a shared pool or incoming chunk). Do NOT
51
+ * modify it; the framing layer owns the memory.
52
+ * @param start — Byte offset in `buffer` where the current candidate frame begins
53
+ * (the unit ID byte). Read the function code at `buffer[start + 1]` and derive
54
+ * the total frame length from the trailing bytes.
55
+ * @returns Total frame length (>= 4, <= 256), or `null` if more bytes are needed.
56
+ */
57
+ predictRequestLength: (buffer: Buffer, start: number, end: number) => number | null;
58
+ /**
59
+ * Predict total RTU frame length for an incoming response (master-side framing).
60
+ *
61
+ * @param buffer — The raw frame buffer (a shared pool or incoming chunk). Do NOT
62
+ * modify it; the framing layer owns the memory.
63
+ * @param start — Byte offset in `buffer` where the current candidate frame begins
64
+ * (the unit ID byte). Read the function code at `buffer[start + 1]` and derive
65
+ * the total frame length from the trailing bytes.
66
+ * @param end — Byte offset one past the last valid byte. Bounds checks must use
67
+ * `end - start` as available bytes, NOT `buffer.length` (pool capacity).
68
+ * @returns Total frame length (>= 4, <= 256), or `null` if more bytes are needed.
69
+ */
70
+ predictResponseLength: (buffer: Buffer, start: number, end: number) => number | null;
51
71
  /**
52
72
  * Slave-side handler. Receives PDU payload (bytes after `fc`, before CRC) and
53
73
  * the unit ID being addressed; must return the PDU payload of the response.
@@ -115,6 +135,8 @@ declare enum ConformityLevel {
115
135
  }
116
136
  /** Shared empty Buffer to avoid repeated allocations. */
117
137
  declare const EMPTY_BUFFER: Buffer<ArrayBuffer>;
138
+ /** Shared no-op function to avoid repeated allocations. */
139
+ declare const NOOP: () => void;
118
140
  /** Modbus V1.1b3 PDU quantity limits. */
119
141
  declare const LIMITS: {
120
142
  readonly READ_COILS_MIN: 1;
@@ -206,7 +228,8 @@ declare class TcpClientPhysicalLayer extends AbstractPhysicalLayer {
206
228
  get state(): PhysicalState;
207
229
  get socket(): Socket | null;
208
230
  constructor(options?: SocketConstructorOpts);
209
- open(options?: SocketConnectOpts | ((err?: Error | null) => void), cb?: (err?: Error | null) => void): void;
231
+ open(cb?: (err?: Error | null) => void): void;
232
+ open(options: SocketConnectOpts, cb?: (err?: Error | null) => void): void;
210
233
  close(cb?: (err?: Error | null) => void): void;
211
234
  }
212
235
 
@@ -232,7 +255,8 @@ declare class TcpServerPhysicalLayer extends AbstractPhysicalLayer {
232
255
  get state(): PhysicalState;
233
256
  get server(): Server | null;
234
257
  constructor(options?: TcpServerPhysicalLayerOptions);
235
- open(options?: ListenOptions | ((err?: Error | null) => void), cb?: (err?: Error | null) => void): void;
258
+ open(cb?: (err?: Error | null) => void): void;
259
+ open(options: ListenOptions, cb?: (err?: Error | null) => void): void;
236
260
  close(cb?: (err?: Error | null) => void): void;
237
261
  }
238
262
 
@@ -248,10 +272,11 @@ declare class UdpClientPhysicalLayer extends AbstractPhysicalLayer {
248
272
  get state(): PhysicalState;
249
273
  get socket(): Socket$1 | null;
250
274
  constructor(options?: Partial<SocketOptions>);
251
- open(remote?: {
275
+ open(cb?: (err?: Error | null) => void): void;
276
+ open(remote: {
252
277
  port?: number;
253
278
  address?: string;
254
- } | ((err?: Error | null) => void), cb?: (err?: Error | null) => void): void;
279
+ }, cb?: (err?: Error | null) => void): void;
255
280
  close(cb?: (err?: Error | null) => void): void;
256
281
  }
257
282
 
@@ -283,7 +308,8 @@ declare class UdpServerPhysicalLayer extends AbstractPhysicalLayer {
283
308
  * @param options Bind options (port, address, etc.). Defaults to port 502.
284
309
  * @param cb Callback invoked when binding completes or fails.
285
310
  */
286
- open(options?: BindOptions | ((err?: Error | null) => void), cb?: (err?: Error | null) => void): void;
311
+ open(cb?: (err?: Error | null) => void): void;
312
+ open(options: BindOptions, cb?: (err?: Error | null) => void): void;
287
313
  close(cb?: (err?: Error | null) => void): void;
288
314
  }
289
315
 
@@ -349,25 +375,37 @@ type PhysicalConfig = {
349
375
  type: 'CUSTOM';
350
376
  layer: AbstractPhysicalLayer;
351
377
  };
378
+ /**
379
+ * User-facing arguments for `Master.open()` / `Slave.open()`.
380
+ *
381
+ * These match the physical layer's `open()` signatures **without** the trailing
382
+ * callback — `promisifyCb` appends it internally so the user gets a `Promise`.
383
+ *
384
+ * | Config | User-facing args |
385
+ * |---------------|--------------------------------------|
386
+ * | SERIAL | `()` — configured at construction |
387
+ * | TCP_CLIENT | `(opts?)` — `SocketConnectOpts` |
388
+ * | TCP_SERVER | `(opts?)` — `ListenOptions` |
389
+ * | UDP_CLIENT | `(remote?)` — `{ port?, address? }` |
390
+ * | UDP_SERVER | `(opts?)` — `BindOptions` |
391
+ * | CUSTOM | `never` — call `layer.open()` directly |
392
+ */
352
393
  type OpenArgs<T extends PhysicalConfig> = T extends {
353
394
  type: 'SERIAL';
354
- } ? Parameters<SerialPhysicalLayer['open']> : T extends {
395
+ } ? [] : T extends {
355
396
  type: 'TCP_CLIENT';
356
- } ? Parameters<TcpClientPhysicalLayer['open']> : T extends {
397
+ } ? [options?: SocketConnectOpts] : T extends {
357
398
  type: 'TCP_SERVER';
358
- } ? Parameters<TcpServerPhysicalLayer['open']> : T extends {
399
+ } ? [options?: ListenOptions] : T extends {
359
400
  type: 'UDP_CLIENT';
360
- } ? Parameters<UdpClientPhysicalLayer['open']> : T extends {
401
+ } ? [remote?: {
402
+ port?: number;
403
+ address?: string;
404
+ }] : T extends {
361
405
  type: 'UDP_SERVER';
362
- } ? Parameters<UdpServerPhysicalLayer['open']> : never;
406
+ } ? [options?: BindOptions] : never;
363
407
  declare function createPhysicalLayer(config: PhysicalConfig): AbstractPhysicalLayer;
364
408
 
365
- interface AbstractApplicationLayerEvents {
366
- framing: [frame: ApplicationDataUnit & {
367
- buffer: Buffer;
368
- }];
369
- 'framing-error': [error: Error];
370
- }
371
409
  /**
372
410
  * Application-layer protocol handler bound to a single physical connection.
373
411
  *
@@ -375,10 +413,16 @@ interface AbstractApplicationLayerEvents {
375
413
  * established and discarded when the connection closes. Subclasses implement
376
414
  * ASCII, RTU, or TCP framing rules.
377
415
  */
378
- declare abstract class AbstractApplicationLayer extends EventEmitter<AbstractApplicationLayerEvents> {
416
+ declare abstract class AbstractApplicationLayer {
379
417
  abstract readonly PROTOCOL: 'ASCII' | 'RTU' | 'TCP';
380
418
  abstract ROLE: 'MASTER' | 'SLAVE';
381
419
  abstract readonly connection: AbstractPhysicalConnection;
420
+ /** Called when a complete frame is decoded. Defaults to no-op. */
421
+ onFraming: (frame: ApplicationDataUnit & {
422
+ buffer: Buffer;
423
+ }) => void;
424
+ /** Called when a framing error is detected. Defaults to no-op. */
425
+ onFramingError: (error: Error) => void;
382
426
  flush(): void;
383
427
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
384
428
  removeCustomFunctionCode(fc: number): void;
@@ -399,7 +443,7 @@ declare class AsciiApplicationLayer extends AbstractApplicationLayer {
399
443
  readonly lenientHex: boolean;
400
444
  private _connection;
401
445
  private _state;
402
- private _cleanupFns;
446
+ private _cleanupCbs;
403
447
  get connection(): AbstractPhysicalConnection;
404
448
  constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: AsciiApplicationLayerOptions);
405
449
  private framing;
@@ -428,10 +472,18 @@ declare class RtuApplicationLayer extends AbstractApplicationLayer {
428
472
  private _threePointFiveT;
429
473
  private _onePointFiveT;
430
474
  private _customFunctionCodes;
431
- private _cleanupFns;
475
+ private _cleanupCbs;
432
476
  get connection(): AbstractPhysicalConnection;
433
477
  constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection, options?: RtuApplicationLayerOptions);
434
478
  private clearStateTimers;
479
+ /**
480
+ * Shared handler for every "frame is not yet complete" exit in `flushBuffer`.
481
+ * Returns `true` when the caller should `return` (strict reset), `false` to
482
+ * `break` the parse loop. Hot path never reaches here — only error/incomplete
483
+ * edges. Extracted as a method so it is not recreated on every `flushBuffer`
484
+ * call.
485
+ */
486
+ private _handleIncomplete;
435
487
  private flushBuffer;
436
488
  flush(): void;
437
489
  addCustomFunctionCode(cfc: CustomFunctionCode): void;
@@ -445,7 +497,7 @@ declare class TcpApplicationLayer extends AbstractApplicationLayer {
445
497
  private _connection;
446
498
  private _transactionId;
447
499
  private _buffer;
448
- private _cleanupFns;
500
+ private _cleanupCbs;
449
501
  get connection(): AbstractPhysicalConnection;
450
502
  constructor(role: 'MASTER' | 'SLAVE', connection: AbstractPhysicalConnection);
451
503
  private tryExtract;
@@ -542,19 +594,21 @@ declare class ModbusMaster<T extends ModbusMasterOptions = ModbusMasterOptions>
542
594
  private _queueDatas;
543
595
  private _queueTimeouts;
544
596
  private _queueBroadcasts;
545
- private _queueResolves;
546
- private _queueRejects;
597
+ private _queueCallbacks;
547
598
  private _queueHead;
548
599
  private _queueLen;
549
600
  private _draining;
550
601
  private _nextTid;
551
602
  private _cleanupFns;
552
603
  private _closePromise;
604
+ private _nextExchangeId;
605
+ private _pendingExchanges;
606
+ private _timerHeap;
553
607
  get state(): PhysicalState;
554
608
  get physicalLayer(): AbstractPhysicalLayer;
555
609
  constructor(options: T);
556
610
  private _createAppLayer;
557
- private send;
611
+ private _send;
558
612
  private _drain;
559
613
  private _processNext;
560
614
  private _exchange;
@@ -715,7 +769,7 @@ declare class ModbusSlave<T extends ModbusSlaveOptions = ModbusSlaveOptions> ext
715
769
  private _protocol;
716
770
  private _appLayers;
717
771
  private _customFunctionCodes;
718
- private _locks;
772
+ private _intervalLocks;
719
773
  private _cleanupFns;
720
774
  private _closePromise;
721
775
  get state(): PhysicalState;
@@ -738,7 +792,19 @@ declare class ModbusSlave<T extends ModbusSlaveOptions = ModbusSlaveOptions> ext
738
792
  private _drain;
739
793
  private _processFrame;
740
794
  private _intercept;
741
- private _withAddressLock;
795
+ /**
796
+ * Serialize a code block over the half-open address interval `[lo, hi)`.
797
+ * The block runs after all previously-installed locks whose intervals
798
+ * overlap with this one have completed. Two non-overlapping intervals
799
+ * execute in parallel.
800
+ *
801
+ * Locks are tracked in a flat array (`_intervalLocks`); the typical depth
802
+ * is 0-2 entries, so the linear overlap scan is sub-µs. Compare with the
803
+ * old per-address Map design, where FC23 writing 121 registers allocated
804
+ * ~125 objects per request (one Promise.resolve / address + Set + sort +
805
+ * Promise.all); this version allocates 1-3.
806
+ */
807
+ private _withIntervalLock;
742
808
  private _handleFC;
743
809
  private _handleCustomFC;
744
810
  add(model: ModbusSlaveModel): void;
@@ -766,5 +832,5 @@ declare class ModbusSlave<T extends ModbusSlaveOptions = ModbusSlaveOptions> ext
766
832
  close(): Promise<void>;
767
833
  }
768
834
 
769
- export { AbstractApplicationLayer, AbstractPhysicalConnection, AbstractPhysicalLayer, AsciiApplicationLayer, COIL_OFF, COIL_ON, ConformityLevel, EMPTY_BUFFER, EXCEPTION_OFFSET, ErrorCode, FunctionCode, LIMITS, MEI_READ_DEVICE_ID, MasterSession, ModbusError, ModbusMaster, ModbusSlave, PhysicalConnectionState, PhysicalState, ReadDeviceIDCode, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpClientPhysicalLayer, UdpServerPhysicalLayer, createPhysicalLayer, getCodeByError, getErrorByCode };
835
+ export { AbstractApplicationLayer, AbstractPhysicalConnection, AbstractPhysicalLayer, AsciiApplicationLayer, COIL_OFF, COIL_ON, ConformityLevel, EMPTY_BUFFER, EXCEPTION_OFFSET, ErrorCode, FunctionCode, LIMITS, MEI_READ_DEVICE_ID, MasterSession, ModbusError, ModbusMaster, ModbusSlave, NOOP, PhysicalConnectionState, PhysicalState, ReadDeviceIDCode, RtuApplicationLayer, SerialPhysicalLayer, TcpApplicationLayer, TcpClientPhysicalLayer, TcpServerPhysicalLayer, UdpClientPhysicalLayer, UdpServerPhysicalLayer, createPhysicalLayer, getCodeByError, getErrorByCode };
770
836
  export type { AbstractPhysicalLayerEvents, ApplicationDataUnit, AsciiApplicationLayerOptions, Callback$1 as Callback, CustomFunctionCode, DeviceIdentification, MaybeAsyncFunction, ModbusMasterOptions, ModbusSlaveModel, ModbusSlaveOptions, OpenArgs, PhysicalConfig, RtuApplicationLayerOptions, ServerId, TcpServerPhysicalLayerOptions, UdpServerPhysicalLayerOptions };