myio-js-library 0.1.4 → 0.1.5

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.
package/README.md CHANGED
@@ -289,6 +289,45 @@ strings.normalizeRecipients('a; b, c'); // "a,b,c"
289
289
 
290
290
  ### Number Utilities
291
291
 
292
+ #### `formatNumberReadable(value: unknown, locale?: string, minimumFractionDigits?: number, maximumFractionDigits?: number): string`
293
+
294
+ Formats numbers for Brazilian locale with robust input handling. Safely handles strings, numbers, and other types with sensible defaults.
295
+
296
+ **Parameters:**
297
+ - `value: unknown` - Value to format (number, string, or any other type)
298
+ - `locale?: string` - Locale string (default: 'pt-BR')
299
+ - `minimumFractionDigits?: number` - Minimum decimal places (default: 2)
300
+ - `maximumFractionDigits?: number` - Maximum decimal places (default: 2)
301
+
302
+ **Features:**
303
+ - Handles string inputs with comma decimal separators (e.g., "12,34" → 12.34)
304
+ - Normalizes -0 to 0
305
+ - Returns "-" for null, undefined, NaN, or invalid inputs
306
+ - Configurable locale and decimal places
307
+
308
+ ```javascript
309
+ import { formatNumberReadable } from 'myio-js-library';
310
+
311
+ // Basic usage
312
+ formatNumberReadable(1234.56); // "1.234,56"
313
+ formatNumberReadable(1000); // "1.000,00"
314
+ formatNumberReadable(12.3); // "12,30"
315
+
316
+ // String inputs (handles comma separators)
317
+ formatNumberReadable("1234,56"); // "1.234,56"
318
+ formatNumberReadable("12.34"); // "12,34"
319
+
320
+ // Invalid inputs
321
+ formatNumberReadable(null); // "-"
322
+ formatNumberReadable(NaN); // "-"
323
+ formatNumberReadable("invalid"); // "-"
324
+
325
+ // Custom locale and precision
326
+ formatNumberReadable(1234.56, 'en-US'); // "1,234.56"
327
+ formatNumberReadable(1234.56, 'pt-BR', 0, 0); // "1.235"
328
+ formatNumberReadable(1234.56, 'pt-BR', 1, 3); // "1.234,560"
329
+ ```
330
+
292
331
  #### `numbers.fmtPerc(x: number, digits?: number): string`
293
332
 
294
333
  Formats a ratio (0-1) as a percentage string.
@@ -770,6 +809,83 @@ const legacyData = [
770
809
  findValue(legacyData, 'sensor1', 'temperature'); // 25
771
810
  ```
772
811
 
812
+ ### ThingsBoard Utilities
813
+
814
+ #### `getEntityInfoAndAttributesTB(deviceId: string, opts: TBFetchOptions): Promise<TBEntityInfo>`
815
+
816
+ Fetches a ThingsBoard device and its `SERVER_SCOPE` attributes with one call. Provides robust parsing and sensible defaults for direct UI use.
817
+
818
+ **Parameters:**
819
+ - `deviceId: string` - The ThingsBoard device ID
820
+ - `opts: TBFetchOptions` - Configuration options:
821
+ - `jwt: string` - Bearer token for authentication (required)
822
+ - `baseUrl?: string` - ThingsBoard base URL (default: '', supports relative or absolute URLs)
823
+ - `scope?: string` - Attribute scope (default: 'SERVER_SCOPE')
824
+ - `attributeKeys?: string[]` - Specific attribute keys to fetch (optional)
825
+ - `fetcher?: typeof fetch` - Custom fetch implementation (default: globalThis.fetch)
826
+
827
+ **Returns:** Promise resolving to `TBEntityInfo` object with:
828
+ - `label: string` - Device label (fallback to name or 'Sem etiqueta')
829
+ - `andar: string` - Floor information from 'floor' attribute
830
+ - `numeroLoja: string` - Store number from 'NumLoja' attribute
831
+ - `identificadorMedidor: string` - Meter ID from 'IDMedidor' attribute
832
+ - `identificadorDispositivo: string` - Device ID from 'deviceId' attribute
833
+ - `guid: string` - GUID from 'guid' attribute
834
+ - `consumoDiario: number` - Daily consumption from 'maxDailyConsumption' attribute
835
+ - `consumoMadrugada: number` - Night consumption from 'maxNightConsumption' attribute
836
+
837
+ **UMD Usage (ThingsBoard widgets):**
838
+ ```html
839
+ <script src="https://unpkg.com/myio-js-library@0.1.4/dist/myio-js-library.umd.min.js"></script>
840
+ <script>
841
+ (async () => {
842
+ const { getEntityInfoAndAttributesTB } = MyIOLibrary;
843
+ const jwt = localStorage.getItem('jwt_token'); // or your JWT source
844
+ const deviceId = 'YOUR_DEVICE_ID';
845
+
846
+ try {
847
+ const info = await getEntityInfoAndAttributesTB(deviceId, { jwt });
848
+ console.log('TB entity info:', info);
849
+ // {
850
+ // label: 'My Device',
851
+ // andar: '1',
852
+ // numeroLoja: 'A-12',
853
+ // identificadorMedidor: 'ID-123',
854
+ // identificadorDispositivo: 'DEV-456',
855
+ // guid: '...',
856
+ // consumoDiario: 10.5,
857
+ // consumoMadrugada: 1.2
858
+ // }
859
+ } catch (error) {
860
+ console.error('Failed to fetch device info:', error);
861
+ }
862
+ })();
863
+ </script>
864
+ ```
865
+
866
+ **ESM Usage:**
867
+ ```javascript
868
+ import { getEntityInfoAndAttributesTB } from 'myio-js-library';
869
+
870
+ const info = await getEntityInfoAndAttributesTB('DEVICE_ID', {
871
+ jwt: process.env.TB_JWT!,
872
+ baseUrl: 'https://thingsboard.example.com'
873
+ });
874
+ ```
875
+
876
+ **Error Handling:**
877
+ The function throws meaningful errors for:
878
+ - Missing `deviceId` parameter
879
+ - Missing `jwt` token
880
+ - HTTP failures (device not found, authentication issues, etc.)
881
+ - Missing fetch implementation
882
+
883
+ **Robust Parsing:**
884
+ - Numbers are safely coerced from strings (handles comma decimal separators)
885
+ - Invalid numbers default to 0
886
+ - Missing string attributes default to empty string
887
+ - Handles null/undefined values gracefully
888
+
773
889
  ## 🧪 Development
774
890
 
775
891
  ```bash
package/dist/index.cjs CHANGED
@@ -45,10 +45,12 @@ __export(index_exports, {
45
45
  formatDateWithTimezoneOffset: () => formatDateWithTimezoneOffset,
46
46
  formatEnergy: () => formatEnergy,
47
47
  formatEnergyByGroup: () => formatEnergyByGroup,
48
+ formatNumberReadable: () => formatNumberReadable,
48
49
  formatTankHeadFromCm: () => formatTankHeadFromCm,
49
50
  formatWaterVolumeM3: () => formatWaterVolumeM3,
50
51
  getAvailableContexts: () => getAvailableContexts,
51
52
  getDateRangeArray: () => getDateRangeArray,
53
+ getEntityInfoAndAttributesTB: () => getEntityInfoAndAttributesTB,
52
54
  getSaoPauloISOString: () => getSaoPauloISOString,
53
55
  getSaoPauloISOStringFixed: () => getSaoPauloISOStringFixed,
54
56
  getValueByDatakey: () => getValueByDatakey,
@@ -107,6 +109,19 @@ function fmtPerc(value) {
107
109
  }) + "%";
108
110
  }
109
111
 
112
+ // src/format/numbers.ts
113
+ function formatNumberReadable(value, locale = "pt-BR", minimumFractionDigits = 2, maximumFractionDigits = 2) {
114
+ const n = typeof value === "string" ? Number(value.replace(",", ".")) : Number(value);
115
+ if (!Number.isFinite(n)) {
116
+ return "-";
117
+ }
118
+ const safe = Object.is(n, -0) ? 0 : n;
119
+ return safe.toLocaleString(locale, {
120
+ minimumFractionDigits,
121
+ maximumFractionDigits
122
+ });
123
+ }
124
+
110
125
  // src/format/water.ts
111
126
  function formatWaterVolumeM3(value, locale = "pt-BR") {
112
127
  if (value === null || value === void 0 || isNaN(value)) {
@@ -548,15 +563,6 @@ function toCSV(rows, delimiter = ";") {
548
563
  }).join(delimiter)
549
564
  ).join("\n");
550
565
  }
551
- function formatNumberReadable(value) {
552
- if (value == null || isNaN(value)) {
553
- return "-";
554
- }
555
- return value.toLocaleString("pt-BR", {
556
- minimumFractionDigits: 2,
557
- maximumFractionDigits: 2
558
- });
559
- }
560
566
 
561
567
  // src/classify/energyEntity.ts
