@justair/justair-library 7.6.0 → 7.7.0-alpha.1cf63be

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/SECURITY.md ADDED
@@ -0,0 +1,21 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes are prioritized for the current default branch and actively supported releases of `JustAir-Solutions/justair-library`. Older or retired versions may not receive fixes.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Do not open a public GitHub issue for a suspected vulnerability.
10
+
11
+ Send the initial report to `info@justair.co` with the subject `Security vulnerability: justair-library`. Include the affected repository or component, the potential impact, minimal reproduction steps, and a way to contact you.
12
+
13
+ Do not include passwords, tokens, private keys, personal data, regulated data, or other sensitive payloads in the initial message. Ask for an approved secure transfer method when sensitive evidence is necessary.
14
+
15
+ ## Testing boundaries
16
+
17
+ This policy does not grant authorization to access data that is not yours, disrupt service, perform denial-of-service testing, use social engineering, destroy data, or establish persistence. Stop testing and report the issue if sensitive data or credentials are encountered.
18
+
19
+ ## Coordinated disclosure
20
+
21
+ Please allow JustAir reasonable time to investigate and remediate before public disclosure. Receipt of a report does not create a promise of a specific response or remediation timeline.
@@ -4,4 +4,5 @@ export * from "./userNotifications.js";
4
4
  export * from "./deviceTypes.js";
5
5
  export * from "./timezones.js";
6
6
  export * from "./dataDownloads.js";
7
+ export * from "./terms.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -6,8 +6,8 @@ export namespace PARAMETERS {
6
6
  let unit: string;
7
7
  let type: string;
8
8
  let rfc: any;
9
- let origin: any;
10
- let impacts: any;
9
+ let origin: string;
10
+ let impacts: string;
11
11
  }
