@webority-technologies/mobile-core 0.0.4 → 0.0.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.
@@ -0,0 +1,292 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.startOfDay = exports.isSameDay = exports.isDateBefore = exports.isDateAfter = exports.formatTimeInZone = exports.formatTime12h = exports.formatDatePattern = exports.formatDateISO = exports.formatDateDDMMMYYYY = exports.diffMs = exports.addDays = void 0;
7
+ const toDate = value => {
8
+ if (value instanceof Date) {
9
+ return Number.isNaN(value.getTime()) ? null : value;
10
+ }
11
+ const d = new Date(value);
12
+ return Number.isNaN(d.getTime()) ? null : d;
13
+ };
14
+ const PAD = n => n < 10 ? `0${n}` : `${n}`;
15
+
16
+ /**
17
+ * Whether two values fall on the same calendar day in the device's local
18
+ * timezone. Invalid input on either side returns false rather than throwing.
19
+ */
20
+ const isSameDay = (a, b) => {
21
+ const da = toDate(a);
22
+ const db = toDate(b);
23
+ if (!da || !db) {
24
+ return false;
25
+ }
26
+ return da.getFullYear() === db.getFullYear() && da.getMonth() === db.getMonth() && da.getDate() === db.getDate();
27
+ };
28
+
29
+ /** `a` is strictly before `b`. Invalid input on either side returns false. */
30
+ exports.isSameDay = isSameDay;
31
+ const isDateBefore = (a, b) => {
32
+ const da = toDate(a);
33
+ const db = toDate(b);
34
+ return da !== null && db !== null && da.getTime() < db.getTime();
35
+ };
36
+
37
+ /** `a` is strictly after `b`. Invalid input on either side returns false. */
38
+ exports.isDateBefore = isDateBefore;
39
+ const isDateAfter = (a, b) => {
40
+ const da = toDate(a);
41
+ const db = toDate(b);
42
+ return da !== null && db !== null && da.getTime() > db.getTime();
43
+ };
44
+
45
+ /**
46
+ * `a - b` in milliseconds, matching dayjs's `a.diff(b)` sign convention
47
+ * (positive when `a` is later than `b`). Returns `NaN` if either side fails
48
+ * to parse, the same way subtracting two invalid dayjs objects would.
49
+ */
50
+ exports.isDateAfter = isDateAfter;
51
+ const diffMs = (a, b) => {
52
+ const da = toDate(a);
53
+ const db = toDate(b);
54
+ if (!da || !db) {
55
+ return Number.NaN;
56
+ }
57
+ return da.getTime() - db.getTime();
58
+ };
59
+
60
+ /**
61
+ * Midnight of the given date, in the device's local timezone. Returns null
62
+ * for unparseable input.
63
+ */
64
+ exports.diffMs = diffMs;
65
+ const startOfDay = value => {
66
+ const d = toDate(value);
67
+ if (!d) {
68
+ return null;
69
+ }
70
+ const start = new Date(d);
71
+ start.setHours(0, 0, 0, 0);
72
+ return start;
73
+ };
74
+
75
+ /**
76
+ * Adds (or, with a negative count, subtracts) whole calendar days in the
77
+ * device's local timezone. Returns null for unparseable input.
78
+ */
79
+ exports.startOfDay = startOfDay;
80
+ const addDays = (value, count) => {
81
+ const d = toDate(value);
82
+ if (!d) {
83
+ return null;
84
+ }
85
+ const next = new Date(d);
86
+ next.setDate(next.getDate() + count);
87
+ return next;
88
+ };
89
+ exports.addDays = addDays;
90
+ const WEEKDAY_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
91
+ const WEEKDAY_LONG = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
92
+ const MONTH_LONG = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
93
+
94
+ // Longest-first so 'YYYY' is tried before 'YY', 'DD' before 'D', etc. — a
95
+ // greedy single pass over the pattern, not a regex, so a literal character
96
+ // (a comma, a colon, a digit) can never accidentally match a token.
97
+ const TOKENS = ['YYYY', 'YY', 'MMMM', 'MMM', 'MM', 'M', 'DD', 'D', 'dddd', 'ddd', 'HH', 'H', 'hh', 'h', 'mm', 'm', 'ss', 's', 'A', 'a'];
98
+ const tokenizePattern = pattern => {
99
+ const result = [];
100
+ let i = 0;
101
+ outer: while (i < pattern.length) {
102
+ for (const token of TOKENS) {
103
+ if (pattern.startsWith(token, i)) {
104
+ result.push(token);
105
+ i += token.length;
106
+ continue outer;
107
+ }
108
+ }
109
+ result.push(pattern[i]);
110
+ i += 1;
111
+ }
112
+ return result;
113
+ };
114
+
115
+ /**
116
+ * Small dayjs/moment-style token formatter — YYYY/YY, MMMM/MMM/MM/M,
117
+ * DD/D, dddd/ddd, HH/H (24h), hh/h (12h), mm/m, ss/s, A/a (upper/lower AM/PM).
118
+ * Everything else in the pattern (spaces, commas, colons) passes through
119
+ * literally. Hand-rolled rather than a dependency because this fleet's date
120
+ * formatting needs are a small, closed token set, not general i18n — for
121
+ * genuinely locale-aware output, `formatDate`/`formatTime` (Intl-backed, in
122
+ * `./date`) are the ones to reach for; this is for matching an exact
123
+ * existing pattern (a legacy display string, an API's expected shape).
124
+ *
125
+ * `utc: true` reads every field from the UTC calendar/clock instead of the
126
+ * device's local one — see `formatDateDDMMMYYYY`'s note on why this isn't
127
+ * cosmetic.
128
+ */
129
+ const formatDatePattern = (value, pattern, options) => {
130
+ const d = toDate(value);
131
+ if (!d) {
132
+ return '';
133
+ }
134
+ const utc = options?.utc ?? false;
135
+ const year = utc ? d.getUTCFullYear() : d.getFullYear();
136
+ const month = utc ? d.getUTCMonth() : d.getMonth();
137
+ const day = utc ? d.getUTCDate() : d.getDate();
138
+ const weekday = utc ? d.getUTCDay() : d.getDay();
139
+ const hours24 = utc ? d.getUTCHours() : d.getHours();
140
+ const minutes = utc ? d.getUTCMinutes() : d.getMinutes();
141
+ const seconds = utc ? d.getUTCSeconds() : d.getSeconds();
142
+ const {
143
+ hour12,
144
+ period
145
+ } = to12Hour(hours24);
146
+ return tokenizePattern(pattern).map(t => {
147
+ switch (t) {
148
+ case 'YYYY':
149
+ return String(year);
150
+ case 'YY':
151
+ return String(year).slice(-2);
152
+ case 'MMMM':
153
+ return MONTH_LONG[month];
154
+ case 'MMM':
155
+ return MONTH_SHORT[month];
156
+ case 'MM':
157
+ return PAD(month + 1);
158
+ case 'M':
159
+ return String(month + 1);
160
+ case 'DD':
161
+ return PAD(day);
162
+ case 'D':
163
+ return String(day);
164
+ case 'dddd':
165
+ return WEEKDAY_LONG[weekday];
166
+ case 'ddd':
167
+ return WEEKDAY_SHORT[weekday];
168
+ case 'HH':
169
+ return PAD(hours24);
170
+ case 'H':
171
+ return String(hours24);
172
+ case 'hh':
173
+ return PAD(hour12);
174
+ case 'h':
175
+ return String(hour12);
176
+ case 'mm':
177
+ return PAD(minutes);
178
+ case 'm':
179
+ return String(minutes);
180
+ case 'ss':
181
+ return PAD(seconds);
182
+ case 's':
183
+ return String(seconds);
184
+ case 'A':
185
+ return period;
186
+ case 'a':
187
+ return period.toLowerCase();
188
+ default:
189
+ return t;
190
+ }
191
+ }).join('');
192
+ };
193
+ exports.formatDatePattern = formatDatePattern;
194
+ const MONTH_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
195
+
196
+ /**
197
+ * `DD MMM, YYYY` — zero-padded day, short month, comma, year (e.g.
198
+ * `05 Sep, 2025`). A specific legacy pattern some apps already display,
199
+ * not the fleet's own product convention: `formatDate` (the Intl `style`-based
200
+ * formatter in `./date`) is the one to reach for in new code. This exists so
201
+ * an app already showing this exact pattern can move off a date library
202
+ * without a visible change.
203
+ *
204
+ * `utc: true` reads the calendar day from the UTC fields instead of the
205
+ * device's local ones — matching `dayjs.utc(x).format(...)`, not
206
+ * `dayjs(x).format(...)`. The two disagree whenever local time has crossed a
207
+ * day boundary UTC hasn't yet (or vice versa), so this is not a cosmetic
208
+ * option; pass it only to match a call site that was genuinely UTC-explicit.
209
+ */
210
+ const formatDateDDMMMYYYY = (value, options) => {
211
+ const d = toDate(value);
212
+ if (!d) {
213
+ return '';
214
+ }
215
+ if (options?.utc) {
216
+ return `${PAD(d.getUTCDate())} ${MONTH_SHORT[d.getUTCMonth()]}, ${d.getUTCFullYear()}`;
217
+ }
218
+ return `${PAD(d.getDate())} ${MONTH_SHORT[d.getMonth()]}, ${d.getFullYear()}`;
219
+ };
220
+
221
+ /** `YYYY-MM-DD`, in the device's local timezone. */
222
+ exports.formatDateDDMMMYYYY = formatDateDDMMMYYYY;
223
+ const formatDateISO = value => {
224
+ const d = toDate(value);
225
+ if (!d) {
226
+ return '';
227
+ }
228
+ return `${d.getFullYear()}-${PAD(d.getMonth() + 1)}-${PAD(d.getDate())}`;
229
+ };
230
+ exports.formatDateISO = formatDateISO;
231
+ const to12Hour = hours => {
232
+ const period = hours >= 12 ? 'PM' : 'AM';
233
+ const hour12 = hours % 12 || 12;
234
+ return {
235
+ hour12,
236
+ period
237
+ };
238
+ };
239
+
240
+ /**
241
+ * `hh:mm A` in the device's local timezone — zero-padded 12-hour clock,
242
+ * uppercase AM/PM (e.g. `09:30 PM`).
243
+ */
244
+ const formatTime12h = value => {
245
+ const d = toDate(value);
246
+ if (!d) {
247
+ return '';
248
+ }
249
+ const {
250
+ hour12,
251
+ period
252
+ } = to12Hour(d.getHours());
253
+ return `${PAD(hour12)}:${PAD(d.getMinutes())} ${period}`;
254
+ };
255
+ exports.formatTime12h = formatTime12h;
256
+ const hasIntl = () => typeof Intl !== 'undefined' && typeof Intl.DateTimeFormat === 'function';
257
+
258
+ /**
259
+ * `hh:mm a` (zero-padded 12-hour, lowercase am/pm) of the given instant as
260
+ * seen in `ianaTimeZone` (e.g. `Asia/Kolkata`) — not the device's own
261
+ * timezone. Reads the hour/minute/period through `Intl.DateTimeFormat`'s
262
+ * `formatToParts` rather than its assembled string, because the assembled
263
+ * string's spacing before AM/PM varies by JS engine (some insert a narrow
264
+ * no-break space, U+202F, instead of a plain space) and isn't reliably
265
+ * zero-padded either. Throws if `ianaTimeZone` isn't a timezone Intl
266
+ * recognises — the same failure mode as handing dayjs.tz() a bad zone name.
267
+ */
268
+ const formatTimeInZone = (value, ianaTimeZone) => {
269
+ const d = toDate(value);
270
+ if (!d) {
271
+ return '';
272
+ }
273
+ if (!hasIntl()) {
274
+ // No Intl.DateTimeFormat means no reliable IANA timezone database to
275
+ // convert against; the device-local fallback below is the least-wrong
276
+ // answer available, not a genuine timezone conversion.
277
+ return formatTime12h(d);
278
+ }
279
+ const parts = new Intl.DateTimeFormat('en-US', {
280
+ timeZone: ianaTimeZone,
281
+ hour: 'numeric',
282
+ minute: '2-digit',
283
+ hour12: true
284
+ }).formatToParts(d);
285
+ const get = type => parts.find(p => p.type === type)?.value ?? '';
286
+ const hour = Number.parseInt(get('hour'), 10);
287
+ const minute = get('minute');
288
+ const period = get('dayPeriod').toLowerCase();
289
+ return `${PAD(hour)}:${minute} ${period}`;
290
+ };
291
+ exports.formatTimeInZone = formatTimeInZone;
292
+ //# sourceMappingURL=dateMath.js.map
@@ -3,12 +3,24 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ Object.defineProperty(exports, "addDays", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _dateMath.addDays;
10
+ }
11
+ });
6
12
  Object.defineProperty(exports, "detectPhoneCountry", {
7
13
  enumerable: true,
8
14
  get: function () {
9
15
  return _phone.detectPhoneCountry;
10
16
  }
11
17
  });
