@enyo-energy/sunspec-sdk 0.0.84 → 0.0.86

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.
@@ -73,22 +73,30 @@ class SunspecBatteryScheduleHandler extends storage_schedule_handler_js_1.Storag
73
73
  // baseline so the persisted snapshot continues to point at the
74
74
  // original pre-schedule state. The library will overwrite its
75
75
  // storage row with the same value — harmless.
76
- this.installedWChaMax = this.originalBaseline.wChaMax;
76
+ const wChaMax = this.originalBaseline.wChaMax;
77
+ if (!wChaMax || wChaMax <= 0) {
78
+ throw new Error(`SunspecBatteryScheduleHandler ${this.applianceIdForLog}: cannot start schedule — originalBaseline has no usable wChaMax (got ${wChaMax})`);
79
+ }
80
+ this.installedWChaMax = wChaMax;
77
81
  return { ...this.originalBaseline };
78
82
  }
79
83
  const controls = await this.sunspecClient.readBatteryControls(this.unitId);
80
84
  if (!controls) {
81
85
  throw new Error(`SunspecBatteryScheduleHandler ${this.applianceIdForLog}: failed to read pre-schedule controls`);
82
86
  }
87
+ const wChaMax = controls.wChaMax;
88
+ if (!wChaMax || wChaMax <= 0) {
89
+ throw new Error(`SunspecBatteryScheduleHandler ${this.applianceIdForLog}: cannot start schedule — device did not report a usable wChaMax (got ${wChaMax})`);
90
+ }
83
91
  const baseline = {
84
92
  storCtlMod: controls.storCtlMod,
85
93
  chaGriSet: controls.chaGriSet,
86
- wChaMax: controls.wChaMax,
94
+ wChaMax,
87
95
  inWRte: controls.inWRte,
88
96
  outWRte: controls.outWRte,
89
97
  };
90
98
  this.originalBaseline = baseline;
91
- this.installedWChaMax = baseline.wChaMax;
99
+ this.installedWChaMax = wChaMax;
92
100
  return { ...baseline };
93
101
  }