12
12
  namespace NO2 {
13
13
  let id_1: string;
@@ -94,9 +94,9 @@ export namespace PARAMETERS {
94
94
  export { type_5 as type };
95
95
  let rfc_5: any;
96
96
  export { rfc_5 as rfc };
97
- let origin_5: any;
97
+ let origin_5: string;
98
98
  export { origin_5 as origin };
99
- let impacts_5: any;
99
+ let impacts_5: string;
100
100
  export { impacts_5 as impacts };
101
101
  }
102
102
  namespace BC {
@@ -112,9 +112,9 @@ export namespace PARAMETERS {
112
112
  export { type_6 as type };
113
113
  let rfc_6: any;
114
114
  export { rfc_6 as rfc };
115
- let origin_6: any;
115
+ let origin_6: string;
116
116
  export { origin_6 as origin };
117
- let impacts_6: any;
117
+ let impacts_6: string;
118
118
  export { impacts_6 as impacts };
119
119
  }
120
120
  namespace BRC {
@@ -130,9 +130,9 @@ export namespace PARAMETERS {
130
130
  export { type_7 as type };
131
131
  let rfc_7: any;
132
132
  export { rfc_7 as rfc };
133
- let origin_7: any;
133
+ let origin_7: string;
134
134
  export { origin_7 as origin };
135
- let impacts_7: any;
135
+ let impacts_7: string;
136
136
  export { impacts_7 as impacts };
137
137
  }
138
138
  namespace CO2 {
@@ -148,9 +148,9 @@ export namespace PARAMETERS {
148
148
  export { type_8 as type };
149
149
  let rfc_8: any;
150
150
  export { rfc_8 as rfc };
151
- let origin_8: any;
151
+ let origin_8: string;
152
152
  export { origin_8 as origin };
153
- let impacts_8: any;
153
+ let impacts_8: string;
154
154
  export { impacts_8 as impacts };
155
155
  }
156
156
  namespace H2S {
@@ -166,9 +166,9 @@ export namespace PARAMETERS {
166
166
  export { type_9 as type };
167
167
  let rfc_9: any;
168
168
  export { rfc_9 as rfc };
169
- let origin_9: any;
169
+ let origin_9: string;
170
170
  export { origin_9 as origin };
171
- let impacts_9: any;
171
+ let impacts_9: string;
172
172
  export { impacts_9 as impacts };
173
173
  }
174
174
  namespace CH4 {
@@ -184,9 +184,9 @@ export namespace PARAMETERS {
184
184
  export { type_10 as type };
185
185
  let rfc_10: any;
186
186
  export { rfc_10 as rfc };
187
- let origin_10: any;
187
+ let origin_10: string;
188
188
  export { origin_10 as origin };
189
- let impacts_10: any;
189
+ let impacts_10: string;
190
190
  export { impacts_10 as impacts };
191
191
  }
192
192
  namespace NO {
@@ -202,12 +202,12 @@ export namespace PARAMETERS {
202
202
  export { type_11 as type };
203
203
  let rfc_11: any;
204
204
  export { rfc_11 as rfc };
205
- let origin_11: any;
205
+ let origin_11: string;
206
206
  export { origin_11 as origin };
207
- let impacts_11: any;
207
+ let impacts_11: string;
208
208
  export { impacts_11 as impacts };
209
209
  }
210
- namespace PM1 {
210
+ namespace RN {
211
211
  let id_12: string;
212
212
  export { id_12 as id };
213
213
  let label_12: string;
@@ -218,14 +218,14 @@ export namespace PARAMETERS {
218
218
  export { unit_12 as unit };
219
219
  let type_12: string;
220
220
  export { type_12 as type };
221
- let rfc_12: any;
221
+ let rfc_12: number;
222
222
  export { rfc_12 as rfc };
223
- let origin_12: any;
223
+ let origin_12: string;
224
224
  export { origin_12 as origin };
225
- let impacts_12: any;
225
+ let impacts_12: string;
226
226
  export { impacts_12 as impacts };
227
227
  }
228
- namespace UFP {
228
+ namespace PM1 {
229
229
  let id_13: string;
230
230
  export { id_13 as id };
231
231
  let label_13: string;
@@ -238,12 +238,12 @@ export namespace PARAMETERS {
238
238
  export { type_13 as type };
239
239
  let rfc_13: any;
240
240
  export { rfc_13 as rfc };
241
- let origin_13: any;
241
+ let origin_13: string;
242
242
  export { origin_13 as origin };
243
- let impacts_13: any;
243
+ let impacts_13: string;
244
244
  export { impacts_13 as impacts };
245
245
  }
246
- namespace VOC {
246
+ namespace UFP {
247
247
  let id_14: string;
248
248
  export { id_14 as id };
249
249
  let label_14: string;
@@ -256,12 +256,12 @@ export namespace PARAMETERS {
256
256
  export { type_14 as type };
257
257
  let rfc_14: any;
258
258
  export { rfc_14 as rfc };
259
- let origin_14: any;
259
+ let origin_14: string;
260
260
  export { origin_14 as origin };
261
- let impacts_14: any;
261
+ let impacts_14: string;
262
262
  export { impacts_14 as impacts };
263
263
  }
264
- namespace DP {
264
+ namespace VOC {
265
265
  let id_15: string;
266
266
  export { id_15 as id };
267
267
  let label_15: string;
@@ -274,12 +274,12 @@ export namespace PARAMETERS {
274
274
  export { type_15 as type };
275
275
  let rfc_15: any;
276
276
  export { rfc_15 as rfc };
277
- let origin_15: any;
277
+ let origin_15: string;
278
278
  export { origin_15 as origin };
279
- let impacts_15: any;
279
+ let impacts_15: string;
280
280
  export { impacts_15 as impacts };
281
281
  }
282
- namespace DP_AMB {
282
+ namespace DP {
283
283
  let id_16: string;
284
284
  export { id_16 as id };
285
285
  let label_16: string;
@@ -297,7 +297,7 @@ export namespace PARAMETERS {
297
297
  let impacts_16: any;
298
298
  export { impacts_16 as impacts };
299
299
  }
300
- namespace RH {
300
+ namespace DP_AMB {
301
301
  let id_17: string;
302
302
  export { id_17 as id };
303
303
  let label_17: string;
@@ -315,7 +315,7 @@ export namespace PARAMETERS {
315
315
  let impacts_17: any;
316
316
  export { impacts_17 as impacts };
317
317
  }
318
- namespace RH_AMB {
318
+ namespace RH {
319
319
  let id_18: string;
320
320
  export { id_18 as id };
321
321
  let label_18: string;
@@ -333,7 +333,7 @@ export namespace PARAMETERS {
333
333
  let impacts_18: any;
334
334
  export { impacts_18 as impacts };
335
335
  }
336
- namespace T {
336
+ namespace RH_AMB {
337
337
  let id_19: string;
338
338
  export { id_19 as id };
339
339
  let label_19: string;
@@ -351,7 +351,7 @@ export namespace PARAMETERS {
351
351
  let impacts_19: any;
352
352
  export { impacts_19 as impacts };
353
353
  }
354
- namespace T_AMB {
354
+ namespace T {
355
355
  let id_20: string;
356
356
  export { id_20 as id };
357
357
  let label_20: string;
@@ -369,7 +369,7 @@ export namespace PARAMETERS {
369
369
  let impacts_20: any;
370
370
  export { impacts_20 as impacts };
371
371
  }
372
- namespace WD {
372
+ namespace T_AMB {
373
373
  let id_21: string;
374
374
  export { id_21 as id };
375
375
  let label_21: string;
@@ -387,7 +387,7 @@ export namespace PARAMETERS {
387
387
  let impacts_21: any;
388
388
  export { impacts_21 as impacts };
389
389
  }
390
- namespace WS {
390
+ namespace WD {
391
391
  let id_22: string;
392
392
  export { id_22 as id };
393
393
  let label_22: string;
@@ -405,7 +405,7 @@ export namespace PARAMETERS {
405
405
  let impacts_22: any;
406
406
  export { impacts_22 as impacts };
407
407
  }
408
- namespace GS {
408
+ namespace WS {
409
409
  let id_23: string;
410
410
  export { id_23 as id };
411
411
  let label_23: string;
@@ -423,7 +423,7 @@ export namespace PARAMETERS {
423
423
  let impacts_23: any;
424
424
  export { impacts_23 as impacts };
425
425
  }
426
- namespace R {
426
+ namespace GS {
427
427
  let id_24: string;
428
428
  export { id_24 as id };
429
429
  let label_24: string;
@@ -441,7 +441,7 @@ export namespace PARAMETERS {
441
441
  let impacts_24: any;
442
442
  export { impacts_24 as impacts };
443
443
  }
444
- namespace SR {
444
+ namespace R {
445
445
  let id_25: string;
446
446
  export { id_25 as id };
447
447
  let label_25: string;
@@ -459,7 +459,7 @@ export namespace PARAMETERS {
459
459
  let impacts_25: any;
460
460
  export { impacts_25 as impacts };
461
461
  }
462
- namespace P {
462
+ namespace SR {
463
463
  let id_26: string;
464
464
  export { id_26 as id };
465
465
  let label_26: string;
@@ -477,7 +477,7 @@ export namespace PARAMETERS {
477
477
  let impacts_26: any;
478
478
  export { impacts_26 as impacts };
479
479
  }
480
- namespace PAR {
480
+ namespace P {
481
481
  let id_27: string;
482
482
  export { id_27 as id };
483
483
  let label_27: string;
@@ -495,7 +495,7 @@ export namespace PARAMETERS {
495
495
  let impacts_27: any;
496
496
  export { impacts_27 as impacts };
497
497
  }
498
- namespace ARM {
498
+ namespace PAR {
499
499
  let id_28: string;
500
500
  export { id_28 as id };
501
501
  let label_28: string;
@@ -513,7 +513,7 @@ export namespace PARAMETERS {
513
513
  let impacts_28: any;
514
514
  export { impacts_28 as impacts };
515
515
  }
516
- namespace ARW {
516
+ namespace ARM {
517
517
  let id_29: string;
518
518
  export { id_29 as id };
519
519
  let label_29: string;
@@ -531,7 +531,7 @@ export namespace PARAMETERS {
531
531
  let impacts_29: any;
532
532
  export { impacts_29 as impacts };
533
533
  }
534
- namespace ARD {
534
+ namespace ARW {
535
535
  let id_30: string;
536
536
  export { id_30 as id };
537
537
  let label_30: string;
@@ -549,7 +549,7 @@ export namespace PARAMETERS {
549
549
  let impacts_30: any;
550
550
  export { impacts_30 as impacts };
551
551
  }
552
- namespace ARH {
552
+ namespace ARD {
553
553
  let id_31: string;
554
554
  export { id_31 as id };
555
555
  let label_31: string;
@@ -567,9 +567,7 @@ export namespace PARAMETERS {
567
567
  let impacts_31: any;
568
568
  export { impacts_31 as impacts };
569
569
  }
570
- }
571
- export namespace HEAVY_METALS {
572
- export namespace AS {
570
+ namespace ARH {
573
571
  let id_32: string;
574
572
  export { id_32 as id };
575
573
  let label_32: string;
@@ -580,14 +578,16 @@ export namespace HEAVY_METALS {
580
578
  export { unit_32 as unit };
581
579
  let type_32: string;
582
580
  export { type_32 as type };
583
- let rfc_32: number;
581
+ let rfc_32: any;
584
582
  export { rfc_32 as rfc };
585
- let origin_32: string;
583
+ let origin_32: any;
586
584
  export { origin_32 as origin };
587
- let impacts_32: string;
585
+ let impacts_32: any;
588
586
  export { impacts_32 as impacts };
589
587
  }
590
- export namespace BA {
588
+ }
589
+ export namespace HEAVY_METALS {
590
+ export namespace AS {
591
591
  let id_33: string;
592
592
  export { id_33 as id };
593
593
  let label_33: string;
@@ -598,14 +598,14 @@ export namespace HEAVY_METALS {
598
598
  export { unit_33 as unit };
599
599
  let type_33: string;
600
600
  export { type_33 as type };
601
- let rfc_33: any;
601
+ let rfc_33: number;
602
602
  export { rfc_33 as rfc };
603
603
  let origin_33: string;
604
604
  export { origin_33 as origin };
605
605
  let impacts_33: string;
606
606
  export { impacts_33 as impacts };
607
607
  }
608
- export namespace C6H6 {
608
+ export namespace BA {
609
609
  let id_34: string;
610
610
  export { id_34 as id };
611
611
  let label_34: string;
@@ -616,14 +616,14 @@ export namespace HEAVY_METALS {
616
616
  export { unit_34 as unit };
617
617
  let type_34: string;
618
618
  export { type_34 as type };
619
- let rfc_34: number;
619
+ let rfc_34: any;
620
620
  export { rfc_34 as rfc };
621
621
  let origin_34: string;
622
622
  export { origin_34 as origin };
623
623
  let impacts_34: string;
624
624
  export { impacts_34 as impacts };
625
625
  }
626
- export namespace BE {
626
+ export namespace C6H6 {
627
627
  let id_35: string;
628
628
  export { id_35 as id };
629
629
  let label_35: string;
@@ -641,7 +641,7 @@ export namespace HEAVY_METALS {
641
641
  let impacts_35: string;
642
642
  export { impacts_35 as impacts };
643
643
  }
644
- export namespace CD {
644
+ export namespace BE {
645
645
  let id_36: string;
646
646
  export { id_36 as id };
647
647
  let label_36: string;
@@ -659,7 +659,7 @@ export namespace HEAVY_METALS {
659
659
  let impacts_36: string;
660
660
  export { impacts_36 as impacts };
661
661
  }
662
- export namespace CR {
662
+ export namespace CD {
663
663
  let id_37: string;
664
664
  export { id_37 as id };
665
665
  let label_37: string;
@@ -677,7 +677,7 @@ export namespace HEAVY_METALS {
677
677
  let impacts_37: string;
678
678
  export { impacts_37 as impacts };
679
679
  }
680
- export namespace CO_1 {
680
+ export namespace CR {
681
681
  let id_38: string;
682
682
  export { id_38 as id };
683
683
  let label_38: string;
@@ -688,15 +688,14 @@ export namespace HEAVY_METALS {
688
688
  export { unit_38 as unit };
689
689
  let type_38: string;
690
690
  export { type_38 as type };
691
- let rfc_38: any;
691
+ let rfc_38: number;
692
692
  export { rfc_38 as rfc };
693
693
  let origin_38: string;
694
694
  export { origin_38 as origin };
695
695
  let impacts_38: string;
696
696
  export { impacts_38 as impacts };
697
697
  }
698
- export { CO_1 as CO };
699
- export namespace CU {
698
+ export namespace CO_1 {
700
699
  let id_39: string;
701
700
  export { id_39 as id };
702
701
  let label_39: string;
@@ -714,7 +713,8 @@ export namespace HEAVY_METALS {
714
713
  let impacts_39: string;
715
714
  export { impacts_39 as impacts };
716
715
  }
717
- export namespace FE {
716
+ export { CO_1 as CO };
717
+ export namespace CU {
718
718
  let id_40: string;
719
719
  export { id_40 as id };
720
720
  let label_40: string;
@@ -732,7 +732,7 @@ export namespace HEAVY_METALS {
732
732
  let impacts_40: string;
733
733
  export { impacts_40 as impacts };
734
734
  }
735
- export namespace PB {
735
+ export namespace FE {
736
736
  let id_41: string;
737
737
  export { id_41 as id };
738
738
  let label_41: string;
@@ -743,14 +743,14 @@ export namespace HEAVY_METALS {
743
743
  export { unit_41 as unit };
744
744
  let type_41: string;
745
745
  export { type_41 as type };
746
- let rfc_41: number;
746
+ let rfc_41: any;
747
747
  export { rfc_41 as rfc };
748
748
  let origin_41: string;
749
749
  export { origin_41 as origin };
750
750
  let impacts_41: string;
751
751
  export { impacts_41 as impacts };
752
752
  }
753
- export namespace MN {
753
+ export namespace PB {
754
754
  let id_42: string;
755
755
  export { id_42 as id };
756
756
  let label_42: string;
@@ -768,7 +768,7 @@ export namespace HEAVY_METALS {
768
768
  let impacts_42: string;
769
769
  export { impacts_42 as impacts };
770
770
  }
771
- export namespace NI {
771
+ export namespace MN {
772
772
  let id_43: string;
773
773
  export { id_43 as id };
774
774
  let label_43: string;
@@ -779,14 +779,14 @@ export namespace HEAVY_METALS {
779
779
  export { unit_43 as unit };
780
780
  let type_43: string;
781
781
  export { type_43 as type };
782
- let rfc_43: any;
782
+ let rfc_43: number;
783
783
  export { rfc_43 as rfc };
784
784
  let origin_43: string;
785
785
  export { origin_43 as origin };
786
786
  let impacts_43: string;
787
787
  export { impacts_43 as impacts };
788
788
  }
789
- export namespace SE {
789
+ export namespace NI {
790
790
  let id_44: string;
791
791
  export { id_44 as id };
792
792
  let label_44: string;
@@ -804,7 +804,7 @@ export namespace HEAVY_METALS {
804
804
  let impacts_44: string;
805
805
  export { impacts_44 as impacts };
806
806
  }
807
- export namespace ZN {
807
+ export namespace SE {
808
808
  let id_45: string;
809
809
  export { id_45 as id };
810
810
  let label_45: string;
@@ -822,5 +822,23 @@ export namespace HEAVY_METALS {
822
822
  let impacts_45: string;
823
823
  export { impacts_45 as impacts };
824
824
  }
825
+ export namespace ZN {
826
+ let id_46: string;
827
+ export { id_46 as id };
828
+ let label_46: string;
829
+ export { label_46 as label };
830
+ let name_46: string;
831
+ export { name_46 as name };
832
+ let unit_46: string;
833
+ export { unit_46 as unit };
834
+ let type_46: string;
835
+ export { type_46 as type };
836
+ let rfc_46: any;
837
+ export { rfc_46 as rfc };
838
+ let origin_46: string;
839
+ export { origin_46 as origin };
840
+ let impacts_46: string;
841
+ export { impacts_46 as impacts };
842
+ }
825
843
  }
826
844
  //# sourceMappingURL=pollutants.d.ts.map
@@ -0,0 +1,2 @@
1
+ export const TERMS_VERSION_MAX_LENGTH: 32;
2
+ //# sourceMappingURL=terms.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terms.d.ts","sourceRoot":"","sources":["../../src/constants/terms.js"],"names":[],"mappings":"AAaA,0CAA2C"}
package/dist/index.d.ts CHANGED
@@ -103,6 +103,7 @@ import { PARAMETERS } from "./constants/pollutants.js";
103
103
  import { HEAVY_METALS } from "./constants/pollutants.js";
104
104
  import { ALERT_LEVEL_WEIGHTS } from "./constants/alertLevels.js";
105
105
  import { getAlertWeight } from "./constants/alertLevels.js";
106
+ import { TERMS_VERSION_MAX_LENGTH } from "./constants/terms.js";
106
107
  import { DEVICE_TYPES } from "./constants/deviceTypes.js";
107
108
  import { DEVICE_TYPE_VALUES } from "./constants/deviceTypes.js";
108
109
  import { isAqiCapableDeviceType } from "./constants/deviceTypes.js";
@@ -121,5 +122,5 @@ import { validateQuietHours } from "./utils/quietHours.js";
121
122
  import { getZoneMinutes } from "./utils/quietHours.js";
122
123
  import { isInQuietWindow } from "./utils/quietHours.js";
123
124
  import { resolveQuietHours } from "./utils/quietHours.js";
124
- export { Database, adminSchema, Admin, configurationsSchema, Configurations, measurementsSchema, Measurements, monitorRequestsSchema, MonitorRequests, monitorsSchema, Monitors, organizationsSchema, Organizations, usersSchema, Users, COOKIE_CONSENT_CATEGORIES, COOKIE_CONSENT_VALUES, validateCookieConsent, eventsSchema, Events, lightMonitorSchema, LightMonitors, monitorSuppliersSchema, MonitorSuppliers, contextsSchema, Contexts, parametersSchema, Parameters, announcementSchema, Announcements, jobsSchema, Jobs, apiKeySchema, ApiKey, UsageMetrics, usageMetricsSchema, Audit, auditSchema, EventsAudit, eventsAuditSchema, MonitorAudit, monitorAuditSchema, parametersEnum, deploymentTypesEnum, AlertsAudit, Alerts, alertsSchema, alertsAuditSchema, Features, featuresSchema, dataCompletenessSchema, DataCompleteness, networkMetricsSchema, NetworkMetrics, rateOfChangeSchema, RateOfChange, changeStreamCheckpointsSchema, ChangeStreamCheckpoints, sitesSchema, Sites, sampleSitesSchema, SampleSites, sampleSiteAuditSchema, SampleSiteAudit, sampleParametersEnum, samplesSchema, Samples, samplesAuditSchema, SamplesAudit, sampleParameterReferenceConcentrations, passwordResetTokenSchema, PasswordResetToken, monitorEpisodesSchema, MonitorEpisode, userNotificationsSchema, UserNotification, AqiAlertNotification, DeviceOfflineNotification, WeeklyDigestNotification, AccountNotification, NOTIFICATION_KINDS, NOTIFICATION_CHANNELS, DELIVERY_STATUSES, DATA_DOWNLOAD_TYPES, dataDownloadSchema, DataDownloads, AqiDataDownload, SampleDataDownload, DATA_DOWNLOAD_MAX_RANGE_DAYS, DATA_DOWNLOAD_ERRORS, validateDataDownloadDateOrder, validateDataDownloadMaxDateRange, validateDataDownloadEmail, normalizeDataDownloadEmail, validateDataDownloadTimezone, normalizeDataDownloadTimezone, PARAMETERS, HEAVY_METALS, ALERT_LEVEL_WEIGHTS, getAlertWeight, DEVICE_TYPES, DEVICE_TYPE_VALUES, isAqiCapableDeviceType, AQI_CAPABLE_DEVICE_TYPES, projectSubscribedMonitors, resolveSubscribedMonitorIdsByUser, VISIBLE_SITE_MONITOR_FILTER, SUBSCRIBED_DEVICES_ERRORS, splitName, DEFAULT_QUIET_HOURS, DEFAULT_TIMEZONE, QUIET_HOURS_ERRORS, isValidTimeString, isValidIANATimezone, validateQuietHours, getZoneMinutes, isInQuietWindow, resolveQuietHours };
125
+ export { Database, adminSchema, Admin, configurationsSchema, Configurations, measurementsSchema, Measurements, monitorRequestsSchema, MonitorRequests, monitorsSchema, Monitors, organizationsSchema, Organizations, usersSchema, Users, COOKIE_CONSENT_CATEGORIES, COOKIE_CONSENT_VALUES, validateCookieConsent, eventsSchema, Events, lightMonitorSchema, LightMonitors, monitorSuppliersSchema, MonitorSuppliers, contextsSchema, Contexts, parametersSchema, Parameters, announcementSchema, Announcements, jobsSchema, Jobs, apiKeySchema, ApiKey, UsageMetrics, usageMetricsSchema, Audit, auditSchema, EventsAudit, eventsAuditSchema, MonitorAudit, monitorAuditSchema, parametersEnum, deploymentTypesEnum, AlertsAudit, Alerts, alertsSchema, alertsAuditSchema, Features, featuresSchema, dataCompletenessSchema, DataCompleteness, networkMetricsSchema, NetworkMetrics, rateOfChangeSchema, RateOfChange, changeStreamCheckpointsSchema, ChangeStreamCheckpoints, sitesSchema, Sites, sampleSitesSchema, SampleSites, sampleSiteAuditSchema, SampleSiteAudit, sampleParametersEnum, samplesSchema, Samples, samplesAuditSchema, SamplesAudit, sampleParameterReferenceConcentrations, passwordResetTokenSchema, PasswordResetToken, monitorEpisodesSchema, MonitorEpisode, userNotificationsSchema, UserNotification, AqiAlertNotification, DeviceOfflineNotification, WeeklyDigestNotification, AccountNotification, NOTIFICATION_KINDS, NOTIFICATION_CHANNELS, DELIVERY_STATUSES, DATA_DOWNLOAD_TYPES, dataDownloadSchema, DataDownloads, AqiDataDownload, SampleDataDownload, DATA_DOWNLOAD_MAX_RANGE_DAYS, DATA_DOWNLOAD_ERRORS, validateDataDownloadDateOrder, validateDataDownloadMaxDateRange, validateDataDownloadEmail, normalizeDataDownloadEmail, validateDataDownloadTimezone, normalizeDataDownloadTimezone, PARAMETERS, HEAVY_METALS, ALERT_LEVEL_WEIGHTS, getAlertWeight, TERMS_VERSION_MAX_LENGTH, DEVICE_TYPES, DEVICE_TYPE_VALUES, isAqiCapableDeviceType, AQI_CAPABLE_DEVICE_TYPES, projectSubscribedMonitors, resolveSubscribedMonitorIdsByUser, VISIBLE_SITE_MONITOR_FILTER, SUBSCRIBED_DEVICES_ERRORS, splitName, DEFAULT_QUIET_HOURS, DEFAULT_TIMEZONE, QUIET_HOURS_ERRORS, isValidTimeString, isValidIANATimezone, validateQuietHours, getZoneMinutes, isInQuietWindow, resolveQuietHours };
125
126
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAyKA;;;iBAEC;yBAtGwB,oBAAoB;qBADxB,gBAAgB;4BApEF,mBAAmB;sBAAnB,mBAAmB;qCAI/C,4BAA4B;+BAA5B,4BAA4B;mCAM5B,0BAA0B;6BAA1B,0BAA0B;sCAI1B,6BAA6B;gCAA7B,6BAA6B;+BAQ7B,sBAAsB;yBAAtB,sBAAsB;oCACsB,2BAA2B;8BAA3B,2BAA2B;4BAcvE,mBAAmB;sBAAnB,mBAAmB;0CAAnB,mBAAmB;sCAAnB,mBAAmB;sCAAnB,mBAAmB;6BAMnB,oBAAoB;uBAApB,oBAAoB;mCAnBuB,2BAA2B;8BAA3B,2BAA2B;uCAItE,8BAA8B;iCAA9B,8BAA8B;+BACI,sBAAsB;yBAAtB,sBAAsB;iCAClB,wBAAwB;2BAAxB,wBAAwB;mCAcnB,2BAA2B;8BAA3B,2BAA2B;2BAC5C,kBAAkB;qBAAlB,kBAAkB;6BACd,oBAAoB;uBAApB,oBAAoB;6BACR,0BAA0B;mCAA1B,0BAA0B;sBArCpE,0BAA0B;4BAA1B,0BAA0B;4BAiC1B,oBAAoB;kCAApB,oBAAoB;6BArBpB,sBAAsB;mCAAtB,sBAAsB;+BAAtB,sBAAsB;oCAAtB,sBAAsB;4BA+BtB,oBAAoB;uBAApB,oBAAoB;6BAApB,oBAAoB;kCAApB,oBAAoB;yBACc,sBAAsB;+BAAtB,sBAAsB;uCAIxD,8BAA8B;iCAA9B,8BAA8B;qCAI9B,4BAA4B;+BAA5B,4BAA4B;mCACc,0BAA0B;6BAA1B,0BAA0B;8CAIpE,qCAAqC;wCAArC,qCAAqC;4BAkDT,mBAAmB;sBAAnB,mBAAmB;kCAS/C,yBAAyB;4BAAzB,yBAAyB;sCAAzB,yBAAyB;gCAAzB,yBAAyB;qCAAzB,yBAAyB;8BASzB,qBAAqB;wBAArB,qBAAqB;mCAArB,qBAAqB;6BAArB,qBAAqB;uDAArB,qBAAqB;yCAIrB,gCAAgC;mCAAhC,gCAAgC;sCArDhC,6BAA6B;+BAA7B,6BAA6B;wCAW7B,+BAA+B;iCAA/B,+BAA+B;qCAA/B,+BAA+B;0CAA/B,+BAA+B;yCAA/B,+BAA+B;oCAA/B,+BAA+B;mCAA/B,+BAA+B;sCAA/B,+BAA+B;kCAA/B,+BAA+B;oCArBF,8BAA8B;mCAqE3D,2BAA2B;8BAA3B,2BAA2B;gCAA3B,2BAA2B;mCAA3B,2BAA2B;+DAsB3B,mCAAmC;qCAAnC,mCAAmC;mEAAnC,mCAAmC;yEAAnC,mCAAmC;mEAAnC,mCAAmC;qEAAnC,mCAAmC;yEAAnC,mCAAmC;2EAAnC,mCAAmC;2BAjGD,2BAA2B;6BAA3B,2BAA2B;oCAI7D,4BAA4B;+BAA5B,4BAA4B;6BAQ5B,4BAA4B;mCAA5B,4BAA4B;uCAA5B,4BAA4B;yCAA5B,4BAA4B;0CAgC5B,8BAA8B;kDAA9B,8BAA8B;4CAA9B,8BAA8B;0CAA9B,8BAA8B;0BAhBX,sBAAsB;oCASzC,uBAAuB;iCAhCG,0BAA0B;mCAgCpD,uBAAuB;kCAAvB,uBAAuB;oCACM,qBAAqB;mCADlD,uBAAuB;+BAAvB,uBAAuB;gCAAvB,uBAAuB;kCAAvB,uBAAuB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AA0KA;;;iBAEC;yBAvGwB,oBAAoB;qBADxB,gBAAgB;4BApEF,mBAAmB;sBAAnB,mBAAmB;qCAI/C,4BAA4B;+BAA5B,4BAA4B;mCAM5B,0BAA0B;6BAA1B,0BAA0B;sCAI1B,6BAA6B;gCAA7B,6BAA6B;+BAQ7B,sBAAsB;yBAAtB,sBAAsB;oCACsB,2BAA2B;8BAA3B,2BAA2B;4BAcvE,mBAAmB;sBAAnB,mBAAmB;0CAAnB,mBAAmB;sCAAnB,mBAAmB;sCAAnB,mBAAmB;6BAMnB,oBAAoB;uBAApB,oBAAoB;mCAnBuB,2BAA2B;8BAA3B,2BAA2B;uCAItE,8BAA8B;iCAA9B,8BAA8B;+BACI,sBAAsB;yBAAtB,sBAAsB;iCAClB,wBAAwB;2BAAxB,wBAAwB;mCAcnB,2BAA2B;8BAA3B,2BAA2B;2BAC5C,kBAAkB;qBAAlB,kBAAkB;6BACd,oBAAoB;uBAApB,oBAAoB;6BACR,0BAA0B;mCAA1B,0BAA0B;sBArCpE,0BAA0B;4BAA1B,0BAA0B;4BAiC1B,oBAAoB;kCAApB,oBAAoB;6BArBpB,sBAAsB;mCAAtB,sBAAsB;+BAAtB,sBAAsB;oCAAtB,sBAAsB;4BA+BtB,oBAAoB;uBAApB,oBAAoB;6BAApB,oBAAoB;kCAApB,oBAAoB;yBACc,sBAAsB;+BAAtB,sBAAsB;uCAIxD,8BAA8B;iCAA9B,8BAA8B;qCAI9B,4BAA4B;+BAA5B,4BAA4B;mCACc,0BAA0B;6BAA1B,0BAA0B;8CAIpE,qCAAqC;wCAArC,qCAAqC;4BAmDT,mBAAmB;sBAAnB,mBAAmB;kCAS/C,yBAAyB;4BAAzB,yBAAyB;sCAAzB,yBAAyB;gCAAzB,yBAAyB;qCAAzB,yBAAyB;8BASzB,qBAAqB;wBAArB,qBAAqB;mCAArB,qBAAqB;6BAArB,qBAAqB;uDAArB,qBAAqB;yCAIrB,gCAAgC;mCAAhC,gCAAgC;sCArDhC,6BAA6B;+BAA7B,6BAA6B;wCAW7B,+BAA+B;iCAA/B,+BAA+B;qCAA/B,+BAA+B;0CAA/B,+BAA+B;yCAA/B,+BAA+B;oCAA/B,+BAA+B;mCAA/B,+BAA+B;sCAA/B,+BAA+B;kCAA/B,+BAA+B;oCAtBF,8BAA8B;mCAsE3D,2BAA2B;8BAA3B,2BAA2B;gCAA3B,2BAA2B;mCAA3B,2BAA2B;+DAsB3B,mCAAmC;qCAAnC,mCAAmC;mEAAnC,mCAAmC;yEAAnC,mCAAmC;mEAAnC,mCAAmC;qEAAnC,mCAAmC;yEAAnC,mCAAmC;2EAAnC,mCAAmC;2BAlGD,2BAA2B;6BAA3B,2BAA2B;oCAI7D,4BAA4B;+BAA5B,4BAA4B;yCAGM,sBAAsB;6BAMxD,4BAA4B;mCAA5B,4BAA4B;uCAA5B,4BAA4B;yCAA5B,4BAA4B;0CAgC5B,8BAA8B;kDAA9B,8BAA8B;4CAA9B,8BAA8B;0CAA9B,8BAA8B;0BAhBX,sBAAsB;oCASzC,uBAAuB;iCAjCG,0BAA0B;mCAiCpD,uBAAuB;kCAAvB,uBAAuB;oCACM,qBAAqB;mCADlD,uBAAuB;+BAAvB,uBAAuB;gCAAvB,uBAAuB;kCAAvB,uBAAuB"}
@@ -1 +1 @@
1
- {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/models/users.js"],"names":[],"mappings":"AA6FA,8BA0GE;AAiCF,wBAAmD;AA/NnD,iDAA4D;AAC5D,6CAAmD;AAUnD,4DA0BC"}
1
+ {"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/models/users.js"],"names":[],"mappings":"AA8FA,8BAuIE;AAiCF,wBAAmD;AA5PnD,iDAA4D;AAC5D,6CAAmD;AAUnD,4DA0BC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justair/justair-library",
3
- "version": "7.6.0",
3
+ "version": "7.7.0-alpha.1cf63be",
4
4
  "description": "JustAir Internal Library",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -13,3 +13,4 @@ export * from "./userNotifications.js";
13
13
  export * from "./deviceTypes.js";
14
14
  export * from "./timezones.js";
15
15
  export * from "./dataDownloads.js";
16
+ export * from "./terms.js";
@@ -8,8 +8,8 @@ export const PARAMETERS = {
8
8
  unit: "ppm",
9
9
  type: "Gas",
10
10
  rfc: null,
11
- origin: null,
12
- impacts: null,
11
+ origin: "Made when fuel burns without enough oxygen. Comes mostly from car exhaust outdoors, and from heaters, furnaces, and gas stoves indoors. Has no color or smell.",
12
+ impacts: "Makes it harder for blood to carry oxygen to the heart and brain. High indoor levels can cause confusion, passing out, or death.",
13
13
  },
14
14
  NO2: {
15
15
  id: "NO2",
@@ -18,7 +18,7 @@ export const PARAMETERS = {
18
18
  unit: "ppb",
19
19
  type: "Gas",
20
20
  rfc: null,
21
- origin: "Harmful gas from cars, power plants, and industrial equipment that contributes to smog and acid rain.",
21
+ origin: "Harmful gas from cars, power plants, and industrial equipment that contributes to smog and acid rain. Smells sharp and bitter.",
22
22
  impacts: "Can cause respiratory problems, worsen asthma and bronchitis, and increase heart-related health risks.",
23
23
  },
24
24
  O3: {
@@ -28,7 +28,7 @@ export const PARAMETERS = {
28
28
  unit: "ppm",
29
29
  type: "Gas",
30
30
  rfc: null,
31
- origin: "Harmful smog component formed when pollution reacts with sunlight, especially problematic on hot, sunny days.",
31
+ origin: "Harmful smog component formed when pollution reacts with sunlight, especially problematic on hot, sunny days. Smell described as fresh or clean.",
32
32
  impacts: "Can irritate lungs, intensify asthma and chronic bronchitis, and increase heart problems and mortality rates.",
33
33
  },
34
34
  PM2_5: {
@@ -58,8 +58,8 @@ export const PARAMETERS = {
58
58
  unit: "ppb",
59
59
  type: "Gas",
60
60
  rfc: null,
61
- origin: null,
62
- impacts: null,
61
+ origin: "Comes mostly from burning fossil fuels at power plants and factories. Smaller amounts come from metal processing and volcanoes. Smells sharp or pungent.",
62
+ impacts: "Can make it hard to breathe and irritate the lungs, especially for people with asthma. Also helps form fine particle pollution and acid rain.",
63
63
  },
64
64
  BC: {
65
65
  id: "BC",
@@ -68,8 +68,8 @@ export const PARAMETERS = {
68
68
  unit: "ng/m³",
69
69
  type: "Particulate Matter",
70
70
  rfc: null,
71
- origin: null,
72
- impacts: null,
71
+ origin: "Black, sooty particles from burning fuel. Comes mostly from diesel and gas engines, coal power plants, and cookstoves that burn wood or coal. A major part of fine particle matter (PM2.5) in the air.",
72
+ impacts: "Can cause respiratory and cardiovascular disease, including asthma, heart attacks, and lung cancer.",
73
73
  },
74
74
  BRC: {
75
75
  id: "BRC",
@@ -78,8 +78,8 @@ export const PARAMETERS = {
78
78
  unit: "ng/m³",
79
79
  type: "Particulate Matter",
80
80
  rfc: null,
81
- origin: null,
82
- impacts: null,
81
+ origin: "Organic particles released by burning wood and plants, especially in wildfire smoke and wood fires at home.",
82
+ impacts: "Can cause respiratory and cardiovascular illnesses, exacerbate asthma, and increase mortality risk with prolonged exposure.",
83
83
  },
84
84
  CO2: {
85
85
  id: "CO2",
@@ -88,28 +88,28 @@ export const PARAMETERS = {
88
88
  unit: "ppm",
89
89
  type: "Gas",
90
90
  rfc: null,
91
- origin: null,
92
- impacts: null,
91
+ origin: "Comes mostly from burning fossil fuels, plus burning waste and plant material, and industrial processes like making cement. Has no color or smell.",
92
+ impacts: "Can cause drowsiness and reduced concentration, worsen headaches and fatigue, and increase breathing and heart-related health risks.",
93
93
  },
94
94
  H2S: {
95
95
  id: "H2S",
96
96
  label: "H₂S",
97
- name: "Hydrogen Sulfide (H₂S)",
97
+ name: "Hydrogen sulfide (H₂S)",
98
98
  unit: "ppm",
99
- type: "Volatile Organic Compound",
99
+ type: "Gas",
100
100
  rfc: null,
101
- origin: null,
102
- impacts: null,
101
+ origin: "Found naturally in crude oil, natural gas, and volcanic gas. Also released by oil refineries, wastewater plants, farms with livestock, and landfills. Smells of rotten-eggs.",
102
+ impacts: "Low levels over a long time can irritate eyes and airways and cause headaches or nausea. Very high levels can cause someone to pass out or die.",
103
103
  },
104
104
  CH4: {
105
105
  id: "CH4",
106
106
  label: "CH₄",
107
107
  name: "Methane (CH₄)",
108
108
  unit: "ppm",
109
- type: "Volatile Organic Compound",
109
+ type: "Gas",
110
110
  rfc: null,
111
- origin: null,
112
- impacts: null,
111
+ origin: "Comes from landfills, oil and gas drilling, livestock and farming, coal mines, and wastewater treatment. Has no natural smell, sometimes it is added to detect leaks.",
112
+ impacts: "Not harmful to breathe at normal outdoor levels. In a closed space, high levels can push out oxygen and cause dizziness or passing out. It also heats the planet, since it traps heat in the air like other gases that cause climate change.",
113
113
  },
114
114
  NO: {
115
115
  id: "NO",
@@ -118,8 +118,18 @@ export const PARAMETERS = {
118
118
  unit: "ppb",
119
119
  type: "Gas",
120
120
  rfc: null,
121
- origin: null,
122
- impacts: null,
121
+ origin: "Made when fuel burns in car engines, power plants, and factories. Turns into NO2 once it's in the air. Smells sharp and sweet.",
122
+ impacts: "Can irritate the eyes, nose, throat, and lungs at low levels, and cause serious airway damage, fluid buildup in the lungs, and death at high levels.",
123
+ },
124
+ RN: {
125
+ id: "RN",
126
+ label: "Radon",
127
+ name: "Radon (Rn)",
128
+ unit: "pCi/L",
129
+ type: "Gas",
130
+ rfc: 4, // doc states ">= 4pCi/L"; rfc is numeric, so the threshold value only
131
+ origin: "A radioactive gas that comes naturally from uranium breaking down in rock, soil, and water. It can leak into buildings through cracks in the foundation. Has no color or smell.",
132
+ impacts: "The second leading cause of lung cancer in the US, after smoking. Linked to about 21,000 lung cancer deaths a year.",
123
133
  },
124
134
  PM1: {
125
135
  id: "PM1",
@@ -128,8 +138,8 @@ export const PARAMETERS = {
128
138
  unit: "µg/m³",
129
139
  type: "Particulate Matter",
130
140
  rfc: null,
131
- origin: null,
132
- impacts: null,
141
+ origin: "Very fine particles that can penetrate deep into lungs and bloodstream, originating from vehicle emissions, industrial processes, and wildfires.",
142
+ impacts: "Can cause respiratory and cardiovascular illnesses, exacerbate asthma, and increase mortality risk with prolonged exposure.",
133
143
  },
134
144
  UFP: {
135
145
  id: "UFP",
@@ -138,18 +148,18 @@ export const PARAMETERS = {
138
148
  unit: "pt/cm³",
139
149
  type: "Particulate Matter",
140
150
  rfc: null,
141
- origin: null,
142
- impacts: null,
151
+ origin: "Very tiny particles from burning fuel, especially from cars, trucks, factories, power plants, and home heating.",
152
+ impacts: "Can raise the risk of lung and heart disease.",
143
153
  },
144
154
  VOC: {
145
155
  id: "VOC",
146
156
  label: "VOC",
147
- name: "Volatile Organic Compounds (VOC)",
157
+ name: "Volatile organic compounds (VOC)",
148
158
  unit: "ppm",
149
159
  type: "Volatile Organic Compound",
150
160
  rfc: null,
151
- origin: null,
152
- impacts: null,
161
+ origin: "Released when making or using everyday products like paint, cleaning supplies, solvents, and fuel.",
162
+ impacts: "Can harm health when breathed in indoors. Outdoors, reacts with sunlight to form smog. Some VOCs can cause cancer.",
153
163
  },
154
164
  // ---- T/RH/DP and their _AMB pairs (JA-3058). Does not apply to WD/WS below. ----
155
165
  // Bare ids (T/RH/DP) are enclosure-internal, for corrections and fog detection only. The _AMB
@@ -0,0 +1,14 @@
1
+ // Terms-of-service agreement vocabulary.
2
+ //
3
+ // Users.termsVersion stores the policy-document version a public user agreed
4
+ // to at registration (e.g. "2026.1" — the published doc's tab name). The
5
+ // VALUE is owned by the frontend signup flow; nothing in the
6
+ // backend hardcodes a current version, so there is deliberately no
7
+ // TERMS_CURRENT_VERSION constant here.
8
+ //
9
+ // The cap below is the one number shared by the two enforcement points in
10
+ // different repos: the Users schema (`maxlength`) and JustAirUserService's
11
+ // registration route validator. Import it from either barrel rather than
12
+ // inlining the literal — if the two drift, the route accepts what the schema
13
+ // rejects and a signup 500s instead of 400ing.
14
+ export const TERMS_VERSION_MAX_LENGTH = 32;
@@ -8,6 +8,7 @@ import { PARAMETERS, HEAVY_METALS } from '../pollutants.js';
8
8
  import { ALERT_LEVEL_WEIGHTS, getAlertWeight } from '../alertLevels.js';
9
9
  import { DEFAULT_TIMEZONE } from '../timezones.js';
10
10
  import { DATA_DOWNLOAD_TYPES } from '../dataDownloads.js';
11
+ import { TERMS_VERSION_MAX_LENGTH } from '../terms.js';
11
12
 
12
13
  test('barrel re-exports the pollutants constants unchanged (identity, not copies)', () => {
13
14
  expect(barrel.PARAMETERS).toBe(PARAMETERS);
@@ -56,6 +57,11 @@ test('barrel exports DATA_DOWNLOAD_TYPES (browser-safe, no Mongoose)', () => {
56
57
  expect(barrel.DATA_DOWNLOAD_TYPES).toBe(DATA_DOWNLOAD_TYPES);
57
58
  });
58
59
 
60
+ test('barrel exports TERMS_VERSION_MAX_LENGTH (browser-safe, no Mongoose)', () => {
61
+ expect(barrel.TERMS_VERSION_MAX_LENGTH).toBe(TERMS_VERSION_MAX_LENGTH);
62
+ expect(TERMS_VERSION_MAX_LENGTH).toBe(32);
63
+ });
64
+
59
65
  // models/dataDownloads.js destructures this list positionally to build the two
60
66
  // discriminators, so a reorder would silently swap the persisted dataType
61
67
  // values on every future download. Pin contents AND order.
@@ -67,8 +67,26 @@ function sharedAssertions(constants) {
67
67
  });
68
68
  }
69
69
 
70
+ // JA-3460: the public UI now renders concentration data for non-AQI pollutants, so every
71
+ // pollutant it can show needs Origin and Impacts copy. Weather parameters are exempt: they are
72
+ // not pollutants, and the Centralized Constants doc leaves both columns blank for them.
73
+ function hasOriginAndImpacts(constants) {
74
+ const pollutants = Object.entries(constants).filter(([, p]) => p.type !== "Weather");
75
+
76
+ test.each(pollutants)("%s: origin is populated", (key, pollutant) => {
77
+ expect(typeof pollutant.origin).toBe("string");
78
+ expect(pollutant.origin.trim().length).toBeGreaterThan(0);
79
+ });
80
+
81
+ test.each(pollutants)("%s: impacts is populated", (key, pollutant) => {
82
+ expect(typeof pollutant.impacts).toBe("string");
83
+ expect(pollutant.impacts.trim().length).toBeGreaterThan(0);
84
+ });
85
+ }
86
+
70
87
  describe("PARAMETERS", () => {
71
88
  sharedAssertions(PARAMETERS);
89
+ hasOriginAndImpacts(PARAMETERS);
72
90
 
73
91
  // JA-3060: Processing-Engine hardcodes these ids, so lock the ambient/internal pairing.
74
92
  const ambientKeys = Object.keys(PARAMETERS).filter((key) => key.endsWith("_AMB"));
@@ -87,6 +105,7 @@ describe("PARAMETERS", () => {
87
105
 
88
106
  describe("HEAVY_METALS", () => {
89
107
  sharedAssertions(HEAVY_METALS);
108
+ hasOriginAndImpacts(HEAVY_METALS);
90
109
 
91
110
  test("PB.rfc is exactly 0, not null", () => {
92
111
  expect(HEAVY_METALS.PB.rfc).toBe(0);
package/src/index.js CHANGED
@@ -75,6 +75,7 @@ import {
75
75
  } from "./constants/alertLevels.js";
76
76
  import { DEFAULT_TIMEZONE } from "./constants/timezones.js";
77
77
  import { DATA_DOWNLOAD_TYPES } from "./constants/dataDownloads.js";
78
+ import { TERMS_VERSION_MAX_LENGTH } from "./constants/terms.js";
78
79
  import {
79
80
  DEVICE_TYPES,
80
81
  DEVICE_TYPE_VALUES,
@@ -280,6 +281,9 @@ export {
280
281
  // AQI alert-level hierarchy (single source of truth)
281
282
  ALERT_LEVEL_WEIGHTS,
282
283
  getAlertWeight,
284
+ // Terms agreement (JA-3396) — cap shared by the Users schema and
285
+ // JustAirUserService's registration route validator
286
+ TERMS_VERSION_MAX_LENGTH,
283
287
  // V2 device-type vocabulary (single source of truth) — JA-3110 / JA-3335
284
288
  DEVICE_TYPES,
285
289
  DEVICE_TYPE_VALUES,
@@ -6,6 +6,7 @@ import {
6
6
  QUIET_HOURS_ERRORS,
7
7
  } from "../utils/quietHours.js";
8
8
  import { DEVICE_TYPE_VALUES } from "../constants/deviceTypes.js";
9
+ import { TERMS_VERSION_MAX_LENGTH } from "../constants/terms.js";
9
10
 
10
11
  const COOKIE_CONSENT_CATEGORIES = ["analytics_performance"];
11
12
  const COOKIE_CONSENT_VALUES = ["accept", "reject"];
@@ -193,6 +194,35 @@ const usersSchema = mongoose.Schema(
193
194
  message: "timezone must be a valid IANA timezone identifier",
194
195
  },
195
196
  },
197
+ // Terms agreement: the policy-document version the user agreed to at
198
+ // registration (e.g. "2026.1"). The client supplies the value (the signup
199
+ // FE owns it); the backend never hardcodes a
200
+ // current version. Written once at account creation — `immutable` means
201
+ // save() mutations are ignored and strict-mode query updates are
202
+ // stripped, silently by design; treat any need to change it as a product
203
+ // decision, not a bug. Absent = registered before terms tracking existed
204
+ // (or a legacy client that sent only { phone }); absence is legal
205
+ // forever. Deliberately NO acceptance boolean (an account cannot be
206
+ // created without pressing "I Agree", so it would be true on every
207
+ // record) and NO separate acceptance timestamp — createdAt IS the
208
+ // agreement date (grooming decision, 2026-09-03).
209
+ //
210
+ // Blank guard: immutability makes a stored value permanent, so a blank,
211
+ // padded, or null value ("" / " 2026.1" / null) would be an
212
+ // uncorrectable third state — looks present, records nothing. The custom
213
+ // validator (not minlength, which skips null) rejects all three here as
214
+ // the backstop; the registration route validator (JA-3397) is the
215
+ // primary 400 path, both sharing TERMS_VERSION_MAX_LENGTH.
216
+ termsVersion: {
217
+ type: String,
218
+ trim: true,
219
+ immutable: true,
220
+ maxlength: TERMS_VERSION_MAX_LENGTH,
221
+ validate: {
222
+ validator: (v) => typeof v === "string" && v.length > 0,
223
+ message: "termsVersion must be a non-empty string",
224
+ },
225
+ },
196
226
  },
197
227
  {
198
228
  timestamps: true,