562
568
  function classify(entity, criteria) {
@@ -706,6 +712,69 @@ function findValue(data, keyOrPath, legacyDataKey) {
706
712
  return getValueByDatakey(data, keyOrPath);
707
713
  }
708
714
 
715
+ // src/thingsboard/entity.ts
716
+ async function getEntityInfoAndAttributesTB(deviceId, opts) {
717
+ if (!deviceId) throw new Error("getEntityInfoAndAttributesTB: deviceId is required");
718
+ const {
719
+ jwt,
720
+ baseUrl = "",
721
+ scope = "SERVER_SCOPE",
722
+ attributeKeys = [
723
+ "floor",
724
+ "NumLoja",
725
+ "IDMedidor",
726
+ "deviceId",
727
+ "guid",
728
+ "maxDailyConsumption",
729
+ "maxNightConsumption"
730
+ ],
731
+ fetcher = globalThis.fetch?.bind(globalThis)
732
+ } = opts || {};
733
+ if (!jwt) throw new Error("getEntityInfoAndAttributesTB: opts.jwt (Bearer token) is required");
734
+ if (!fetcher) throw new Error("getEntityInfoAndAttributesTB: no fetch implementation available");
735
+ const headers = {
736
+ "Content-Type": "application/json",
737
+ "X-Authorization": `Bearer ${jwt}`
738
+ };
739
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
740
+ const deviceRes = await fetcher(`${base}/api/device/${encodeURIComponent(deviceId)}`, { headers });
741
+ if (!deviceRes.ok) {
742
+ throw new Error(`Failed to fetch device: HTTP ${deviceRes.status} ${deviceRes.statusText}`);
743
+ }
744
+ const device = await deviceRes.json();
745
+ const label = device?.label || device?.name || "Sem etiqueta";
746
+ const attrUrl = `${base}/api/plugins/telemetry/DEVICE/${encodeURIComponent(deviceId)}/values/attributes?scope=${encodeURIComponent(scope)}`;
747
+ const attrRes = await fetcher(attrUrl, { headers });
748
+ if (!attrRes.ok) {
749
+ throw new Error(`Failed to fetch attributes: HTTP ${attrRes.status} ${attrRes.statusText}`);
750
+ }
751
+ const attributes = await attrRes.json();
752
+ const map = /* @__PURE__ */ new Map();
753
+ for (const a of attributes) map.set(a.key, a.value);
754
+ const getStr = (k) => {
755
+ const v = map.get(k);
756
+ if (v == null) return "";
757
+ return typeof v === "string" ? v : String(v);
758
+ };
759
+ const getNum = (k) => {
760
+ const v = map.get(k);
761
+ if (v == null) return 0;
762
+ const n = typeof v === "string" ? Number(v.replace(",", ".")) : Number(v);
763
+ return Number.isFinite(n) ? n : 0;
764
+ };
765
+ void attributeKeys;
766
+ return {
767
+ label,
768
+ andar: getStr("floor") || "",
769
+ numeroLoja: getStr("NumLoja") || "",
770
+ identificadorMedidor: getStr("IDMedidor") || "",
771
+ identificadorDispositivo: getStr("deviceId") || "",
772
+ guid: getStr("guid") || "",
773
+ consumoDiario: getNum("maxDailyConsumption"),
774
+ consumoMadrugada: getNum("maxNightConsumption")
775
+ };
776
+ }
777
+
709
778
  // src/utils/deviceType.js
710
779
  var contexts = {
711
780
  building: (name) => {
@@ -953,10 +1022,12 @@ function isRetryableNetworkError(err) {
953
1022
  formatDateWithTimezoneOffset,
954
1023
  formatEnergy,
955
1024
  formatEnergyByGroup,
1025
+ formatNumberReadable,
956
1026
  formatTankHeadFromCm,
957
1027
  formatWaterVolumeM3,
958
1028
  getAvailableContexts,
959
1029
  getDateRangeArray,
1030
+ getEntityInfoAndAttributesTB,
960
1031
  getSaoPauloISOString,
961
1032
  getSaoPauloISOStringFixed,
962
1033
  getValueByDatakey,
package/dist/index.d.cts CHANGED
@@ -23,6 +23,16 @@ declare function formatAllInSameUnit(values: Array<{
23
23
  */
24
24
  declare function fmtPerc$1(value: number): string;
25
25
 
26
+ /**
27
+ * Formats numbers for Brazilian locale with robust input handling
28
+ * @param value - Value to format (number, string, or any other type)
29
+ * @param locale - Locale string (default: 'pt-BR')
30
+ * @param minimumFractionDigits - Minimum decimal places (default: 2)
31
+ * @param maximumFractionDigits - Maximum decimal places (default: 2)
32
+ * @returns Formatted number string with locale formatting, or '-' for invalid inputs
33
+ */
34
+ declare function formatNumberReadable(value: unknown, locale?: string, minimumFractionDigits?: number, maximumFractionDigits?: number): string;
35
+
26
36
  /**
27
37
  * Formats water volume in cubic meters (M³) using Brazilian locale formatting
28
38
  * @param value - The water volume value in cubic meters
@@ -298,6 +308,37 @@ declare function getValueByDatakeyLegacy(dataList: any[], dataSourceNameTarget:
298
308
  */
299
309
  declare function findValue(data: any, keyOrPath: string, legacyDataKey?: string): any;
300
310
 
311
+ /**
312
+ * ThingsBoard entity and attributes fetching utilities
313
+ */
314
+ interface TBFetchOptions {
315
+ jwt: string;
316
+ baseUrl?: string;
317
+ scope?: 'SERVER_SCOPE' | 'CLIENT_SCOPE' | 'SHARED_SCOPE';
318
+ attributeKeys?: string[];
319
+ fetcher?: typeof fetch;
320
+ }
321
+ interface TBEntityInfo {
322
+ label: string;
323
+ andar: string;
324
+ numeroLoja: string;
325
+ identificadorMedidor: string;
326
+ identificadorDispositivo: string;
327
+ guid: string;
328
+ consumoDiario: number;
329
+ consumoMadrugada: number;
330
+ }
331
+ /**
332
+ * Fetches ThingsBoard Device info + attributes (default: SERVER_SCOPE).
333
+ * Safe defaults and robust coercion, suitable for direct UI use.
334
+ *
335
+ * @param deviceId - The ThingsBoard device ID
336
+ * @param opts - Configuration options including JWT token and API settings
337
+ * @returns Promise resolving to device info and attributes
338
+ * @throws Error if deviceId is missing, JWT is missing, or HTTP requests fail
339
+ */
340
+ declare function getEntityInfoAndAttributesTB(deviceId: string, opts: TBFetchOptions): Promise<TBEntityInfo>;
341
+
301
342
  /**
302
343
  * Detects the device type based on the given name and context.
303
344
  * Uses the specified detection context to identify device types.
@@ -392,4 +433,4 @@ declare function fetchWithRetry(url: any, options?: {}): Promise<any>;
392
433
  */
393
434
  declare function http(url: any, options?: {}): Promise<any>;
394
435
 
395
- export { type StoreRow, type TimedValue, type WaterRow, addDetectionContext, addNamespace, averageByDay, buildWaterReportCSV, buildWaterStoresCSV, calcDeltaPercent, classify, classifyWaterLabel, classifyWaterLabels, decodePayload, decodePayloadBase64Xor, detectDeviceType, determineInterval, exportToCSV, exportToCSVAll, fetchWithRetry, findValue, fmtPerc$1 as fmtPerc, fmtPerc as fmtPercLegacy, formatAllInSameUnit, formatAllInSameWaterUnit, formatDateForInput, formatDateToYMD, formatDateWithTimezoneOffset, formatEnergy, formatEnergyByGroup, formatTankHeadFromCm, formatWaterVolumeM3, getAvailableContexts, getDateRangeArray, getSaoPauloISOString, getSaoPauloISOStringFixed, getValueByDatakey, getValueByDatakeyLegacy, getWaterCategories, groupByDay, http, isWaterCategory, normalizeRecipients, numbers, parseInputDateToDate, strings, timeWindowFromInputYMD, toCSV, toFixedSafe };
436
+ export { type StoreRow, type TBEntityInfo, type TBFetchOptions, type TimedValue, type WaterRow, addDetectionContext, addNamespace, averageByDay, buildWaterReportCSV, buildWaterStoresCSV, calcDeltaPercent, classify, classifyWaterLabel, classifyWaterLabels, decodePayload, decodePayloadBase64Xor, detectDeviceType, determineInterval, exportToCSV, exportToCSVAll, fetchWithRetry, findValue, fmtPerc$1 as fmtPerc, fmtPerc as fmtPercLegacy, formatAllInSameUnit, formatAllInSameWaterUnit, formatDateForInput, formatDateToYMD, formatDateWithTimezoneOffset, formatEnergy, formatEnergyByGroup, formatNumberReadable, formatTankHeadFromCm, formatWaterVolumeM3, getAvailableContexts, getDateRangeArray, getEntityInfoAndAttributesTB, getSaoPauloISOString, getSaoPauloISOStringFixed, getValueByDatakey, getValueByDatakeyLegacy, getWaterCategories, groupByDay, http, isWaterCategory, normalizeRecipients, numbers, parseInputDateToDate, strings, timeWindowFromInputYMD, toCSV, toFixedSafe };
package/dist/index.js CHANGED
@@ -44,6 +44,19 @@ function fmtPerc(value) {
44
44
  }) + "%";
45
45
  }
46
46
 
47
+ // src/format/numbers.ts
48
+ function formatNumberReadable(value, locale = "pt-BR", minimumFractionDigits = 2, maximumFractionDigits = 2) {
49
+ const n = typeof value === "string" ? Number(value.replace(",", ".")) : Number(value);
50
+ if (!Number.isFinite(n)) {
51
+ return "-";
52
+ }
53
+ const safe = Object.is(n, -0) ? 0 : n;
54
+ return safe.toLocaleString(locale, {
55
+ minimumFractionDigits,
56
+ maximumFractionDigits
57
+ });
58
+ }
59
+
47
60
  // src/format/water.ts
48
61
  function formatWaterVolumeM3(value, locale = "pt-BR") {
49
62
  if (value === null || value === void 0 || isNaN(value)) {
@@ -485,15 +498,6 @@ function toCSV(rows, delimiter = ";") {
485
498
  }).join(delimiter)
486
499
  ).join("\n");
487
500
  }
488
- function formatNumberReadable(value) {
489
- if (value == null || isNaN(value)) {
490
- return "-";
491
- }
492
- return value.toLocaleString("pt-BR", {
493
- minimumFractionDigits: 2,
494
- maximumFractionDigits: 2
495
- });
496
- }
497
501
 
498
502
  // src/classify/energyEntity.ts
499
503
  function classify(entity, criteria) {
@@ -643,6 +647,69 @@ function findValue(data, keyOrPath, legacyDataKey) {
643
647
  return getValueByDatakey(data, keyOrPath);
644
648
  }
645
649
 
650
+ // src/thingsboard/entity.ts
651
+ async function getEntityInfoAndAttributesTB(deviceId, opts) {
652
+ if (!deviceId) throw new Error("getEntityInfoAndAttributesTB: deviceId is required");
653
+ const {
654
+ jwt,
655
+ baseUrl = "",
656
+ scope = "SERVER_SCOPE",
657
+ attributeKeys = [
658
+ "floor",
659
+ "NumLoja",
660
+ "IDMedidor",
661
+ "deviceId",
662
+ "guid",
663
+ "maxDailyConsumption",
664
+ "maxNightConsumption"
665
+ ],
666
+ fetcher = globalThis.fetch?.bind(globalThis)
667
+ } = opts || {};
668
+ if (!jwt) throw new Error("getEntityInfoAndAttributesTB: opts.jwt (Bearer token) is required");
669
+ if (!fetcher) throw new Error("getEntityInfoAndAttributesTB: no fetch implementation available");
670
+ const headers = {
671
+ "Content-Type": "application/json",
672
+ "X-Authorization": `Bearer ${jwt}`
673
+ };
674
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
675
+ const deviceRes = await fetcher(`${base}/api/device/${encodeURIComponent(deviceId)}`, { headers });
676
+ if (!deviceRes.ok) {
677
+ throw new Error(`Failed to fetch device: HTTP ${deviceRes.status} ${deviceRes.statusText}`);
678
+ }
679
+ const device = await deviceRes.json();
680
+ const label = device?.label || device?.name || "Sem etiqueta";
681
+ const attrUrl = `${base}/api/plugins/telemetry/DEVICE/${encodeURIComponent(deviceId)}/values/attributes?scope=${encodeURIComponent(scope)}`;
682
+ const attrRes = await fetcher(attrUrl, { headers });
683
+ if (!attrRes.ok) {
684
+ throw new Error(`Failed to fetch attributes: HTTP ${attrRes.status} ${attrRes.statusText}`);
685
+ }
686
+ const attributes = await attrRes.json();
687
+ const map = /* @__PURE__ */ new Map();
688
+ for (const a of attributes) map.set(a.key, a.value);
689
+ const getStr = (k) => {
690
+ const v = map.get(k);
691
+ if (v == null) return "";
692
+ return typeof v === "string" ? v : String(v);
693
+ };
694
+ const getNum = (k) => {
695
+ const v = map.get(k);
696
+ if (v == null) return 0;
697
+ const n = typeof v === "string" ? Number(v.replace(",", ".")) : Number(v);
698
+ return Number.isFinite(n) ? n : 0;
699
+ };
700
+ void attributeKeys;
701
+ return {
702
+ label,
703
+ andar: getStr("floor") || "",
704
+ numeroLoja: getStr("NumLoja") || "",
705
+ identificadorMedidor: getStr("IDMedidor") || "",
706
+ identificadorDispositivo: getStr("deviceId") || "",
707
+ guid: getStr("guid") || "",
708
+ consumoDiario: getNum("maxDailyConsumption"),
709
+ consumoMadrugada: getNum("maxNightConsumption")
710
+ };
711
+ }
712
+
646
713
  // src/utils/deviceType.js
647
714
  var contexts = {
648
715
  building: (name) => {
@@ -889,10 +956,12 @@ export {
889
956
  formatDateWithTimezoneOffset,
890
957
  formatEnergy,
891
958
  formatEnergyByGroup,
959
+ formatNumberReadable,
892
960
  formatTankHeadFromCm,
893
961
  formatWaterVolumeM3,
894
962
  getAvailableContexts,
895
963
  getDateRangeArray,
964
+ getEntityInfoAndAttributesTB,
896
965
  getSaoPauloISOString,
897
966
  getSaoPauloISOStringFixed,
898
967
  getValueByDatakey,
@@ -50,6 +50,19 @@
50
50
  }) + "%";
51
51
  }
52
52
 
53
+ // src/format/numbers.ts
54
+ function formatNumberReadable(value, locale = "pt-BR", minimumFractionDigits = 2, maximumFractionDigits = 2) {
55
+ const n = typeof value === "string" ? Number(value.replace(",", ".")) : Number(value);
56
+ if (!Number.isFinite(n)) {
57
+ return "-";
58
+ }
59
+ const safe = Object.is(n, -0) ? 0 : n;
60
+ return safe.toLocaleString(locale, {
61
+ minimumFractionDigits,
62
+ maximumFractionDigits
63
+ });
64
+ }
65
+
53
66
  // src/format/water.ts
54
67
  function formatWaterVolumeM3(value, locale = "pt-BR") {
55
68
  if (value === null || value === void 0 || isNaN(value)) {
@@ -491,15 +504,6 @@
491
504
  }).join(delimiter)
492
505
  ).join("\n");
493
506
  }
494
- function formatNumberReadable(value) {
495
- if (value == null || isNaN(value)) {
496
- return "-";
497
- }
498
- return value.toLocaleString("pt-BR", {
499
- minimumFractionDigits: 2,
500
- maximumFractionDigits: 2
501
- });
502
- }
503
507
 
504
508
  // src/classify/energyEntity.ts
505
509
  function classify(entity, criteria) {
@@ -649,6 +653,68 @@
649
653
  return getValueByDatakey(data, keyOrPath);
650
654
  }
651
655
 
656
+ // src/thingsboard/entity.ts
657
+ async function getEntityInfoAndAttributesTB(deviceId, opts) {
658
+ if (!deviceId) throw new Error("getEntityInfoAndAttributesTB: deviceId is required");
659
+ const {
660
+ jwt,
661
+ baseUrl = "",
662
+ scope = "SERVER_SCOPE",
663
+ attributeKeys = [
664
+ "floor",
665
+ "NumLoja",
666
+ "IDMedidor",
667
+ "deviceId",
668
+ "guid",
669
+ "maxDailyConsumption",
670
+ "maxNightConsumption"
671
+ ],
672
+ fetcher = globalThis.fetch?.bind(globalThis)
673
+ } = opts || {};
674
+ if (!jwt) throw new Error("getEntityInfoAndAttributesTB: opts.jwt (Bearer token) is required");
675
+ if (!fetcher) throw new Error("getEntityInfoAndAttributesTB: no fetch implementation available");
676
+ const headers = {
677
+ "Content-Type": "application/json",
678
+ "X-Authorization": `Bearer ${jwt}`
679
+ };
680
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
681
+ const deviceRes = await fetcher(`${base}/api/device/${encodeURIComponent(deviceId)}`, { headers });
682
+ if (!deviceRes.ok) {
683
+ throw new Error(`Failed to fetch device: HTTP ${deviceRes.status} ${deviceRes.statusText}`);
684
+ }
685
+ const device = await deviceRes.json();
686
+ const label = device?.label || device?.name || "Sem etiqueta";
687
+ const attrUrl = `${base}/api/plugins/telemetry/DEVICE/${encodeURIComponent(deviceId)}/values/attributes?scope=${encodeURIComponent(scope)}`;
688
+ const attrRes = await fetcher(attrUrl, { headers });
689
+ if (!attrRes.ok) {
690
+ throw new Error(`Failed to fetch attributes: HTTP ${attrRes.status} ${attrRes.statusText}`);
691
+ }
692
+ const attributes = await attrRes.json();
693
+ const map = /* @__PURE__ */ new Map();
694
+ for (const a of attributes) map.set(a.key, a.value);
695
+ const getStr = (k) => {
696
+ const v = map.get(k);
697
+ if (v == null) return "";
698
+ return typeof v === "string" ? v : String(v);
699
+ };
700
+ const getNum = (k) => {
701
+ const v = map.get(k);
702
+ if (v == null) return 0;
703
+ const n = typeof v === "string" ? Number(v.replace(",", ".")) : Number(v);
704
+ return Number.isFinite(n) ? n : 0;
705
+ };
706
+ return {
707
+ label,
708
+ andar: getStr("floor") || "",
709
+ numeroLoja: getStr("NumLoja") || "",
710
+ identificadorMedidor: getStr("IDMedidor") || "",
711
+ identificadorDispositivo: getStr("deviceId") || "",
712
+ guid: getStr("guid") || "",
713
+ consumoDiario: getNum("maxDailyConsumption"),
714
+ consumoMadrugada: getNum("maxNightConsumption")
715
+ };
716
+ }
717
+
652
718
  // src/utils/deviceType.js
653
719
  var contexts = {
654
720
  building: (name) => {
@@ -895,10 +961,12 @@
895
961
  exports.formatDateWithTimezoneOffset = formatDateWithTimezoneOffset;
896
962
  exports.formatEnergy = formatEnergy;
897
963
  exports.formatEnergyByGroup = formatEnergyByGroup;
964
+ exports.formatNumberReadable = formatNumberReadable;
898
965
  exports.formatTankHeadFromCm = formatTankHeadFromCm;
899
966
  exports.formatWaterVolumeM3 = formatWaterVolumeM3;
900
967
  exports.getAvailableContexts = getAvailableContexts;
901
968
  exports.getDateRangeArray = getDateRangeArray;
969
+ exports.getEntityInfoAndAttributesTB = getEntityInfoAndAttributesTB;
902
970
  exports.getSaoPauloISOString = getSaoPauloISOString;
903
971
  exports.getSaoPauloISOStringFixed = getSaoPauloISOStringFixed;
904
972
  exports.getValueByDatakey = getValueByDatakey;
@@ -1 +1 @@
1
- (function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?factory(exports):typeof define==="function"&&define.amd?define(["exports"],factory):(global=typeof globalThis!=="undefined"?globalThis:global||self,factory(global.MyIOLibrary={}))})(this,function(exports){"use strict";var __defProp=Object.defineProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};function formatEnergy(value,unit){if(value===null||value===void 0||isNaN(value)){return"-"}const formattedValue=value.toLocaleString("pt-BR",{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} ${unit}`}function formatAllInSameUnit(values,targetUnit){const unitMultipliers={kWh:1,MWh:1e3,GWh:1e6};const targetMultiplier=unitMultipliers[targetUnit]||1;return values.map(item=>{if(item.value===null||item.value===void 0||isNaN(item.value)){return"-"}const sourceMultiplier=unitMultipliers[item.unit]||1;const convertedValue=item.value*sourceMultiplier/targetMultiplier;return formatEnergy(convertedValue,targetUnit)})}function fmtPerc(value){if(value===null||value===void 0||isNaN(value)||!isFinite(value)){return"-"}const percentage=value*100;return percentage.toLocaleString("pt-BR",{minimumFractionDigits:2,maximumFractionDigits:2})+"%"}function formatWaterVolumeM3(value,locale="pt-BR"){if(value===null||value===void 0||isNaN(value)){return"-"}const formattedValue=value.toLocaleString(locale,{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} M³`}function formatTankHeadFromCm(valueCm,locale="pt-BR"){if(valueCm===null||valueCm===void 0||isNaN(valueCm)){return"-"}const valueMeters=valueCm/100;const formattedValue=valueMeters.toLocaleString(locale,{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} m.c.a.`}function calcDeltaPercent(prev,current){if(prev===null||prev===void 0||isNaN(prev)||current===null||current===void 0||isNaN(current)){return{value:0,type:"neutral"}}if(prev===0&&current===0){return{value:0,type:"neutral"}}if(prev===0&&current>0){return{value:100,type:"increase"}}if(prev===0&&current<0){return{value:100,type:"decrease"}}const percentChange=(current-prev)/prev*100;if(percentChange>0){return{value:percentChange,type:"increase"}}else if(percentChange<0){return{value:Math.abs(percentChange),type:"decrease"}}else{return{value:0,type:"neutral"}}}function formatEnergyByGroup(value,group){if(value===null||value===void 0||isNaN(value)){return"-"}if(group==="Caixas D'Água"){return formatTankHeadFromCm(value)}if(value>=1e6){return formatWaterVolumeM3(value/1e6)+" (GWh scale)"}if(value>=1e3){return formatWaterVolumeM3(value/1e3)+" (MWh scale)"}return formatWaterVolumeM3(value)}function formatAllInSameWaterUnit(values){const max=Math.max(...values.filter(v=>!isNaN(v)&&v!==null&&v!==void 0));let divisor=1;let unit="M³";if(max>=1e6){divisor=1e6;unit="M³"}else if(max>=1e3){divisor=1e3;unit="M³"}return{format:val=>{if(val===null||val===void 0||isNaN(val)){return"-"}return(val/divisor).toFixed(2)+" "+unit},unit:unit}}function formatDateToYMD(date){if(!date){return""}const dateObj=new Date(date);if(isNaN(dateObj.getTime())){return""}const year=dateObj.getFullYear();const month=String(dateObj.getMonth()+1).padStart(2,"0");const day=String(dateObj.getDate()).padStart(2,"0");return`${year}-${month}-${day}`}function determineInterval(startDate,endDate){const start=new Date(startDate);const end=new Date(endDate);if(isNaN(start.getTime())||isNaN(end.getTime())){return"day"}const diffMs=end.getTime()-start.getTime();const diffDays=diffMs/(1e3*60*60*24);if(diffDays<=1){return"hour"}else if(diffDays<=7){return"day"}else if(diffDays<=31){return"week"}else if(diffDays<=365){return"month"}else{return"year"}}function getSaoPauloISOString(date,edge="start"){const dateObj=new Date(date);if(isNaN(dateObj.getTime())){return""}const saoPauloOffset=-3;const saoPauloDate=new Date(dateObj.getTime()+saoPauloOffset*60*60*1e3);if(edge==="start"){saoPauloDate.setHours(0,0,0,0)}else{saoPauloDate.setHours(23,59,59,999)}const utcDate=new Date(saoPauloDate.getTime()-saoPauloOffset*60*60*1e3);return utcDate.toISOString()}function getDateRangeArray(startDate,endDate,interval="day"){const start=new Date(startDate);const end=new Date(endDate);const dates=[];if(isNaN(start.getTime())||isNaN(end.getTime())||start>end){return dates}const current=new Date(start);while(current<=end){dates.push(new Date(current));switch(interval){case"day":current.setDate(current.getDate()+1);break;case"week":current.setDate(current.getDate()+7);break;case"month":current.setMonth(current.getMonth()+1);break;case"year":current.setFullYear(current.getFullYear()+1);break;default:current.setDate(current.getDate()+1)}}return dates}function formatDateForInput(date){if(!date||isNaN(date.getTime())){return""}const year=date.getFullYear();const month=String(date.getMonth()+1).padStart(2,"0");const day=String(date.getDate()).padStart(2,"0");return`${year}-${month}-${day}`}function parseInputDateToDate(inputDateStr){if(!inputDateStr){return null}const parts=inputDateStr.split("-");if(parts.length!==3){return null}const year=parseInt(parts[0],10);const month=parseInt(parts[1],10)-1;const day=parseInt(parts[2],10);if(isNaN(year)||isNaN(month)||isNaN(day)){return null}return new Date(year,month,day,0,0,0,0)}function timeWindowFromInputYMD(startYmd,endYmd,tzOffset="-03:00"){if(!startYmd||!endYmd){return{startTs:0,endTs:0}}const startParts=startYmd.split("-");const endParts=endYmd.split("-");if(startParts.length!==3||endParts.length!==3){return{startTs:0,endTs:0}}const startDate=new Date(parseInt(startParts[0],10),parseInt(startParts[1],10)-1,parseInt(startParts[2],10),0,0,0,0);const endDate=new Date(parseInt(endParts[0],10),parseInt(endParts[1],10)-1,parseInt(endParts[2],10),23,59,59,999);return{startTs:startDate.getTime(),endTs:endDate.getTime()}}function formatDateWithTimezoneOffset(date,endOfDay=false,tzOffset="-03:00"){if(!date||isNaN(date.getTime())){return""}const year=date.getFullYear();const month=String(date.getMonth()+1).padStart(2,"0");const day=String(date.getDate()).padStart(2,"0");let hours,minutes,seconds,milliseconds;if(endOfDay){hours="23";minutes="59";seconds="59";milliseconds="999"}else{hours=String(date.getHours()).padStart(2,"0");minutes=String(date.getMinutes()).padStart(2,"0");seconds=String(date.getSeconds()).padStart(2,"0");milliseconds=String(date.getMilliseconds()).padStart(3,"0")}return`${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}${tzOffset}`}function getSaoPauloISOStringFixed(dateStr,endOfDay=false){if(!dateStr)return"";if(endOfDay){return`${dateStr}T23:59:59.999-03:00`}else{return`${dateStr}T00:00:00.000-03:00`}}function averageByDay(data){if(!data||data.length===0){return[]}const grouped={};data.forEach(item=>{if(!item||item.value===null||item.value===void 0||isNaN(item.value)){return}const date=new Date(item.ts);if(isNaN(date.getTime())){return}const day=date.toISOString().split("T")[0];if(!grouped[day]){grouped[day]=[]}grouped[day].push(Number(item.value))});const result=Object.entries(grouped).map(([day,values])=>{const sum=values.reduce((acc,val)=>acc+val,0);const average=sum/values.length;return{day:day,average:average}});result.sort((a,b)=>a.day.localeCompare(b.day));return result}function groupByDay(data){if(!data||data.length===0){return{}}const grouped={};data.forEach(item=>{if(!item||item.value===null||item.value===void 0||isNaN(item.value)){return}const date=new Date(item.ts);if(isNaN(date.getTime())){return}const day=date.toISOString().split("T")[0];if(!grouped[day]){grouped[day]=[]}grouped[day].push(Number(item.value))});return grouped}function exportToCSV(data,headers,filename){if(!data||data.length===0){return""}const csvHeaders=headers.join(",");const csvRows=data.map(row=>headers.map(header=>{const value=row[header];if(value===null||value===void 0){return""}const stringValue=String(value);if(stringValue.includes(",")||stringValue.includes('"')||stringValue.includes("\n")){return`"${stringValue.replace(/"/g,'""')}"`}return stringValue}).join(","));const csvContent=[csvHeaders,...csvRows].join("\n");return csvContent}function exportToCSVAll(storesData,headers,filename){if(!storesData||Object.keys(storesData).length===0){return""}const csvRows=[];const csvHeaders=["Store",...headers].join(",");csvRows.push(csvHeaders);Object.entries(storesData).forEach(([storeName,storeData])=>{if(!storeData||storeData.length===0){return}storeData.forEach(row=>{let formattedStoreName=storeName;if(storeName.includes(",")||storeName.includes('"')||storeName.includes("\n")){formattedStoreName=`"${storeName.replace(/"/g,'""')}"`}const csvRow=[formattedStoreName,...headers.map(header=>{const value=row[header];if(value===null||value===void 0){return""}const stringValue=String(value);if(stringValue.includes(",")||stringValue.includes('"')||stringValue.includes("\n")){return`"${stringValue.replace(/"/g,'""')}"`}return stringValue})].join(",");csvRows.push(csvRow)})});return csvRows.join("\n")}function buildWaterReportCSV(rows,meta){if(!rows||rows.length===0){return""}const csvRows=[];let totalConsumption=0;rows.forEach(row=>{const consumptionStr=String(row.totalConsumption).replace(",",".");const consumption=Number(consumptionStr)||0;totalConsumption+=consumption});const finalTotal=meta.total!==void 0?meta.total:totalConsumption;csvRows.push(["DATA EMISSÃO",meta.issueDate]);csvRows.push(["Total",finalTotal.toFixed(2)]);if(meta.name&&meta.identifier){csvRows.push(["Loja:",meta.name,meta.identifier])}csvRows.push(["Data","Dia da Semana","Consumo Médio (m³)","Consumo Mínimo (m³)","Consumo Máximo (m³)","Consumo (m³)"]);rows.forEach(row=>{csvRows.push([row.formattedDate,row.day,String(row.avgConsumption),String(row.minDemand),String(row.maxDemand),String(row.totalConsumption)])});return csvRows.map(row=>row.join(";")).join("\n")}function buildWaterStoresCSV(rows,meta){if(!rows||rows.length===0){return""}const csvRows=[];let totalConsumption=0;rows.forEach(row=>{const consumption=row.consumptionM3!==void 0?row.consumptionM3:row.consumptionKwh||0;totalConsumption+=consumption});const finalTotal=meta.total!==void 0?meta.total:totalConsumption;csvRows.push(["DATA EMISSÃO",meta.issueDate]);csvRows.push(["Total",finalTotal.toFixed(2)]);csvRows.push(["Loja","Identificador","Consumo"]);rows.forEach(row=>{const label=row.entityLabel||row.deviceName||"-";const deviceId=row.deviceId||"-";const consumption=row.consumptionM3!==void 0?row.consumptionM3:row.consumptionKwh||0;const formattedConsumption=consumption!==null&&consumption!==void 0?formatNumberReadable(consumption):"0,00";csvRows.push([label,deviceId,formattedConsumption])});return csvRows.map(row=>row.join(";")).join("\n")}function toCSV(rows,delimiter=";"){if(!rows||rows.length===0){return""}return rows.map(row=>row.map(cell=>{const value=String(cell);if(value.includes(delimiter)||value.includes('"')||value.includes("\n")){return`"${value.replace(/"/g,'""')}"`}return value}).join(delimiter)).join("\n")}function formatNumberReadable(value){if(value==null||isNaN(value)){return"-"}return value.toLocaleString("pt-BR",{minimumFractionDigits:2,maximumFractionDigits:2})}function classify(entity,criteria){if(!entity||!criteria){return{category:"unknown",confidence:0}}let category="unknown";let subcategory;let confidence=0;if(entity.type){switch(entity.type.toLowerCase()){case"consumption":category="energy_consumption";confidence=.9;break;case"generation":category="energy_generation";confidence=.9;break;case"storage":category="energy_storage";confidence=.9;break;case"distribution":category="energy_distribution";confidence=.8;break;default:category="energy_other";confidence=.5}}if(entity.powerRating){const power=parseFloat(entity.powerRating);if(!isNaN(power)){if(power<1e3){subcategory="small_scale"}else if(power<1e4){subcategory="medium_scale"}else{subcategory="large_scale"}confidence=Math.min(confidence+.1,1)}}return{category:category,subcategory:subcategory,confidence:confidence}}function classifyWaterLabel(label){if(!label){console.warn('classifyWaterLabel: empty label, defaulting to "Lojas"');return"Lojas"}const normalizedLabel=label.toLowerCase().trim();if(/rel[óo]gio|caixa|superior|inferior|nível_terraço/.test(normalizedLabel)){return"Caixas D'Água"}if(/administra|bomba|chiller|adm/.test(normalizedLabel)){return"Área Comum"}return"Lojas"}function classifyWaterLabels(labels){const counts={"Caixas D'Água":0,Lojas:0,"Área Comum":0,total:0};labels.forEach(label=>{const category=classifyWaterLabel(label);counts[category]++;counts.total++});return counts}function getWaterCategories(){return["Caixas D'Água","Lojas","Área Comum"]}function isWaterCategory(label,category){return classifyWaterLabel(label)===category}function getValueByDatakey(data,datakey){if(!data||!datakey){return void 0}if(Array.isArray(data)){for(const item of data){const value=getValueByDatakey(item,datakey);if(value!==void 0){return value}}return void 0}if(typeof data==="object"&&data!==null){const keys=datakey.split(".");let current=data;for(const key of keys){if(current===null||current===void 0){return void 0}if(key.includes("[")&&key.includes("]")){const arrayKey=key.substring(0,key.indexOf("["));const indexMatch=key.match(/\[(\d+)\]/);if(indexMatch){const index=parseInt(indexMatch[1],10);current=current[arrayKey];if(Array.isArray(current)&&index>=0&&index<current.length){current=current[index]}else{return void 0}}else{return void 0}}else{current=current[key]}}return current}return void 0}function getValueByDatakeyLegacy(dataList,dataSourceNameTarget,dataKeyTarget){if(!Array.isArray(dataList)||!dataSourceNameTarget||!dataKeyTarget){return void 0}for(const item of dataList){if(item&&item.dataSourceName===dataSourceNameTarget&&item.dataKey===dataKeyTarget){return item.value}}return void 0}function findValue(data,keyOrPath,legacyDataKey){if(legacyDataKey!==void 0){return getValueByDatakeyLegacy(data,keyOrPath,legacyDataKey)}return getValueByDatakey(data,keyOrPath)}var contexts={building:name=>{const upper=name.toUpperCase();if(upper.includes("COMPRESSOR"))return"COMPRESSOR";if(upper.includes("VENT"))return"VENTILADOR";if(upper.includes("AUTOMATICO")||upper.includes("AUTOMÁTICO"))return"SELETOR_AUTO_MANUAL";if(upper.includes("TERMOSTATO"))return"TERMOSTATO";if(upper.includes("3F"))return"3F_MEDIDOR";if(upper.includes("TERMO")||upper.includes("TEMP"))return"TERMOSTATO";if(upper.includes("HIDR"))return"HIDROMETRO";if(upper.includes("ABRE"))return"SOLENOIDE";if(upper.includes("RECALQUE"))return"MOTOR";if(upper.includes("AUTOMACAO")||upper.includes("AUTOMAÇÃO"))return"GLOBAL_AUTOMACAO";if(upper.includes("AC"))return"CONTROLE REMOTO";if(upper.includes("SCD"))return"CAIXA_D_AGUA";return"default"},mall:name=>{const upper=name.toUpperCase();if(upper.includes("CHILLER"))return"CHILLER";if(upper.includes("ESCADA"))return"ESCADA_ROLANTE";if(upper.includes("LOJA"))return"LOJA_SENSOR";if(upper.includes("ILUMINACAO")||upper.includes("ILUMINAÇÃO"))return"ILUMINACAO";return"default"}};function detectDeviceType(name,context="building"){if(typeof name!=="string"){throw new Error("Device name must be a string.")}const detectFunction=contexts[context];if(!detectFunction){console.warn(`[myio-js-library] Context "${context}" not found. Using default fallback.`);return contexts.building(name)}return detectFunction(name)}function getAvailableContexts(){return Object.keys(contexts)}function addDetectionContext(contextName,detectFunction){if(typeof contextName!=="string"){throw new Error("Context name must be a string.")}if(typeof detectFunction!=="function"){throw new Error("Detection function must be a function.")}contexts[contextName]=detectFunction}function addNamespace(payload,namespace=""){if(!payload||typeof payload!=="object"||Array.isArray(payload)){throw new Error("Payload must be an object.")}const keys=Object.keys(payload);const suffix=namespace.trim()?` (${namespace.trim()})`:"";return keys.reduce((acc,key)=>{acc[`${key}${suffix}`]=payload[key];return acc},{})}var numbers_exports={};__export(numbers_exports,{fmtPerc:()=>fmtPerc2,toFixedSafe:()=>toFixedSafe});function fmtPerc2(x,digits=2){if(!Number.isFinite(x))return"—";return(x*100).toFixed(digits)+"%"}function toFixedSafe(x,digits=2){if(!Number.isFinite(x))return"—";return x.toFixed(digits)}var strings_exports={};__export(strings_exports,{normalizeRecipients:()=>normalizeRecipients});function normalizeRecipients(val){if(val===null||val===void 0||val==="")return"";if(Object.prototype.toString.call(val)==="[object Array]"){return val.filter(Boolean).join(",")}let s=String(val).trim();if(/^\s*\[/.test(s)){try{const arr=JSON.parse(s);if(Object.prototype.toString.call(arr)==="[object Array]"){return arr.filter(Boolean).join(",")}}catch{}}s=s.replace(/[;\s]+/g,",");s=s.replace(/,+/g,",").replace(/^,|,$/g,"");return s}function decodePayload(encoded,key){const bytes=base64ToBytesStrict(encoded);if(bytes.length===0)return"";if(key===""||key===void 0||key===null){return(new TextDecoder).decode(bytes)}if(typeof key==="number"&&Number.isFinite(key)){const k=key&255;for(let i=0;i<bytes.length;i++)bytes[i]^=k;return(new TextDecoder).decode(bytes)}const keyStr=String(key);const keyBytes=(new TextEncoder).encode(keyStr);if(keyBytes.length===0){return(new TextDecoder).decode(bytes)}for(let i=0;i<bytes.length;i++){bytes[i]^=keyBytes[i%keyBytes.length]}return(new TextDecoder).decode(bytes)}function decodePayloadBase64Xor(encoded,xorKey=73){const bytes=base64ToBytesStrict(encoded);for(let i=0;i<bytes.length;i++)bytes[i]^=xorKey&255;return(new TextDecoder).decode(bytes)}function base64ToBytesStrict(b64){if(b64===""||b64===void 0||b64===null)return new Uint8Array;const s=String(b64).replace(/\s+/g,"");const re=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;if(!re.test(s))throw new Error("Invalid base64");if(typeof Buffer!=="undefined"&&Buffer.from){return Uint8Array.from(Buffer.from(s,"base64"))}const bin=atob(s);const out=new Uint8Array(bin.length);for(let i=0;i<bin.length;i++)out[i]=bin.charCodeAt(i);return out}async function fetchWithRetry(url,options={}){const{retries:retries=0,retryDelay:retryDelay=100,timeout:timeout=1e4,retryCondition:retryCondition,...passThrough}=options;const baseInit={...passThrough,timeout:timeout};let attempt=0;while(true){try{const res=await withTimeout(fetch(url,baseInit),timeout);if(!res.ok){const doRetry=typeof retryCondition==="function"&&retryCondition(null,res)||res.status>=500;if(doRetry&&attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}const msg=`HTTP ${res.status}: ${res.statusText||""}`.trim();throw new Error(msg)}return res}catch(err){if(err&&err.message==="Request timeout"){if(attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}throw err}const doRetry=typeof retryCondition==="function"&&retryCondition(err,void 0)||isRetryableNetworkError(err);if(doRetry&&attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}throw err}}}var http=fetchWithRetry;function withTimeout(promise,ms){return new Promise((resolve,reject)=>{const t=setTimeout(()=>reject(new Error("Request timeout")),ms);promise.then(v=>{clearTimeout(t);resolve(v)},e=>{clearTimeout(t);reject(e)})})}function delay(ms){return new Promise(r=>setTimeout(r,ms))}function expBackoff(base,attempt){return base*Math.pow(2,attempt)}function isRetryableNetworkError(err){if(!err)return false;const msg=String(err.message||"").toLowerCase();return msg.includes("network")||err.name==="AbortError"}exports.addDetectionContext=addDetectionContext;exports.addNamespace=addNamespace;exports.averageByDay=averageByDay;exports.buildWaterReportCSV=buildWaterReportCSV;exports.buildWaterStoresCSV=buildWaterStoresCSV;exports.calcDeltaPercent=calcDeltaPercent;exports.classify=classify;exports.classifyWaterLabel=classifyWaterLabel;exports.classifyWaterLabels=classifyWaterLabels;exports.decodePayload=decodePayload;exports.decodePayloadBase64Xor=decodePayloadBase64Xor;exports.detectDeviceType=detectDeviceType;exports.determineInterval=determineInterval;exports.exportToCSV=exportToCSV;exports.exportToCSVAll=exportToCSVAll;exports.fetchWithRetry=fetchWithRetry;exports.findValue=findValue;exports.fmtPerc=fmtPerc;exports.fmtPercLegacy=fmtPerc2;exports.formatAllInSameUnit=formatAllInSameUnit;exports.formatAllInSameWaterUnit=formatAllInSameWaterUnit;exports.formatDateForInput=formatDateForInput;exports.formatDateToYMD=formatDateToYMD;exports.formatDateWithTimezoneOffset=formatDateWithTimezoneOffset;exports.formatEnergy=formatEnergy;exports.formatEnergyByGroup=formatEnergyByGroup;exports.formatTankHeadFromCm=formatTankHeadFromCm;exports.formatWaterVolumeM3=formatWaterVolumeM3;exports.getAvailableContexts=getAvailableContexts;exports.getDateRangeArray=getDateRangeArray;exports.getSaoPauloISOString=getSaoPauloISOString;exports.getSaoPauloISOStringFixed=getSaoPauloISOStringFixed;exports.getValueByDatakey=getValueByDatakey;exports.getValueByDatakeyLegacy=getValueByDatakeyLegacy;exports.getWaterCategories=getWaterCategories;exports.groupByDay=groupByDay;exports.http=http;exports.isWaterCategory=isWaterCategory;exports.normalizeRecipients=normalizeRecipients;exports.numbers=numbers_exports;exports.parseInputDateToDate=parseInputDateToDate;exports.strings=strings_exports;exports.timeWindowFromInputYMD=timeWindowFromInputYMD;exports.toCSV=toCSV;exports.toFixedSafe=toFixedSafe});
1
+ (function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?factory(exports):typeof define==="function"&&define.amd?define(["exports"],factory):(global=typeof globalThis!=="undefined"?globalThis:global||self,factory(global.MyIOLibrary={}))})(this,function(exports){"use strict";var __defProp=Object.defineProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:true})};function formatEnergy(value,unit){if(value===null||value===void 0||isNaN(value)){return"-"}const formattedValue=value.toLocaleString("pt-BR",{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} ${unit}`}function formatAllInSameUnit(values,targetUnit){const unitMultipliers={kWh:1,MWh:1e3,GWh:1e6};const targetMultiplier=unitMultipliers[targetUnit]||1;return values.map(item=>{if(item.value===null||item.value===void 0||isNaN(item.value)){return"-"}const sourceMultiplier=unitMultipliers[item.unit]||1;const convertedValue=item.value*sourceMultiplier/targetMultiplier;return formatEnergy(convertedValue,targetUnit)})}function fmtPerc(value){if(value===null||value===void 0||isNaN(value)||!isFinite(value)){return"-"}const percentage=value*100;return percentage.toLocaleString("pt-BR",{minimumFractionDigits:2,maximumFractionDigits:2})+"%"}function formatNumberReadable(value,locale="pt-BR",minimumFractionDigits=2,maximumFractionDigits=2){const n=typeof value==="string"?Number(value.replace(",",".")):Number(value);if(!Number.isFinite(n)){return"-"}const safe=Object.is(n,-0)?0:n;return safe.toLocaleString(locale,{minimumFractionDigits:minimumFractionDigits,maximumFractionDigits:maximumFractionDigits})}function formatWaterVolumeM3(value,locale="pt-BR"){if(value===null||value===void 0||isNaN(value)){return"-"}const formattedValue=value.toLocaleString(locale,{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} M³`}function formatTankHeadFromCm(valueCm,locale="pt-BR"){if(valueCm===null||valueCm===void 0||isNaN(valueCm)){return"-"}const valueMeters=valueCm/100;const formattedValue=valueMeters.toLocaleString(locale,{minimumFractionDigits:2,maximumFractionDigits:2});return`${formattedValue} m.c.a.`}function calcDeltaPercent(prev,current){if(prev===null||prev===void 0||isNaN(prev)||current===null||current===void 0||isNaN(current)){return{value:0,type:"neutral"}}if(prev===0&&current===0){return{value:0,type:"neutral"}}if(prev===0&&current>0){return{value:100,type:"increase"}}if(prev===0&&current<0){return{value:100,type:"decrease"}}const percentChange=(current-prev)/prev*100;if(percentChange>0){return{value:percentChange,type:"increase"}}else if(percentChange<0){return{value:Math.abs(percentChange),type:"decrease"}}else{return{value:0,type:"neutral"}}}function formatEnergyByGroup(value,group){if(value===null||value===void 0||isNaN(value)){return"-"}if(group==="Caixas D'Água"){return formatTankHeadFromCm(value)}if(value>=1e6){return formatWaterVolumeM3(value/1e6)+" (GWh scale)"}if(value>=1e3){return formatWaterVolumeM3(value/1e3)+" (MWh scale)"}return formatWaterVolumeM3(value)}function formatAllInSameWaterUnit(values){const max=Math.max(...values.filter(v=>!isNaN(v)&&v!==null&&v!==void 0));let divisor=1;let unit="M³";if(max>=1e6){divisor=1e6;unit="M³"}else if(max>=1e3){divisor=1e3;unit="M³"}return{format:val=>{if(val===null||val===void 0||isNaN(val)){return"-"}return(val/divisor).toFixed(2)+" "+unit},unit:unit}}function formatDateToYMD(date){if(!date){return""}const dateObj=new Date(date);if(isNaN(dateObj.getTime())){return""}const year=dateObj.getFullYear();const month=String(dateObj.getMonth()+1).padStart(2,"0");const day=String(dateObj.getDate()).padStart(2,"0");return`${year}-${month}-${day}`}function determineInterval(startDate,endDate){const start=new Date(startDate);const end=new Date(endDate);if(isNaN(start.getTime())||isNaN(end.getTime())){return"day"}const diffMs=end.getTime()-start.getTime();const diffDays=diffMs/(1e3*60*60*24);if(diffDays<=1){return"hour"}else if(diffDays<=7){return"day"}else if(diffDays<=31){return"week"}else if(diffDays<=365){return"month"}else{return"year"}}function getSaoPauloISOString(date,edge="start"){const dateObj=new Date(date);if(isNaN(dateObj.getTime())){return""}const saoPauloOffset=-3;const saoPauloDate=new Date(dateObj.getTime()+saoPauloOffset*60*60*1e3);if(edge==="start"){saoPauloDate.setHours(0,0,0,0)}else{saoPauloDate.setHours(23,59,59,999)}const utcDate=new Date(saoPauloDate.getTime()-saoPauloOffset*60*60*1e3);return utcDate.toISOString()}function getDateRangeArray(startDate,endDate,interval="day"){const start=new Date(startDate);const end=new Date(endDate);const dates=[];if(isNaN(start.getTime())||isNaN(end.getTime())||start>end){return dates}const current=new Date(start);while(current<=end){dates.push(new Date(current));switch(interval){case"day":current.setDate(current.getDate()+1);break;case"week":current.setDate(current.getDate()+7);break;case"month":current.setMonth(current.getMonth()+1);break;case"year":current.setFullYear(current.getFullYear()+1);break;default:current.setDate(current.getDate()+1)}}return dates}function formatDateForInput(date){if(!date||isNaN(date.getTime())){return""}const year=date.getFullYear();const month=String(date.getMonth()+1).padStart(2,"0");const day=String(date.getDate()).padStart(2,"0");return`${year}-${month}-${day}`}function parseInputDateToDate(inputDateStr){if(!inputDateStr){return null}const parts=inputDateStr.split("-");if(parts.length!==3){return null}const year=parseInt(parts[0],10);const month=parseInt(parts[1],10)-1;const day=parseInt(parts[2],10);if(isNaN(year)||isNaN(month)||isNaN(day)){return null}return new Date(year,month,day,0,0,0,0)}function timeWindowFromInputYMD(startYmd,endYmd,tzOffset="-03:00"){if(!startYmd||!endYmd){return{startTs:0,endTs:0}}const startParts=startYmd.split("-");const endParts=endYmd.split("-");if(startParts.length!==3||endParts.length!==3){return{startTs:0,endTs:0}}const startDate=new Date(parseInt(startParts[0],10),parseInt(startParts[1],10)-1,parseInt(startParts[2],10),0,0,0,0);const endDate=new Date(parseInt(endParts[0],10),parseInt(endParts[1],10)-1,parseInt(endParts[2],10),23,59,59,999);return{startTs:startDate.getTime(),endTs:endDate.getTime()}}function formatDateWithTimezoneOffset(date,endOfDay=false,tzOffset="-03:00"){if(!date||isNaN(date.getTime())){return""}const year=date.getFullYear();const month=String(date.getMonth()+1).padStart(2,"0");const day=String(date.getDate()).padStart(2,"0");let hours,minutes,seconds,milliseconds;if(endOfDay){hours="23";minutes="59";seconds="59";milliseconds="999"}else{hours=String(date.getHours()).padStart(2,"0");minutes=String(date.getMinutes()).padStart(2,"0");seconds=String(date.getSeconds()).padStart(2,"0");milliseconds=String(date.getMilliseconds()).padStart(3,"0")}return`${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}${tzOffset}`}function getSaoPauloISOStringFixed(dateStr,endOfDay=false){if(!dateStr)return"";if(endOfDay){return`${dateStr}T23:59:59.999-03:00`}else{return`${dateStr}T00:00:00.000-03:00`}}function averageByDay(data){if(!data||data.length===0){return[]}const grouped={};data.forEach(item=>{if(!item||item.value===null||item.value===void 0||isNaN(item.value)){return}const date=new Date(item.ts);if(isNaN(date.getTime())){return}const day=date.toISOString().split("T")[0];if(!grouped[day]){grouped[day]=[]}grouped[day].push(Number(item.value))});const result=Object.entries(grouped).map(([day,values])=>{const sum=values.reduce((acc,val)=>acc+val,0);const average=sum/values.length;return{day:day,average:average}});result.sort((a,b)=>a.day.localeCompare(b.day));return result}function groupByDay(data){if(!data||data.length===0){return{}}const grouped={};data.forEach(item=>{if(!item||item.value===null||item.value===void 0||isNaN(item.value)){return}const date=new Date(item.ts);if(isNaN(date.getTime())){return}const day=date.toISOString().split("T")[0];if(!grouped[day]){grouped[day]=[]}grouped[day].push(Number(item.value))});return grouped}function exportToCSV(data,headers,filename){if(!data||data.length===0){return""}const csvHeaders=headers.join(",");const csvRows=data.map(row=>headers.map(header=>{const value=row[header];if(value===null||value===void 0){return""}const stringValue=String(value);if(stringValue.includes(",")||stringValue.includes('"')||stringValue.includes("\n")){return`"${stringValue.replace(/"/g,'""')}"`}return stringValue}).join(","));const csvContent=[csvHeaders,...csvRows].join("\n");return csvContent}function exportToCSVAll(storesData,headers,filename){if(!storesData||Object.keys(storesData).length===0){return""}const csvRows=[];const csvHeaders=["Store",...headers].join(",");csvRows.push(csvHeaders);Object.entries(storesData).forEach(([storeName,storeData])=>{if(!storeData||storeData.length===0){return}storeData.forEach(row=>{let formattedStoreName=storeName;if(storeName.includes(",")||storeName.includes('"')||storeName.includes("\n")){formattedStoreName=`"${storeName.replace(/"/g,'""')}"`}const csvRow=[formattedStoreName,...headers.map(header=>{const value=row[header];if(value===null||value===void 0){return""}const stringValue=String(value);if(stringValue.includes(",")||stringValue.includes('"')||stringValue.includes("\n")){return`"${stringValue.replace(/"/g,'""')}"`}return stringValue})].join(",");csvRows.push(csvRow)})});return csvRows.join("\n")}function buildWaterReportCSV(rows,meta){if(!rows||rows.length===0){return""}const csvRows=[];let totalConsumption=0;rows.forEach(row=>{const consumptionStr=String(row.totalConsumption).replace(",",".");const consumption=Number(consumptionStr)||0;totalConsumption+=consumption});const finalTotal=meta.total!==void 0?meta.total:totalConsumption;csvRows.push(["DATA EMISSÃO",meta.issueDate]);csvRows.push(["Total",finalTotal.toFixed(2)]);if(meta.name&&meta.identifier){csvRows.push(["Loja:",meta.name,meta.identifier])}csvRows.push(["Data","Dia da Semana","Consumo Médio (m³)","Consumo Mínimo (m³)","Consumo Máximo (m³)","Consumo (m³)"]);rows.forEach(row=>{csvRows.push([row.formattedDate,row.day,String(row.avgConsumption),String(row.minDemand),String(row.maxDemand),String(row.totalConsumption)])});return csvRows.map(row=>row.join(";")).join("\n")}function buildWaterStoresCSV(rows,meta){if(!rows||rows.length===0){return""}const csvRows=[];let totalConsumption=0;rows.forEach(row=>{const consumption=row.consumptionM3!==void 0?row.consumptionM3:row.consumptionKwh||0;totalConsumption+=consumption});const finalTotal=meta.total!==void 0?meta.total:totalConsumption;csvRows.push(["DATA EMISSÃO",meta.issueDate]);csvRows.push(["Total",finalTotal.toFixed(2)]);csvRows.push(["Loja","Identificador","Consumo"]);rows.forEach(row=>{const label=row.entityLabel||row.deviceName||"-";const deviceId=row.deviceId||"-";const consumption=row.consumptionM3!==void 0?row.consumptionM3:row.consumptionKwh||0;const formattedConsumption=consumption!==null&&consumption!==void 0?formatNumberReadable(consumption):"0,00";csvRows.push([label,deviceId,formattedConsumption])});return csvRows.map(row=>row.join(";")).join("\n")}function toCSV(rows,delimiter=";"){if(!rows||rows.length===0){return""}return rows.map(row=>row.map(cell=>{const value=String(cell);if(value.includes(delimiter)||value.includes('"')||value.includes("\n")){return`"${value.replace(/"/g,'""')}"`}return value}).join(delimiter)).join("\n")}function classify(entity,criteria){if(!entity||!criteria){return{category:"unknown",confidence:0}}let category="unknown";let subcategory;let confidence=0;if(entity.type){switch(entity.type.toLowerCase()){case"consumption":category="energy_consumption";confidence=.9;break;case"generation":category="energy_generation";confidence=.9;break;case"storage":category="energy_storage";confidence=.9;break;case"distribution":category="energy_distribution";confidence=.8;break;default:category="energy_other";confidence=.5}}if(entity.powerRating){const power=parseFloat(entity.powerRating);if(!isNaN(power)){if(power<1e3){subcategory="small_scale"}else if(power<1e4){subcategory="medium_scale"}else{subcategory="large_scale"}confidence=Math.min(confidence+.1,1)}}return{category:category,subcategory:subcategory,confidence:confidence}}function classifyWaterLabel(label){if(!label){console.warn('classifyWaterLabel: empty label, defaulting to "Lojas"');return"Lojas"}const normalizedLabel=label.toLowerCase().trim();if(/rel[óo]gio|caixa|superior|inferior|nível_terraço/.test(normalizedLabel)){return"Caixas D'Água"}if(/administra|bomba|chiller|adm/.test(normalizedLabel)){return"Área Comum"}return"Lojas"}function classifyWaterLabels(labels){const counts={"Caixas D'Água":0,Lojas:0,"Área Comum":0,total:0};labels.forEach(label=>{const category=classifyWaterLabel(label);counts[category]++;counts.total++});return counts}function getWaterCategories(){return["Caixas D'Água","Lojas","Área Comum"]}function isWaterCategory(label,category){return classifyWaterLabel(label)===category}function getValueByDatakey(data,datakey){if(!data||!datakey){return void 0}if(Array.isArray(data)){for(const item of data){const value=getValueByDatakey(item,datakey);if(value!==void 0){return value}}return void 0}if(typeof data==="object"&&data!==null){const keys=datakey.split(".");let current=data;for(const key of keys){if(current===null||current===void 0){return void 0}if(key.includes("[")&&key.includes("]")){const arrayKey=key.substring(0,key.indexOf("["));const indexMatch=key.match(/\[(\d+)\]/);if(indexMatch){const index=parseInt(indexMatch[1],10);current=current[arrayKey];if(Array.isArray(current)&&index>=0&&index<current.length){current=current[index]}else{return void 0}}else{return void 0}}else{current=current[key]}}return current}return void 0}function getValueByDatakeyLegacy(dataList,dataSourceNameTarget,dataKeyTarget){if(!Array.isArray(dataList)||!dataSourceNameTarget||!dataKeyTarget){return void 0}for(const item of dataList){if(item&&item.dataSourceName===dataSourceNameTarget&&item.dataKey===dataKeyTarget){return item.value}}return void 0}function findValue(data,keyOrPath,legacyDataKey){if(legacyDataKey!==void 0){return getValueByDatakeyLegacy(data,keyOrPath,legacyDataKey)}return getValueByDatakey(data,keyOrPath)}async function getEntityInfoAndAttributesTB(deviceId,opts){if(!deviceId)throw new Error("getEntityInfoAndAttributesTB: deviceId is required");const{jwt:jwt,baseUrl:baseUrl="",scope:scope="SERVER_SCOPE",attributeKeys:attributeKeys=["floor","NumLoja","IDMedidor","deviceId","guid","maxDailyConsumption","maxNightConsumption"],fetcher:fetcher=globalThis.fetch?.bind(globalThis)}=opts||{};if(!jwt)throw new Error("getEntityInfoAndAttributesTB: opts.jwt (Bearer token) is required");if(!fetcher)throw new Error("getEntityInfoAndAttributesTB: no fetch implementation available");const headers={"Content-Type":"application/json","X-Authorization":`Bearer ${jwt}`};const base=baseUrl.endsWith("/")?baseUrl.slice(0,-1):baseUrl;const deviceRes=await fetcher(`${base}/api/device/${encodeURIComponent(deviceId)}`,{headers:headers});if(!deviceRes.ok){throw new Error(`Failed to fetch device: HTTP ${deviceRes.status} ${deviceRes.statusText}`)}const device=await deviceRes.json();const label=device?.label||device?.name||"Sem etiqueta";const attrUrl=`${base}/api/plugins/telemetry/DEVICE/${encodeURIComponent(deviceId)}/values/attributes?scope=${encodeURIComponent(scope)}`;const attrRes=await fetcher(attrUrl,{headers:headers});if(!attrRes.ok){throw new Error(`Failed to fetch attributes: HTTP ${attrRes.status} ${attrRes.statusText}`)}const attributes=await attrRes.json();const map=new Map;for(const a of attributes)map.set(a.key,a.value);const getStr=k=>{const v=map.get(k);if(v==null)return"";return typeof v==="string"?v:String(v)};const getNum=k=>{const v=map.get(k);if(v==null)return 0;const n=typeof v==="string"?Number(v.replace(",",".")):Number(v);return Number.isFinite(n)?n:0};return{label:label,andar:getStr("floor")||"",numeroLoja:getStr("NumLoja")||"",identificadorMedidor:getStr("IDMedidor")||"",identificadorDispositivo:getStr("deviceId")||"",guid:getStr("guid")||"",consumoDiario:getNum("maxDailyConsumption"),consumoMadrugada:getNum("maxNightConsumption")}}var contexts={building:name=>{const upper=name.toUpperCase();if(upper.includes("COMPRESSOR"))return"COMPRESSOR";if(upper.includes("VENT"))return"VENTILADOR";if(upper.includes("AUTOMATICO")||upper.includes("AUTOMÁTICO"))return"SELETOR_AUTO_MANUAL";if(upper.includes("TERMOSTATO"))return"TERMOSTATO";if(upper.includes("3F"))return"3F_MEDIDOR";if(upper.includes("TERMO")||upper.includes("TEMP"))return"TERMOSTATO";if(upper.includes("HIDR"))return"HIDROMETRO";if(upper.includes("ABRE"))return"SOLENOIDE";if(upper.includes("RECALQUE"))return"MOTOR";if(upper.includes("AUTOMACAO")||upper.includes("AUTOMAÇÃO"))return"GLOBAL_AUTOMACAO";if(upper.includes("AC"))return"CONTROLE REMOTO";if(upper.includes("SCD"))return"CAIXA_D_AGUA";return"default"},mall:name=>{const upper=name.toUpperCase();if(upper.includes("CHILLER"))return"CHILLER";if(upper.includes("ESCADA"))return"ESCADA_ROLANTE";if(upper.includes("LOJA"))return"LOJA_SENSOR";if(upper.includes("ILUMINACAO")||upper.includes("ILUMINAÇÃO"))return"ILUMINACAO";return"default"}};function detectDeviceType(name,context="building"){if(typeof name!=="string"){throw new Error("Device name must be a string.")}const detectFunction=contexts[context];if(!detectFunction){console.warn(`[myio-js-library] Context "${context}" not found. Using default fallback.`);return contexts.building(name)}return detectFunction(name)}function getAvailableContexts(){return Object.keys(contexts)}function addDetectionContext(contextName,detectFunction){if(typeof contextName!=="string"){throw new Error("Context name must be a string.")}if(typeof detectFunction!=="function"){throw new Error("Detection function must be a function.")}contexts[contextName]=detectFunction}function addNamespace(payload,namespace=""){if(!payload||typeof payload!=="object"||Array.isArray(payload)){throw new Error("Payload must be an object.")}const keys=Object.keys(payload);const suffix=namespace.trim()?` (${namespace.trim()})`:"";return keys.reduce((acc,key)=>{acc[`${key}${suffix}`]=payload[key];return acc},{})}var numbers_exports={};__export(numbers_exports,{fmtPerc:()=>fmtPerc2,toFixedSafe:()=>toFixedSafe});function fmtPerc2(x,digits=2){if(!Number.isFinite(x))return"—";return(x*100).toFixed(digits)+"%"}function toFixedSafe(x,digits=2){if(!Number.isFinite(x))return"—";return x.toFixed(digits)}var strings_exports={};__export(strings_exports,{normalizeRecipients:()=>normalizeRecipients});function normalizeRecipients(val){if(val===null||val===void 0||val==="")return"";if(Object.prototype.toString.call(val)==="[object Array]"){return val.filter(Boolean).join(",")}let s=String(val).trim();if(/^\s*\[/.test(s)){try{const arr=JSON.parse(s);if(Object.prototype.toString.call(arr)==="[object Array]"){return arr.filter(Boolean).join(",")}}catch{}}s=s.replace(/[;\s]+/g,",");s=s.replace(/,+/g,",").replace(/^,|,$/g,"");return s}function decodePayload(encoded,key){const bytes=base64ToBytesStrict(encoded);if(bytes.length===0)return"";if(key===""||key===void 0||key===null){return(new TextDecoder).decode(bytes)}if(typeof key==="number"&&Number.isFinite(key)){const k=key&255;for(let i=0;i<bytes.length;i++)bytes[i]^=k;return(new TextDecoder).decode(bytes)}const keyStr=String(key);const keyBytes=(new TextEncoder).encode(keyStr);if(keyBytes.length===0){return(new TextDecoder).decode(bytes)}for(let i=0;i<bytes.length;i++){bytes[i]^=keyBytes[i%keyBytes.length]}return(new TextDecoder).decode(bytes)}function decodePayloadBase64Xor(encoded,xorKey=73){const bytes=base64ToBytesStrict(encoded);for(let i=0;i<bytes.length;i++)bytes[i]^=xorKey&255;return(new TextDecoder).decode(bytes)}function base64ToBytesStrict(b64){if(b64===""||b64===void 0||b64===null)return new Uint8Array;const s=String(b64).replace(/\s+/g,"");const re=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;if(!re.test(s))throw new Error("Invalid base64");if(typeof Buffer!=="undefined"&&Buffer.from){return Uint8Array.from(Buffer.from(s,"base64"))}const bin=atob(s);const out=new Uint8Array(bin.length);for(let i=0;i<bin.length;i++)out[i]=bin.charCodeAt(i);return out}async function fetchWithRetry(url,options={}){const{retries:retries=0,retryDelay:retryDelay=100,timeout:timeout=1e4,retryCondition:retryCondition,...passThrough}=options;const baseInit={...passThrough,timeout:timeout};let attempt=0;while(true){try{const res=await withTimeout(fetch(url,baseInit),timeout);if(!res.ok){const doRetry=typeof retryCondition==="function"&&retryCondition(null,res)||res.status>=500;if(doRetry&&attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}const msg=`HTTP ${res.status}: ${res.statusText||""}`.trim();throw new Error(msg)}return res}catch(err){if(err&&err.message==="Request timeout"){if(attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}throw err}const doRetry=typeof retryCondition==="function"&&retryCondition(err,void 0)||isRetryableNetworkError(err);if(doRetry&&attempt<retries){await delay(expBackoff(retryDelay,attempt));attempt++;continue}throw err}}}var http=fetchWithRetry;function withTimeout(promise,ms){return new Promise((resolve,reject)=>{const t=setTimeout(()=>reject(new Error("Request timeout")),ms);promise.then(v=>{clearTimeout(t);resolve(v)},e=>{clearTimeout(t);reject(e)})})}function delay(ms){return new Promise(r=>setTimeout(r,ms))}function expBackoff(base,attempt){return base*Math.pow(2,attempt)}function isRetryableNetworkError(err){if(!err)return false;const msg=String(err.message||"").toLowerCase();return msg.includes("network")||err.name==="AbortError"}exports.addDetectionContext=addDetectionContext;exports.addNamespace=addNamespace;exports.averageByDay=averageByDay;exports.buildWaterReportCSV=buildWaterReportCSV;exports.buildWaterStoresCSV=buildWaterStoresCSV;exports.calcDeltaPercent=calcDeltaPercent;exports.classify=classify;exports.classifyWaterLabel=classifyWaterLabel;exports.classifyWaterLabels=classifyWaterLabels;exports.decodePayload=decodePayload;exports.decodePayloadBase64Xor=decodePayloadBase64Xor;exports.detectDeviceType=detectDeviceType;exports.determineInterval=determineInterval;exports.exportToCSV=exportToCSV;exports.exportToCSVAll=exportToCSVAll;exports.fetchWithRetry=fetchWithRetry;exports.findValue=findValue;exports.fmtPerc=fmtPerc;exports.fmtPercLegacy=fmtPerc2;exports.formatAllInSameUnit=formatAllInSameUnit;exports.formatAllInSameWaterUnit=formatAllInSameWaterUnit;exports.formatDateForInput=formatDateForInput;exports.formatDateToYMD=formatDateToYMD;exports.formatDateWithTimezoneOffset=formatDateWithTimezoneOffset;exports.formatEnergy=formatEnergy;exports.formatEnergyByGroup=formatEnergyByGroup;exports.formatNumberReadable=formatNumberReadable;exports.formatTankHeadFromCm=formatTankHeadFromCm;exports.formatWaterVolumeM3=formatWaterVolumeM3;exports.getAvailableContexts=getAvailableContexts;exports.getDateRangeArray=getDateRangeArray;exports.getEntityInfoAndAttributesTB=getEntityInfoAndAttributesTB;exports.getSaoPauloISOString=getSaoPauloISOString;exports.getSaoPauloISOStringFixed=getSaoPauloISOStringFixed;exports.getValueByDatakey=getValueByDatakey;exports.getValueByDatakeyLegacy=getValueByDatakeyLegacy;exports.getWaterCategories=getWaterCategories;exports.groupByDay=groupByDay;exports.http=http;exports.isWaterCategory=isWaterCategory;exports.normalizeRecipients=normalizeRecipients;exports.numbers=numbers_exports;exports.parseInputDateToDate=parseInputDateToDate;exports.strings=strings_exports;exports.timeWindowFromInputYMD=timeWindowFromInputYMD;exports.toCSV=toCSV;exports.toFixedSafe=toFixedSafe});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myio-js-library",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "A clean, standalone JS SDK for MYIO projects",
5
5
  "license": "MIT",
6
6
  "repository": "github:gh-myio/myio-js-library",