18
+ Object.defineProperty(exports, "diffMs", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _dateMath.diffMs;
22
+ }
23
+ });
12
24
  Object.defineProperty(exports, "formatCompactNumber", {
13
25
  enumerable: true,
14
26
  get: function () {
@@ -27,6 +39,24 @@ Object.defineProperty(exports, "formatDate", {
27
39
  return _date.formatDate;
28
40
  }
29
41
  });
42
+ Object.defineProperty(exports, "formatDateDDMMMYYYY", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _dateMath.formatDateDDMMMYYYY;
46
+ }
47
+ });
48
+ Object.defineProperty(exports, "formatDateISO", {
49
+ enumerable: true,
50
+ get: function () {
51
+ return _dateMath.formatDateISO;
52
+ }
53
+ });
54
+ Object.defineProperty(exports, "formatDatePattern", {
55
+ enumerable: true,
56
+ get: function () {
57
+ return _dateMath.formatDatePattern;
58
+ }
59
+ });
30
60
  Object.defineProperty(exports, "formatDateTime", {
31
61
  enumerable: true,
32
62
  get: function () {
@@ -63,12 +93,42 @@ Object.defineProperty(exports, "formatTime", {
63
93
  return _date.formatTime;
64
94
  }
65
95
  });
96
+ Object.defineProperty(exports, "formatTime12h", {
97
+ enumerable: true,
98
+ get: function () {
99
+ return _dateMath.formatTime12h;
100
+ }
101
+ });
102
+ Object.defineProperty(exports, "formatTimeInZone", {
103
+ enumerable: true,
104
+ get: function () {
105
+ return _dateMath.formatTimeInZone;
106
+ }
107
+ });
66
108
  Object.defineProperty(exports, "getInitials", {
67
109
  enumerable: true,
68
110
  get: function () {
69
111
  return _initials.getInitials;
70
112
  }
71
113
  });
114
+ Object.defineProperty(exports, "isDateAfter", {
115
+ enumerable: true,
116
+ get: function () {
117
+ return _dateMath.isDateAfter;
118
+ }
119
+ });
120
+ Object.defineProperty(exports, "isDateBefore", {
121
+ enumerable: true,
122
+ get: function () {
123
+ return _dateMath.isDateBefore;
124
+ }
125
+ });
126
+ Object.defineProperty(exports, "isSameDay", {
127
+ enumerable: true,
128
+ get: function () {
129
+ return _dateMath.isSameDay;
130
+ }
131
+ });
72
132
  Object.defineProperty(exports, "isValidPhoneNumber", {
73
133
  enumerable: true,
74
134
  get: function () {
@@ -93,8 +153,15 @@ Object.defineProperty(exports, "parsePhoneNumberFromString", {
93
153
  return _phone.parsePhoneNumberFromString;
94
154
  }
95
155
  });
156
+ Object.defineProperty(exports, "startOfDay", {
157
+ enumerable: true,
158
+ get: function () {
159
+ return _dateMath.startOfDay;
160
+ }
161
+ });
96
162
  var _currency = require("./currency.js");
97
163
  var _date = require("./date.js");
164
+ var _dateMath = require("./dateMath.js");
98
165
  var _initials = require("./initials.js");
99
166
  var _number = require("./number.js");
100
167
  var _phone = require("./phone.js");
@@ -51,6 +51,12 @@ Object.defineProperty(exports, "VersionCheck", {
51
51
  return _index8.VersionCheck;
52
52
  }
53
53
  });
54
+ Object.defineProperty(exports, "addDays", {
55
+ enumerable: true,
56
+ get: function () {
57
+ return _index3.addDays;
58
+ }
59
+ });
54
60
  Object.defineProperty(exports, "addNetworkStatusListener", {
55
61
  enumerable: true,
56
62
  get: function () {
@@ -87,6 +93,12 @@ Object.defineProperty(exports, "detectPhoneCountry", {
87
93
  return _index3.detectPhoneCountry;
88
94
  }
89
95
  });
96
+ Object.defineProperty(exports, "diffMs", {
97
+ enumerable: true,
98
+ get: function () {
99
+ return _index3.diffMs;
100
+ }
101
+ });
90
102
  Object.defineProperty(exports, "formatCompactNumber", {
91
103
  enumerable: true,
92
104
  get: function () {
@@ -105,6 +117,24 @@ Object.defineProperty(exports, "formatDate", {
105
117
  return _index3.formatDate;
106
118
  }
107
119
  });
120
+ Object.defineProperty(exports, "formatDateDDMMMYYYY", {
121
+ enumerable: true,
122
+ get: function () {
123
+ return _index3.formatDateDDMMMYYYY;
124
+ }
125
+ });
126
+ Object.defineProperty(exports, "formatDateISO", {
127
+ enumerable: true,
128
+ get: function () {
129
+ return _index3.formatDateISO;
130
+ }
131
+ });
132
+ Object.defineProperty(exports, "formatDatePattern", {
133
+ enumerable: true,
134
+ get: function () {
135
+ return _index3.formatDatePattern;
136
+ }
137
+ });
108
138
  Object.defineProperty(exports, "formatDateTime", {
109
139
  enumerable: true,
110
140
  get: function () {
@@ -141,6 +171,18 @@ Object.defineProperty(exports, "formatTime", {
141
171
  return _index3.formatTime;
142
172
  }
143
173
  });
174
+ Object.defineProperty(exports, "formatTime12h", {
175
+ enumerable: true,
176
+ get: function () {
177
+ return _index3.formatTime12h;
178
+ }
179
+ });
180
+ Object.defineProperty(exports, "formatTimeInZone", {
181
+ enumerable: true,
182
+ get: function () {
183
+ return _index3.formatTimeInZone;
184
+ }
185
+ });
144
186
  Object.defineProperty(exports, "getConfig", {
145
187
  enumerable: true,
146
188
  get: function () {
@@ -225,6 +267,18 @@ Object.defineProperty(exports, "isBetween", {
225
267
  return _index7.isBetween;
226
268
  }
227
269
  });
270
+ Object.defineProperty(exports, "isDateAfter", {
271
+ enumerable: true,
272
+ get: function () {
273
+ return _index3.isDateAfter;
274
+ }
275
+ });
276
+ Object.defineProperty(exports, "isDateBefore", {
277
+ enumerable: true,
278
+ get: function () {
279
+ return _index3.isDateBefore;
280
+ }
281
+ });
228
282
  Object.defineProperty(exports, "isEmail", {
229
283
  enumerable: true,
230
284
  get: function () {
@@ -303,6 +357,12 @@ Object.defineProperty(exports, "isRetryableStatus", {
303
357
  return _retry.isRetryableStatus;
304
358
  }
305
359
  });
360
+ Object.defineProperty(exports, "isSameDay", {
361
+ enumerable: true,
362
+ get: function () {
363
+ return _index3.isSameDay;
364
+ }
365
+ });
306
366
  Object.defineProperty(exports, "isStorageAvailable", {
307
367
  enumerable: true,
308
368
  get: function () {
@@ -435,6 +495,12 @@ Object.defineProperty(exports, "setStorageImplementation", {
435
495
  return _index6.setStorageImplementation;
436
496
  }
437
497
  });
498
+ Object.defineProperty(exports, "startOfDay", {
499
+ enumerable: true,
500
+ get: function () {
501
+ return _index3.startOfDay;
502
+ }
503
+ });
438
504
  Object.defineProperty(exports, "storeToken", {
439
505
  enumerable: true,
440
506
  get: function () {
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+
3
+ const toDate = value => {
4
+ if (value instanceof Date) {
5
+ return Number.isNaN(value.getTime()) ? null : value;
6
+ }
7
+ const d = new Date(value);
8
+ return Number.isNaN(d.getTime()) ? null : d;
9
+ };
10
+ const PAD = n => n < 10 ? `0${n}` : `${n}`;
11
+
12
+ /**
13
+ * Whether two values fall on the same calendar day in the device's local
14
+ * timezone. Invalid input on either side returns false rather than throwing.
15
+ */
16
+ export const isSameDay = (a, b) => {
17
+ const da = toDate(a);
18
+ const db = toDate(b);
19
+ if (!da || !db) {
20
+ return false;
21
+ }
22
+ return da.getFullYear() === db.getFullYear() && da.getMonth() === db.getMonth() && da.getDate() === db.getDate();
23
+ };
24
+
25
+ /** `a` is strictly before `b`. Invalid input on either side returns false. */
26
+ export const isDateBefore = (a, b) => {
27
+ const da = toDate(a);
28
+ const db = toDate(b);
29
+ return da !== null && db !== null && da.getTime() < db.getTime();
30
+ };
31
+
32
+ /** `a` is strictly after `b`. Invalid input on either side returns false. */
33
+ export const isDateAfter = (a, b) => {
34
+ const da = toDate(a);
35
+ const db = toDate(b);
36
+ return da !== null && db !== null && da.getTime() > db.getTime();
37
+ };
38
+
39
+ /**
40
+ * `a - b` in milliseconds, matching dayjs's `a.diff(b)` sign convention
41
+ * (positive when `a` is later than `b`). Returns `NaN` if either side fails
42
+ * to parse, the same way subtracting two invalid dayjs objects would.
43
+ */
44
+ export const diffMs = (a, b) => {
45
+ const da = toDate(a);
46
+ const db = toDate(b);
47
+ if (!da || !db) {
48
+ return Number.NaN;
49
+ }
50
+ return da.getTime() - db.getTime();
51
+ };
52
+
53
+ /**
54
+ * Midnight of the given date, in the device's local timezone. Returns null
55
+ * for unparseable input.
56
+ */
57
+ export const startOfDay = value => {
58
+ const d = toDate(value);
59
+ if (!d) {
60
+ return null;
61
+ }
62
+ const start = new Date(d);
63
+ start.setHours(0, 0, 0, 0);
64
+ return start;
65
+ };
66
+
67
+ /**
68
+ * Adds (or, with a negative count, subtracts) whole calendar days in the
69
+ * device's local timezone. Returns null for unparseable input.
70
+ */
71
+ export const addDays = (value, count) => {
72
+ const d = toDate(value);
73
+ if (!d) {
74
+ return null;
75
+ }
76
+ const next = new Date(d);
77
+ next.setDate(next.getDate() + count);
78
+ return next;
79
+ };
80
+ const WEEKDAY_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
81
+ const WEEKDAY_LONG = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
82
+ const MONTH_LONG = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
83
+
84
+ // Longest-first so 'YYYY' is tried before 'YY', 'DD' before 'D', etc. — a
85
+ // greedy single pass over the pattern, not a regex, so a literal character
86
+ // (a comma, a colon, a digit) can never accidentally match a token.
87
+ const TOKENS = ['YYYY', 'YY', 'MMMM', 'MMM', 'MM', 'M', 'DD', 'D', 'dddd', 'ddd', 'HH', 'H', 'hh', 'h', 'mm', 'm', 'ss', 's', 'A', 'a'];
88
+ const tokenizePattern = pattern => {
89
+ const result = [];
90
+ let i = 0;
91
+ outer: while (i < pattern.length) {
92
+ for (const token of TOKENS) {
93
+ if (pattern.startsWith(token, i)) {
94
+ result.push(token);
95
+ i += token.length;
96
+ continue outer;
97
+ }
98
+ }
99
+ result.push(pattern[i]);
100
+ i += 1;
101
+ }
102
+ return result;
103
+ };
104
+
105
+ /**
106
+ * Small dayjs/moment-style token formatter — YYYY/YY, MMMM/MMM/MM/M,
107
+ * DD/D, dddd/ddd, HH/H (24h), hh/h (12h), mm/m, ss/s, A/a (upper/lower AM/PM).
108
+ * Everything else in the pattern (spaces, commas, colons) passes through
109
+ * literally. Hand-rolled rather than a dependency because this fleet's date
110
+ * formatting needs are a small, closed token set, not general i18n — for
111
+ * genuinely locale-aware output, `formatDate`/`formatTime` (Intl-backed, in
112
+ * `./date`) are the ones to reach for; this is for matching an exact
113
+ * existing pattern (a legacy display string, an API's expected shape).
114
+ *
115
+ * `utc: true` reads every field from the UTC calendar/clock instead of the
116
+ * device's local one — see `formatDateDDMMMYYYY`'s note on why this isn't
117
+ * cosmetic.
118
+ */
119
+ export const formatDatePattern = (value, pattern, options) => {
120
+ const d = toDate(value);
121
+ if (!d) {
122
+ return '';
123
+ }
124
+ const utc = options?.utc ?? false;
125
+ const year = utc ? d.getUTCFullYear() : d.getFullYear();
126
+ const month = utc ? d.getUTCMonth() : d.getMonth();
127
+ const day = utc ? d.getUTCDate() : d.getDate();
128
+ const weekday = utc ? d.getUTCDay() : d.getDay();
129
+ const hours24 = utc ? d.getUTCHours() : d.getHours();
130
+ const minutes = utc ? d.getUTCMinutes() : d.getMinutes();
131
+ const seconds = utc ? d.getUTCSeconds() : d.getSeconds();
132
+ const {
133
+ hour12,
134
+ period
135
+ } = to12Hour(hours24);
136
+ return tokenizePattern(pattern).map(t => {
137
+ switch (t) {
138
+ case 'YYYY':
139
+ return String(year);
140
+ case 'YY':
141
+ return String(year).slice(-2);
142
+ case 'MMMM':
143
+ return MONTH_LONG[month];
144
+ case 'MMM':
145
+ return MONTH_SHORT[month];
146
+ case 'MM':
147
+ return PAD(month + 1);
148
+ case 'M':
149
+ return String(month + 1);
150
+ case 'DD':
151
+ return PAD(day);
152
+ case 'D':
153
+ return String(day);
154
+ case 'dddd':
155
+ return WEEKDAY_LONG[weekday];
156
+ case 'ddd':
157
+ return WEEKDAY_SHORT[weekday];
158
+ case 'HH':
159
+ return PAD(hours24);
160
+ case 'H':
161
+ return String(hours24);
162
+ case 'hh':
163
+ return PAD(hour12);
164
+ case 'h':
165
+ return String(hour12);
166
+ case 'mm':
167
+ return PAD(minutes);
168
+ case 'm':
169
+ return String(minutes);
170
+ case 'ss':
171
+ return PAD(seconds);
172
+ case 's':
173
+ return String(seconds);
174
+ case 'A':
175
+ return period;
176
+ case 'a':
177
+ return period.toLowerCase();
178
+ default:
179
+ return t;
180
+ }
181
+ }).join('');
182
+ };
183
+ const MONTH_SHORT = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
184
+
185
+ /**
186
+ * `DD MMM, YYYY` — zero-padded day, short month, comma, year (e.g.
187
+ * `05 Sep, 2025`). A specific legacy pattern some apps already display,
188
+ * not the fleet's own product convention: `formatDate` (the Intl `style`-based
189
+ * formatter in `./date`) is the one to reach for in new code. This exists so
190
+ * an app already showing this exact pattern can move off a date library
191
+ * without a visible change.
192
+ *
193
+ * `utc: true` reads the calendar day from the UTC fields instead of the
194
+ * device's local ones — matching `dayjs.utc(x).format(...)`, not
195
+ * `dayjs(x).format(...)`. The two disagree whenever local time has crossed a
196
+ * day boundary UTC hasn't yet (or vice versa), so this is not a cosmetic
197
+ * option; pass it only to match a call site that was genuinely UTC-explicit.
198
+ */
199
+ export const formatDateDDMMMYYYY = (value, options) => {
200
+ const d = toDate(value);
201
+ if (!d) {
202
+ return '';
203
+ }
204
+ if (options?.utc) {
205
+ return `${PAD(d.getUTCDate())} ${MONTH_SHORT[d.getUTCMonth()]}, ${d.getUTCFullYear()}`;
206
+ }
207
+ return `${PAD(d.getDate())} ${MONTH_SHORT[d.getMonth()]}, ${d.getFullYear()}`;
208
+ };
209
+
210
+ /** `YYYY-MM-DD`, in the device's local timezone. */
211
+ export const formatDateISO = value => {
212
+ const d = toDate(value);
213
+ if (!d) {
214
+ return '';
215
+ }
216
+ return `${d.getFullYear()}-${PAD(d.getMonth() + 1)}-${PAD(d.getDate())}`;
217
+ };
218
+ const to12Hour = hours => {
219
+ const period = hours >= 12 ? 'PM' : 'AM';
220
+ const hour12 = hours % 12 || 12;
221
+ return {
222
+ hour12,
223
+ period
224
+ };
225
+ };
226
+
227
+ /**
228
+ * `hh:mm A` in the device's local timezone — zero-padded 12-hour clock,
229
+ * uppercase AM/PM (e.g. `09:30 PM`).
230
+ */
231
+ export const formatTime12h = value => {
232
+ const d = toDate(value);
233
+ if (!d) {
234
+ return '';
235
+ }
236
+ const {
237
+ hour12,
238
+ period
239
+ } = to12Hour(d.getHours());
240
+ return `${PAD(hour12)}:${PAD(d.getMinutes())} ${period}`;
241
+ };
242
+ const hasIntl = () => typeof Intl !== 'undefined' && typeof Intl.DateTimeFormat === 'function';
243
+
244
+ /**
245
+ * `hh:mm a` (zero-padded 12-hour, lowercase am/pm) of the given instant as
246
+ * seen in `ianaTimeZone` (e.g. `Asia/Kolkata`) — not the device's own
247
+ * timezone. Reads the hour/minute/period through `Intl.DateTimeFormat`'s
248
+ * `formatToParts` rather than its assembled string, because the assembled
249
+ * string's spacing before AM/PM varies by JS engine (some insert a narrow
250
+ * no-break space, U+202F, instead of a plain space) and isn't reliably
251
+ * zero-padded either. Throws if `ianaTimeZone` isn't a timezone Intl
252
+ * recognises — the same failure mode as handing dayjs.tz() a bad zone name.
253
+ */
254
+ export const formatTimeInZone = (value, ianaTimeZone) => {
255
+ const d = toDate(value);
256
+ if (!d) {
257
+ return '';
258
+ }
259
+ if (!hasIntl()) {
260
+ // No Intl.DateTimeFormat means no reliable IANA timezone database to
261
+ // convert against; the device-local fallback below is the least-wrong
262
+ // answer available, not a genuine timezone conversion.
263
+ return formatTime12h(d);
264
+ }
265
+ const parts = new Intl.DateTimeFormat('en-US', {
266
+ timeZone: ianaTimeZone,
267
+ hour: 'numeric',
268
+ minute: '2-digit',
269
+ hour12: true
270
+ }).formatToParts(d);
271
+ const get = type => parts.find(p => p.type === type)?.value ?? '';
272
+ const hour = Number.parseInt(get('hour'), 10);
273
+ const minute = get('minute');
274
+ const period = get('dayPeriod').toLowerCase();
275
+ return `${PAD(hour)}:${minute} ${period}`;
276
+ };
277
+ //# sourceMappingURL=dateMath.js.map
@@ -2,6 +2,7 @@
2
2
 
3
3
  export { formatCurrency } from "./currency.js";
4
4
  export { formatDate, formatDateTime, formatRelativeTime, formatTime } from "./date.js";
5
+ export { addDays, diffMs, formatDateDDMMMYYYY, formatDateISO, formatDatePattern, formatTime12h, formatTimeInZone, isDateAfter, isDateBefore, isSameDay, startOfDay } from "./dateMath.js";
5
6
  export { getInitials } from "./initials.js";
6
7
  export { formatCompactNumber, formatNumber, formatPercent } from "./number.js";
7
8
  export { detectPhoneCountry, formatPhone, isValidPhoneNumber, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString } from "./phone.js";
@@ -29,7 +29,7 @@ export { getConfig, getConfigValue, resetConfig, setConfig } from "./config/inde
29
29
  // Deep links
30
30
  export { DeepLink, parseDeepLink, useDeepLink } from "./deepLink/index.js";
31
31
  // Formatters
32
- export { detectPhoneCountry, formatCompactNumber, formatCurrency, formatDate, formatDateTime, formatNumber, formatPercent, formatPhone, formatRelativeTime, formatTime, getInitials, isValidPhoneNumber, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString } from "./formatters/index.js";
32
+ export { addDays, detectPhoneCountry, diffMs, formatCompactNumber, formatCurrency, formatDate, formatDateDDMMMYYYY, formatDateISO, formatDatePattern, formatDateTime, formatNumber, formatPercent, formatPhone, formatRelativeTime, formatTime, formatTime12h, formatTimeInZone, getInitials, isDateAfter, isDateBefore, isSameDay, isValidPhoneNumber, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString, startOfDay } from "./formatters/index.js";
33
33
  // Initialization
34
34
  export { initWebority } from "./initSDK.js";
35
35
  export { initUserAuth } from "./initUserAuth.js";
@@ -0,0 +1,79 @@
1
+ import type { DateInput } from './date';
2
+ /**
3
+ * Whether two values fall on the same calendar day in the device's local
4
+ * timezone. Invalid input on either side returns false rather than throwing.
5
+ */
6
+ export declare const isSameDay: (a: DateInput, b: DateInput) => boolean;
7
+ /** `a` is strictly before `b`. Invalid input on either side returns false. */
8
+ export declare const isDateBefore: (a: DateInput, b: DateInput) => boolean;
9
+ /** `a` is strictly after `b`. Invalid input on either side returns false. */
10
+ export declare const isDateAfter: (a: DateInput, b: DateInput) => boolean;
11
+ /**
12
+ * `a - b` in milliseconds, matching dayjs's `a.diff(b)` sign convention
13
+ * (positive when `a` is later than `b`). Returns `NaN` if either side fails
14
+ * to parse, the same way subtracting two invalid dayjs objects would.
15
+ */
16
+ export declare const diffMs: (a: DateInput, b: DateInput) => number;
17
+ /**
18
+ * Midnight of the given date, in the device's local timezone. Returns null
19
+ * for unparseable input.
20
+ */
21
+ export declare const startOfDay: (value: DateInput) => Date | null;
22
+ /**
23
+ * Adds (or, with a negative count, subtracts) whole calendar days in the
24
+ * device's local timezone. Returns null for unparseable input.
25
+ */
26
+ export declare const addDays: (value: DateInput, count: number) => Date | null;
27
+ /**
28
+ * Small dayjs/moment-style token formatter — YYYY/YY, MMMM/MMM/MM/M,
29
+ * DD/D, dddd/ddd, HH/H (24h), hh/h (12h), mm/m, ss/s, A/a (upper/lower AM/PM).
30
+ * Everything else in the pattern (spaces, commas, colons) passes through
31
+ * literally. Hand-rolled rather than a dependency because this fleet's date
32
+ * formatting needs are a small, closed token set, not general i18n — for
33
+ * genuinely locale-aware output, `formatDate`/`formatTime` (Intl-backed, in
34
+ * `./date`) are the ones to reach for; this is for matching an exact
35
+ * existing pattern (a legacy display string, an API's expected shape).
36
+ *
37
+ * `utc: true` reads every field from the UTC calendar/clock instead of the
38
+ * device's local one — see `formatDateDDMMMYYYY`'s note on why this isn't
39
+ * cosmetic.
40
+ */
41
+ export declare const formatDatePattern: (value: DateInput, pattern: string, options?: {
42
+ utc?: boolean;
43
+ }) => string;
44
+ /**
45
+ * `DD MMM, YYYY` — zero-padded day, short month, comma, year (e.g.
46
+ * `05 Sep, 2025`). A specific legacy pattern some apps already display,
47
+ * not the fleet's own product convention: `formatDate` (the Intl `style`-based
48
+ * formatter in `./date`) is the one to reach for in new code. This exists so
49
+ * an app already showing this exact pattern can move off a date library
50
+ * without a visible change.
51
+ *
52
+ * `utc: true` reads the calendar day from the UTC fields instead of the
53
+ * device's local ones — matching `dayjs.utc(x).format(...)`, not
54
+ * `dayjs(x).format(...)`. The two disagree whenever local time has crossed a
55
+ * day boundary UTC hasn't yet (or vice versa), so this is not a cosmetic
56
+ * option; pass it only to match a call site that was genuinely UTC-explicit.
57
+ */
58
+ export declare const formatDateDDMMMYYYY: (value: DateInput, options?: {
59
+ utc?: boolean;
60
+ }) => string;
61
+ /** `YYYY-MM-DD`, in the device's local timezone. */
62
+ export declare const formatDateISO: (value: DateInput) => string;
63
+ /**
64
+ * `hh:mm A` in the device's local timezone — zero-padded 12-hour clock,
65
+ * uppercase AM/PM (e.g. `09:30 PM`).
66
+ */
67
+ export declare const formatTime12h: (value: DateInput) => string;
68
+ /**
69
+ * `hh:mm a` (zero-padded 12-hour, lowercase am/pm) of the given instant as
70
+ * seen in `ianaTimeZone` (e.g. `Asia/Kolkata`) — not the device's own
71
+ * timezone. Reads the hour/minute/period through `Intl.DateTimeFormat`'s
72
+ * `formatToParts` rather than its assembled string, because the assembled
73
+ * string's spacing before AM/PM varies by JS engine (some insert a narrow
74
+ * no-break space, U+202F, instead of a plain space) and isn't reliably
75
+ * zero-padded either. Throws if `ianaTimeZone` isn't a timezone Intl
76
+ * recognises — the same failure mode as handing dayjs.tz() a bad zone name.
77
+ */
78
+ export declare const formatTimeInZone: (value: DateInput, ianaTimeZone: string) => string;
79
+ //# sourceMappingURL=dateMath.d.ts.map
@@ -2,6 +2,7 @@ export type { FormatCurrencyOptions } from './currency';
2
2
  export { formatCurrency } from './currency';
3
3
  export type { DateInput, DateStyle, TimeStyle } from './date';
4
4
  export { formatDate, formatDateTime, formatRelativeTime, formatTime } from './date';
5
+ export { addDays, diffMs, formatDateDDMMMYYYY, formatDateISO, formatDatePattern, formatTime12h, formatTimeInZone, isDateAfter, isDateBefore, isSameDay, startOfDay } from './dateMath';
5
6
  export { getInitials } from './initials';
6
7
  export { formatCompactNumber, formatNumber, formatPercent } from './number';
7
8
  export type { FormatPhoneOptions, PhoneCountry, PhoneNumber } from './phone';
@@ -29,7 +29,7 @@ export { getConfig, getConfigValue, resetConfig, setConfig } from './config';
29
29
  export type { DeepLinkConfig, DeepLinkRoute, ParsedLink } from './deepLink';
30
30
  export { DeepLink, parseDeepLink, useDeepLink } from './deepLink';
31
31
  export type { DateInput, DateStyle, FormatCurrencyOptions, FormatPhoneOptions, PhoneCountry, PhoneNumber, TimeStyle } from './formatters';
32
- export { detectPhoneCountry, formatCompactNumber, formatCurrency, formatDate, formatDateTime, formatNumber, formatPercent, formatPhone, formatRelativeTime, formatTime, getInitials, isValidPhoneNumber, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString } from './formatters';
32
+ export { addDays, detectPhoneCountry, diffMs, formatCompactNumber, formatCurrency, formatDate, formatDateDDMMMYYYY, formatDateISO, formatDatePattern, formatDateTime, formatNumber, formatPercent, formatPhone, formatRelativeTime, formatTime, formatTime12h, formatTimeInZone, getInitials, isDateAfter, isDateBefore, isSameDay, isValidPhoneNumber, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString, startOfDay } from './formatters';
33
33
  export type { InitConfig } from './initSDK';
34
34
  export { initWebority } from './initSDK';
35
35
  export type { AuthActions } from './initUserAuth';
@@ -0,0 +1,79 @@
1
+ import type { DateInput } from './date.js';
2
+ /**
3
+ * Whether two values fall on the same calendar day in the device's local
4
+ * timezone. Invalid input on either side returns false rather than throwing.
5
+ */
6
+ export declare const isSameDay: (a: DateInput, b: DateInput) => boolean;
7
+ /** `a` is strictly before `b`. Invalid input on either side returns false. */
8
+ export declare const isDateBefore: (a: DateInput, b: DateInput) => boolean;
9
+ /** `a` is strictly after `b`. Invalid input on either side returns false. */
10
+ export declare const isDateAfter: (a: DateInput, b: DateInput) => boolean;
11
+ /**
12
+ * `a - b` in milliseconds, matching dayjs's `a.diff(b)` sign convention
13
+ * (positive when `a` is later than `b`). Returns `NaN` if either side fails
14
+ * to parse, the same way subtracting two invalid dayjs objects would.
15
+ */
16
+ export declare const diffMs: (a: DateInput, b: DateInput) => number;
17
+ /**
18
+ * Midnight of the given date, in the device's local timezone. Returns null
19
+ * for unparseable input.
20
+ */
21
+ export declare const startOfDay: (value: DateInput) => Date | null;
22
+ /**
23
+ * Adds (or, with a negative count, subtracts) whole calendar days in the
24
+ * device's local timezone. Returns null for unparseable input.
25
+ */
26
+ export declare const addDays: (value: DateInput, count: number) => Date | null;
27
+ /**
28
+ * Small dayjs/moment-style token formatter — YYYY/YY, MMMM/MMM/MM/M,
29
+ * DD/D, dddd/ddd, HH/H (24h), hh/h (12h), mm/m, ss/s, A/a (upper/lower AM/PM).
30
+ * Everything else in the pattern (spaces, commas, colons) passes through
31
+ * literally. Hand-rolled rather than a dependency because this fleet's date
32
+ * formatting needs are a small, closed token set, not general i18n — for
33
+ * genuinely locale-aware output, `formatDate`/`formatTime` (Intl-backed, in
34
+ * `./date`) are the ones to reach for; this is for matching an exact
35
+ * existing pattern (a legacy display string, an API's expected shape).
36
+ *
37
+ * `utc: true` reads every field from the UTC calendar/clock instead of the
38
+ * device's local one — see `formatDateDDMMMYYYY`'s note on why this isn't
39
+ * cosmetic.
40
+ */
41
+ export declare const formatDatePattern: (value: DateInput, pattern: string, options?: {
42
+ utc?: boolean;
43
+ }) => string;
44
+ /**
45
+ * `DD MMM, YYYY` — zero-padded day, short month, comma, year (e.g.
46
+ * `05 Sep, 2025`). A specific legacy pattern some apps already display,
47
+ * not the fleet's own product convention: `formatDate` (the Intl `style`-based
48
+ * formatter in `./date`) is the one to reach for in new code. This exists so
49
+ * an app already showing this exact pattern can move off a date library
50
+ * without a visible change.
51
+ *
52
+ * `utc: true` reads the calendar day from the UTC fields instead of the
53
+ * device's local ones — matching `dayjs.utc(x).format(...)`, not
54
+ * `dayjs(x).format(...)`. The two disagree whenever local time has crossed a
55
+ * day boundary UTC hasn't yet (or vice versa), so this is not a cosmetic
56
+ * option; pass it only to match a call site that was genuinely UTC-explicit.
57
+ */
58
+ export declare const formatDateDDMMMYYYY: (value: DateInput, options?: {
59
+ utc?: boolean;
60
+ }) => string;
61
+ /** `YYYY-MM-DD`, in the device's local timezone. */
62
+ export declare const formatDateISO: (value: DateInput) => string;
63
+ /**
64
+ * `hh:mm A` in the device's local timezone — zero-padded 12-hour clock,
65
+ * uppercase AM/PM (e.g. `09:30 PM`).
66
+ */
67
+ export declare const formatTime12h: (value: DateInput) => string;
68
+ /**
69
+ * `hh:mm a` (zero-padded 12-hour, lowercase am/pm) of the given instant as
70
+ * seen in `ianaTimeZone` (e.g. `Asia/Kolkata`) — not the device's own
71
+ * timezone. Reads the hour/minute/period through `Intl.DateTimeFormat`'s
72
+ * `formatToParts` rather than its assembled string, because the assembled
73
+ * string's spacing before AM/PM varies by JS engine (some insert a narrow
74
+ * no-break space, U+202F, instead of a plain space) and isn't reliably
75
+ * zero-padded either. Throws if `ianaTimeZone` isn't a timezone Intl
76
+ * recognises — the same failure mode as handing dayjs.tz() a bad zone name.
77
+ */
78
+ export declare const formatTimeInZone: (value: DateInput, ianaTimeZone: string) => string;
79
+ //# sourceMappingURL=dateMath.d.ts.map
@@ -2,6 +2,7 @@ export type { FormatCurrencyOptions } from './currency.js';
2
2
  export { formatCurrency } from './currency.js';
3
3
  export type { DateInput, DateStyle, TimeStyle } from './date.js';
4
4
  export { formatDate, formatDateTime, formatRelativeTime, formatTime } from './date.js';
5
+ export { addDays, diffMs, formatDateDDMMMYYYY, formatDateISO, formatDatePattern, formatTime12h, formatTimeInZone, isDateAfter, isDateBefore, isSameDay, startOfDay } from './dateMath.js';
5
6
  export { getInitials } from './initials.js';
6
7
  export { formatCompactNumber, formatNumber, formatPercent } from './number.js';
7
8
  export type { FormatPhoneOptions, PhoneCountry, PhoneNumber } from './phone.js';
@@ -29,7 +29,7 @@ export { getConfig, getConfigValue, resetConfig, setConfig } from './config/inde
29
29
  export type { DeepLinkConfig, DeepLinkRoute, ParsedLink } from './deepLink/index.js';
30
30
  export { DeepLink, parseDeepLink, useDeepLink } from './deepLink/index.js';
31
31
  export type { DateInput, DateStyle, FormatCurrencyOptions, FormatPhoneOptions, PhoneCountry, PhoneNumber, TimeStyle } from './formatters/index.js';
32
- export { detectPhoneCountry, formatCompactNumber, formatCurrency, formatDate, formatDateTime, formatNumber, formatPercent, formatPhone, formatRelativeTime, formatTime, getInitials, isValidPhoneNumber, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString } from './formatters/index.js';
32
+ export { addDays, detectPhoneCountry, diffMs, formatCompactNumber, formatCurrency, formatDate, formatDateDDMMMYYYY, formatDateISO, formatDatePattern, formatDateTime, formatNumber, formatPercent, formatPhone, formatRelativeTime, formatTime, formatTime12h, formatTimeInZone, getInitials, isDateAfter, isDateBefore, isSameDay, isValidPhoneNumber, normalizePhone, parsePhoneNumber, parsePhoneNumberFromString, startOfDay } from './formatters/index.js';
33
33
  export type { InitConfig } from './initSDK.js';
34
34
  export { initWebority } from './initSDK.js';
35
35
  export type { AuthActions } from './initUserAuth.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webority-technologies/mobile-core",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Platform layer for Webority React Native apps: HTTP clients, auth/token storage, config, logging, formatters, validators, network status, permissions, storage and version checks. No UI.",
5
5
  "keywords": [
6
6
  "react-native",