94
102
  onChange(active, _previous) {
@@ -343,7 +343,16 @@ class SunspecInverter extends BaseSunspecDevice {
343
343
  capabilities;
344
344
  /** Emit a connection-lost faulted status after this many consecutive failed reconnect attempts. */
345
345
  static CONNECTION_FAULT_THRESHOLD = 3;
346
+ /**
347
+ * Require this many consecutive operatingState=7 (FAULT) reads before
348
+ * reporting Faulted. Debounces transient single-poll FAULT reads (e.g. a
349
+ * DC-coupled battery disturbing a string) that would otherwise oscillate
350
+ * the appliance status. Recovery is immediate (counter resets on the first
351
+ * non-fault read).
352
+ */
353
+ static OPERATING_FAULT_THRESHOLD = 3;
346
354
  storage;
355
+ consecutiveOperatingFaults = 0;
347
356
  errorState = { activeCodes: [], lastStatus: 'healthy' };
348
357
  snapshotService;
349
358
  // Whether we've emitted a status update at least once since (re)connecting.
@@ -656,7 +665,15 @@ class SunspecInverter extends BaseSunspecDevice {
656
665
  // fault; MPPT (4), SLEEPING (2) and the other states are normal. This
657
666
  // is re-evaluated every poll so a producing inverter (operatingState=4,
658
667
  // no error bits) reports Healthy even if it was previously faulted.
659
- const operatingFault = data.operatingState === sunspec_interfaces_js_1.SunspecInverterOperatingState.FAULT;
668
+ //
669
+ // A single FAULT read is debounced: we only treat it as a fault after
670
+ // OPERATING_FAULT_THRESHOLD consecutive FAULT polls, so a transient
671
+ // wobble (e.g. a faulted DC-coupled battery on a string) does not
672
+ // oscillate the status. Recovery is immediate — the counter resets on
673
+ // the first non-fault read. Event-register error bits stay immediate.
674
+ const operatingFaultRaw = data.operatingState === sunspec_interfaces_js_1.SunspecInverterOperatingState.FAULT;
675
+ this.consecutiveOperatingFaults = operatingFaultRaw ? this.consecutiveOperatingFaults + 1 : 0;
676
+ const operatingFault = this.consecutiveOperatingFaults >= SunspecInverter.OPERATING_FAULT_THRESHOLD;
660
677
  const newStatus = (codeIds.length > 0 || operatingFault)
661
678
  ? enyo_appliance_js_1.EnyoApplianceStatusEnum.Faulted
662
679
  : enyo_appliance_js_1.EnyoApplianceStatusEnum.Healthy;
@@ -772,7 +789,10 @@ class SunspecInverter extends BaseSunspecDevice {
772
789
  // Only include strings with valid data
773
790
  if (mppt.dcVoltage !== undefined || mppt.dcPower !== undefined) {
774
791
  result.push({
775
- index: index + 1,
792
+ // Use the stable physical module position so a string keeps
793
+ // its identity when an earlier module drops out of a poll.
794
+ // Fall back to array position if the field is absent.
795
+ index: mppt.moduleNumber ?? (index + 1),
776
796
  name: mppt.stringId,
777
797
  state: this.mapMPPTOperatingState(mppt.operatingState),
778
798
  current: mppt.dcCurrent,
@@ -129,7 +129,16 @@ export declare class SunspecInverter extends BaseSunspecDevice {
129
129
  private readonly capabilities;
130
130
  /** Emit a connection-lost faulted status after this many consecutive failed reconnect attempts. */
131
131
  private static readonly CONNECTION_FAULT_THRESHOLD;
132
+ /**
133
+ * Require this many consecutive operatingState=7 (FAULT) reads before
134
+ * reporting Faulted. Debounces transient single-poll FAULT reads (e.g. a
135
+ * DC-coupled battery disturbing a string) that would otherwise oscillate
136
+ * the appliance status. Recovery is immediate (counter resets on the first
137
+ * non-fault read).
138
+ */
139
+ private static readonly OPERATING_FAULT_THRESHOLD;
132
140
  private storage?;
141
+ private consecutiveOperatingFaults;
133
142
  private errorState;
134
143
  private snapshotService?;
135
144
  private statusReassertedThisSession;
@@ -190,6 +190,7 @@ export interface SunspecInverterPersistedErrorState {
190
190
  */
191
191
  export interface SunspecMPPTData extends SunspecBlock {
192
192
  blockNumber: 160;
193
+ moduleNumber?: number;
193
194
  id: number;
194
195
  stringId?: string;
195
196
  dcCurrent?: number;
@@ -365,6 +365,20 @@ class SunspecModbusClient {
365
365
  throw new Error(`Modbus instance for unit ${unitId} not initialized — call connect(host, port, ${unitId}) first`);
366
366
  return inst;
367
367
  }
368
+ /**
369
+ * Expose this client's open Modbus connection for a unit so a co-located driver can
370
+ * share the SAME TCP socket instead of opening a second one. This matters for devices
371
+ * behind single-connection bridges (e.g. the Solax ESP32 Pocket WiFi dongle, which
372
+ * resets the link when a second Modbus-TCP client connects): the driver reads its own
373
+ * function codes (e.g. FC04 input registers) over this socket.
374
+ *
375
+ * Throws if the unit isn't open. The connection's lifecycle stays owned by this client —
376
+ * borrowers MUST NOT call disconnect() on the returned instance; release it via
377
+ * disconnectUnit(unitId) / releaseSunspecClient() as usual.
378
+ */
379
+ getModbusInstance(unitId) {
380
+ return this.getInstance(unitId);
381
+ }
368
382
  /**
369
383
  * Get the fault-tolerant reader for a unit, throwing if it isn't open.
370
384
  */
@@ -1254,6 +1268,10 @@ class SunspecModbusClient {
1254
1268
  (data.dcCurrent !== undefined ||
1255
1269
  data.dcVoltage !== undefined ||
1256
1270
  data.dcPower !== undefined)) {
1271
+ // Stamp the stable 1-based module position so downstream
1272
+ // string identity does not shift when an earlier module
1273
+ // drops out of a poll (e.g. transient undefined reads).
1274
+ data.moduleNumber = i;
1257
1275
  mpptData.push(data);
1258
1276
  }
1259
1277
  }
@@ -1769,8 +1787,17 @@ class SunspecModbusClient {
1769
1787
  // governing parameters are already in place when the device
1770
1788
  // transitions into the new mode.
1771
1789
  if (controls.storCtlMod !== undefined) {
1772
- await this.writeRegisterValue(unitId, baseAddr + 5, controls.storCtlMod, 'uint16');
1773
- console.debug(`Set storage control mode to 0x${controls.storCtlMod.toString(16)}`);
1790
+ // storCtlMod = 0 is rejected with Modbus Exception code 3 (Illegal Data Value)
1791
+ // by some devices (e.g. Fronius GEN24). 0 means "no external control" per the
1792
+ // SunSpec spec, but Fronius firmware requires at least one bit to be set.
1793
+ // Mapping 0 -> CHARGE | DISCHARGE returns the battery to autonomous
1794
+ // self-managed operation on all known SunSpec devices (matches the neutral
1795
+ // reset state used in sunspec-battery-feature-calibrator.ts).
1796
+ const safeMode = controls.storCtlMod === 0
1797
+ ? sunspec_interfaces_js_1.SunspecStorageControlMode.CHARGE | sunspec_interfaces_js_1.SunspecStorageControlMode.DISCHARGE
1798
+ : controls.storCtlMod;
1799
+ await this.writeRegisterValue(unitId, baseAddr + 5, safeMode, 'uint16');
1800
+ console.debug(`Set storage control mode to 0x${safeMode.toString(16)}`);
1774
1801
  }
1775
1802
  console.debug('Battery controls written successfully');
1776
1803
  return true;
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import { type SunspecInverterControls, type SunspecInverterData, type SunspecInverterSettings, type SunspecMeterData, type SunspecModel, type SunspecMPPTData, type SunspecBatteryData, type SunspecBatteryBaseData, type SunspecBatteryControls, SunspecStorageMode } from "./sunspec-interfaces.cjs";
20
20
  import { EnergyAppModbusDataType, IConnectionHealth } from "@enyo-energy/energy-app-sdk/dist/implementations/modbus/interfaces.js";
21
+ import { EnergyAppModbusInstance } from "@enyo-energy/energy-app-sdk/dist/packages/energy-app-modbus.js";
21
22
  import { EnergyApp } from "@enyo-energy/energy-app-sdk";
22
23
  /**
23
24
  * Get (or create) the singleton SunspecModbusClient for this network device.
@@ -121,6 +122,18 @@ export declare class SunspecModbusClient {
121
122
  * Get the EnergyAppModbusInstance for a unit, throwing if it isn't open.
122
123
  */
123
124
  private getInstance;
125
+ /**
126
+ * Expose this client's open Modbus connection for a unit so a co-located driver can
127
+ * share the SAME TCP socket instead of opening a second one. This matters for devices
128
+ * behind single-connection bridges (e.g. the Solax ESP32 Pocket WiFi dongle, which
129
+ * resets the link when a second Modbus-TCP client connects): the driver reads its own
130
+ * function codes (e.g. FC04 input registers) over this socket.
131
+ *
132
+ * Throws if the unit isn't open. The connection's lifecycle stays owned by this client —
133
+ * borrowers MUST NOT call disconnect() on the returned instance; release it via
134
+ * disconnectUnit(unitId) / releaseSunspecClient() as usual.
135
+ */
136
+ getModbusInstance(unitId: number): EnergyAppModbusInstance;
124
137
  /**
125
138
  * Get the fault-tolerant reader for a unit, throwing if it isn't open.
126
139
  */
@@ -9,7 +9,7 @@ exports.getSdkVersion = getSdkVersion;
9
9
  /**
10
10
  * Current version of the enyo Energy App SDK.
11
11
  */
12
- exports.SDK_VERSION = '0.0.84';
12
+ exports.SDK_VERSION = '0.0.86';
13
13
  /**
14
14
  * Gets the current SDK version.
15
15
  * @returns The semantic version string of the SDK
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "0.0.84";
8
+ export declare const SDK_VERSION = "0.0.86";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
@@ -70,22 +70,30 @@ export class SunspecBatteryScheduleHandler extends StorageScheduleHandler {
70
70
  // baseline so the persisted snapshot continues to point at the
71
71
  // original pre-schedule state. The library will overwrite its
72
72
  // storage row with the same value — harmless.
73
- this.installedWChaMax = this.originalBaseline.wChaMax;
73
+ const wChaMax = this.originalBaseline.wChaMax;
74
+ if (!wChaMax || wChaMax <= 0) {
75
+ throw new Error(`SunspecBatteryScheduleHandler ${this.applianceIdForLog}: cannot start schedule — originalBaseline has no usable wChaMax (got ${wChaMax})`);
76
+ }
77
+ this.installedWChaMax = wChaMax;
74
78
  return { ...this.originalBaseline };
75
79
  }
76
80
  const controls = await this.sunspecClient.readBatteryControls(this.unitId);
77
81
  if (!controls) {
78
82
  throw new Error(`SunspecBatteryScheduleHandler ${this.applianceIdForLog}: failed to read pre-schedule controls`);
79
83
  }
84
+ const wChaMax = controls.wChaMax;
85
+ if (!wChaMax || wChaMax <= 0) {
86
+ throw new Error(`SunspecBatteryScheduleHandler ${this.applianceIdForLog}: cannot start schedule — device did not report a usable wChaMax (got ${wChaMax})`);
87
+ }
80
88
  const baseline = {
81
89
  storCtlMod: controls.storCtlMod,
82
90
  chaGriSet: controls.chaGriSet,
83
- wChaMax: controls.wChaMax,
91
+ wChaMax,
84
92
  inWRte: controls.inWRte,
85
93
  outWRte: controls.outWRte,
86
94
  };
87
95
  this.originalBaseline = baseline;
88
- this.installedWChaMax = baseline.wChaMax;
96
+ this.installedWChaMax = wChaMax;
89
97
  return { ...baseline };
90
98
  }
91
99
  onChange(active, _previous) {
@@ -129,7 +129,16 @@ export declare class SunspecInverter extends BaseSunspecDevice {
129
129
  private readonly capabilities;
130
130
  /** Emit a connection-lost faulted status after this many consecutive failed reconnect attempts. */
131
131
  private static readonly CONNECTION_FAULT_THRESHOLD;
132
+ /**
133
+ * Require this many consecutive operatingState=7 (FAULT) reads before
134
+ * reporting Faulted. Debounces transient single-poll FAULT reads (e.g. a
135
+ * DC-coupled battery disturbing a string) that would otherwise oscillate
136
+ * the appliance status. Recovery is immediate (counter resets on the first
137
+ * non-fault read).
138
+ */
139
+ private static readonly OPERATING_FAULT_THRESHOLD;
132
140
  private storage?;
141
+ private consecutiveOperatingFaults;
133
142
  private errorState;
134
143
  private snapshotService?;
135
144
  private statusReassertedThisSession;
@@ -336,7 +336,16 @@ export class SunspecInverter extends BaseSunspecDevice {
336
336
  capabilities;
337
337
  /** Emit a connection-lost faulted status after this many consecutive failed reconnect attempts. */
338
338
  static CONNECTION_FAULT_THRESHOLD = 3;
339
+ /**
340
+ * Require this many consecutive operatingState=7 (FAULT) reads before
341
+ * reporting Faulted. Debounces transient single-poll FAULT reads (e.g. a
342
+ * DC-coupled battery disturbing a string) that would otherwise oscillate
343
+ * the appliance status. Recovery is immediate (counter resets on the first
344
+ * non-fault read).
345
+ */
346
+ static OPERATING_FAULT_THRESHOLD = 3;
339
347
  storage;
348
+ consecutiveOperatingFaults = 0;
340
349
  errorState = { activeCodes: [], lastStatus: 'healthy' };
341
350
  snapshotService;
342
351
  // Whether we've emitted a status update at least once since (re)connecting.
@@ -649,7 +658,15 @@ export class SunspecInverter extends BaseSunspecDevice {
649
658
  // fault; MPPT (4), SLEEPING (2) and the other states are normal. This
650
659
  // is re-evaluated every poll so a producing inverter (operatingState=4,
651
660
  // no error bits) reports Healthy even if it was previously faulted.
652
- const operatingFault = data.operatingState === SunspecInverterOperatingState.FAULT;
661
+ //
662
+ // A single FAULT read is debounced: we only treat it as a fault after
663
+ // OPERATING_FAULT_THRESHOLD consecutive FAULT polls, so a transient
664
+ // wobble (e.g. a faulted DC-coupled battery on a string) does not
665
+ // oscillate the status. Recovery is immediate — the counter resets on
666
+ // the first non-fault read. Event-register error bits stay immediate.
667
+ const operatingFaultRaw = data.operatingState === SunspecInverterOperatingState.FAULT;
668
+ this.consecutiveOperatingFaults = operatingFaultRaw ? this.consecutiveOperatingFaults + 1 : 0;
669
+ const operatingFault = this.consecutiveOperatingFaults >= SunspecInverter.OPERATING_FAULT_THRESHOLD;
653
670
  const newStatus = (codeIds.length > 0 || operatingFault)
654
671
  ? EnyoApplianceStatusEnum.Faulted
655
672
  : EnyoApplianceStatusEnum.Healthy;
@@ -765,7 +782,10 @@ export class SunspecInverter extends BaseSunspecDevice {
765
782
  // Only include strings with valid data
766
783
  if (mppt.dcVoltage !== undefined || mppt.dcPower !== undefined) {
767
784
  result.push({
768
- index: index + 1,
785
+ // Use the stable physical module position so a string keeps
786
+ // its identity when an earlier module drops out of a poll.
787
+ // Fall back to array position if the field is absent.
788
+ index: mppt.moduleNumber ?? (index + 1),
769
789
  name: mppt.stringId,
770
790
  state: this.mapMPPTOperatingState(mppt.operatingState),
771
791
  current: mppt.dcCurrent,
@@ -190,6 +190,7 @@ export interface SunspecInverterPersistedErrorState {
190
190
  */
191
191
  export interface SunspecMPPTData extends SunspecBlock {
192
192
  blockNumber: 160;
193
+ moduleNumber?: number;
193
194
  id: number;
194
195
  stringId?: string;
195
196
  dcCurrent?: number;
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import { type SunspecInverterControls, type SunspecInverterData, type SunspecInverterSettings, type SunspecMeterData, type SunspecModel, type SunspecMPPTData, type SunspecBatteryData, type SunspecBatteryBaseData, type SunspecBatteryControls, SunspecStorageMode } from "./sunspec-interfaces.js";
20
20
  import { EnergyAppModbusDataType, IConnectionHealth } from "@enyo-energy/energy-app-sdk/dist/implementations/modbus/interfaces.js";
21
+ import { EnergyAppModbusInstance } from "@enyo-energy/energy-app-sdk/dist/packages/energy-app-modbus.js";
21
22
  import { EnergyApp } from "@enyo-energy/energy-app-sdk";
22
23
  /**
23
24
  * Get (or create) the singleton SunspecModbusClient for this network device.
@@ -121,6 +122,18 @@ export declare class SunspecModbusClient {
121
122
  * Get the EnergyAppModbusInstance for a unit, throwing if it isn't open.
122
123
  */
123
124
  private getInstance;
125
+ /**
126
+ * Expose this client's open Modbus connection for a unit so a co-located driver can
127
+ * share the SAME TCP socket instead of opening a second one. This matters for devices
128
+ * behind single-connection bridges (e.g. the Solax ESP32 Pocket WiFi dongle, which
129
+ * resets the link when a second Modbus-TCP client connects): the driver reads its own
130
+ * function codes (e.g. FC04 input registers) over this socket.
131
+ *
132
+ * Throws if the unit isn't open. The connection's lifecycle stays owned by this client —
133
+ * borrowers MUST NOT call disconnect() on the returned instance; release it via
134
+ * disconnectUnit(unitId) / releaseSunspecClient() as usual.
135
+ */
136
+ getModbusInstance(unitId: number): EnergyAppModbusInstance;
124
137
  /**
125
138
  * Get the fault-tolerant reader for a unit, throwing if it isn't open.
126
139
  */
@@ -360,6 +360,20 @@ export class SunspecModbusClient {
360
360
  throw new Error(`Modbus instance for unit ${unitId} not initialized — call connect(host, port, ${unitId}) first`);
361
361
  return inst;
362
362
  }
363
+ /**
364
+ * Expose this client's open Modbus connection for a unit so a co-located driver can
365
+ * share the SAME TCP socket instead of opening a second one. This matters for devices
366
+ * behind single-connection bridges (e.g. the Solax ESP32 Pocket WiFi dongle, which
367
+ * resets the link when a second Modbus-TCP client connects): the driver reads its own
368
+ * function codes (e.g. FC04 input registers) over this socket.
369
+ *
370
+ * Throws if the unit isn't open. The connection's lifecycle stays owned by this client —
371
+ * borrowers MUST NOT call disconnect() on the returned instance; release it via
372
+ * disconnectUnit(unitId) / releaseSunspecClient() as usual.
373
+ */
374
+ getModbusInstance(unitId) {
375
+ return this.getInstance(unitId);
376
+ }
363
377
  /**
364
378
  * Get the fault-tolerant reader for a unit, throwing if it isn't open.
365
379
  */
@@ -1249,6 +1263,10 @@ export class SunspecModbusClient {
1249
1263
  (data.dcCurrent !== undefined ||
1250
1264
  data.dcVoltage !== undefined ||
1251
1265
  data.dcPower !== undefined)) {
1266
+ // Stamp the stable 1-based module position so downstream
1267
+ // string identity does not shift when an earlier module
1268
+ // drops out of a poll (e.g. transient undefined reads).
1269
+ data.moduleNumber = i;
1252
1270
  mpptData.push(data);
1253
1271
  }
1254
1272
  }
@@ -1764,8 +1782,17 @@ export class SunspecModbusClient {
1764
1782
  // governing parameters are already in place when the device
1765
1783
  // transitions into the new mode.
1766
1784
  if (controls.storCtlMod !== undefined) {
1767
- await this.writeRegisterValue(unitId, baseAddr + 5, controls.storCtlMod, 'uint16');
1768
- console.debug(`Set storage control mode to 0x${controls.storCtlMod.toString(16)}`);
1785
+ // storCtlMod = 0 is rejected with Modbus Exception code 3 (Illegal Data Value)
1786
+ // by some devices (e.g. Fronius GEN24). 0 means "no external control" per the
1787
+ // SunSpec spec, but Fronius firmware requires at least one bit to be set.
1788
+ // Mapping 0 -> CHARGE | DISCHARGE returns the battery to autonomous
1789
+ // self-managed operation on all known SunSpec devices (matches the neutral
1790
+ // reset state used in sunspec-battery-feature-calibrator.ts).
1791
+ const safeMode = controls.storCtlMod === 0
1792
+ ? SunspecStorageControlMode.CHARGE | SunspecStorageControlMode.DISCHARGE
1793
+ : controls.storCtlMod;
1794
+ await this.writeRegisterValue(unitId, baseAddr + 5, safeMode, 'uint16');
1795
+ console.debug(`Set storage control mode to 0x${safeMode.toString(16)}`);
1769
1796
  }
1770
1797
  console.debug('Battery controls written successfully');
1771
1798
  return true;
package/dist/version.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export declare const SDK_VERSION = "0.0.84";
8
+ export declare const SDK_VERSION = "0.0.86";
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/dist/version.js CHANGED
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Current version of the enyo Energy App SDK.
7
7
  */
8
- export const SDK_VERSION = '0.0.84';
8
+ export const SDK_VERSION = '0.0.86';
9
9
  /**
10
10
  * Gets the current SDK version.
11
11
  * @returns The semantic version string of the SDK
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enyo-energy/sunspec-sdk",
3
- "version": "0.0.84",
3
+ "version": "0.0.86",
4
4
  "description": "enyo Energy Sunspec SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",