@elasticias/utils 0.0.16 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -137,9 +137,90 @@ class UuidUtils {
137
137
  }
138
138
  }
139
139
 
140
+ class CsvUtils {
141
+ /** fr-MA Excel convention: ';' separator. Output has a header row, CRLF endings. */
142
+ static toCsv(rows, columns, separator = ';') {
143
+ // Excel/LibreOffice treat cells starting with =, +, -, @ or a tab as formulas.
144
+ // Operator-entered client/product names could carry these as their first
145
+ // character, so string values are neutralized (numbers are never affected —
146
+ // the guard only applies before coercion, when `raw` is actually a string).
147
+ const needsFormulaGuard = (value) => /^[=+\-@\t]/.test(value);
148
+ const escape = (raw) => {
149
+ if (raw === null || raw === undefined)
150
+ return '';
151
+ const isString = typeof raw === 'string';
152
+ let s = String(raw);
153
+ const guard = isString && needsFormulaGuard(s);
154
+ if (guard)
155
+ s = `'${s}`;
156
+ return guard || s.includes(separator) || s.includes('"') || s.includes('\n')
157
+ ? `"${s.replace(/"/g, '""')}"`
158
+ : s;
159
+ };
160
+ const header = columns.map((c) => escape(c.header)).join(separator);
161
+ const lines = rows.map((r) => columns.map((c) => escape(r[c.key])).join(separator));
162
+ return [header, ...lines].join('\r\n');
163
+ }
164
+ /** UTF-8 BOM so Excel opens French accents / Arabic correctly. */
165
+ static download(filename, csv) {
166
+ const blob = new Blob(['', csv], { type: 'text/csv;charset=utf-8;' });
167
+ const url = URL.createObjectURL(blob);
168
+ const a = document.createElement('a');
169
+ a.href = url;
170
+ a.download = filename;
171
+ a.click();
172
+ URL.revokeObjectURL(url);
173
+ }
174
+ }
175
+
176
+ // Ordered largest-first so the first matching threshold wins.
177
+ const UNITS = [
178
+ { threshold: 1_000_000_000, divisor: 1_000_000_000, suffix: 'Md' },
179
+ { threshold: 1_000_000, divisor: 1_000_000, suffix: 'M' },
180
+ { threshold: 1_000, divisor: 1_000, suffix: 'k' },
181
+ ];
182
+ class NumberUtils {
183
+ /**
184
+ * Compact human-readable number: 1000 → "1 k", 2_500_000 → "2,5 M" (fr decimal comma).
185
+ * maxDecimals applies after scaling; trailing zeros stripped. Below 1000 → localized plain number.
186
+ */
187
+ static compact(value, opts) {
188
+ if (!Number.isFinite(value))
189
+ return '';
190
+ const locale = opts?.locale ?? 'fr-MA';
191
+ const maxDecimals = opts?.maxDecimals ?? 2;
192
+ if (value === 0)
193
+ return '0';
194
+ const sign = value < 0 ? '-' : '';
195
+ const abs = Math.abs(value);
196
+ const format = (n, decimals) => n.toLocaleString(locale, {
197
+ minimumFractionDigits: 0,
198
+ maximumFractionDigits: decimals,
199
+ });
200
+ let unit = UNITS.find((u) => abs >= u.threshold);
201
+ if (!unit) {
202
+ return sign + format(abs, maxDecimals);
203
+ }
204
+ let scaled = abs / unit.divisor;
205
+ // Rounding at maxDecimals can push the scaled value up to (or past) the
206
+ // next unit's threshold (e.g. 999_999.9 @ 1 decimal -> 1000.0 k). Re-scale
207
+ // in that case so the boundary bumps to the next unit instead.
208
+ const rounded = Number(scaled.toFixed(maxDecimals));
209
+ if (rounded >= 1000) {
210
+ const nextUnitIndex = UNITS.indexOf(unit) - 1;
211
+ const nextUnit = nextUnitIndex >= 0 ? UNITS[nextUnitIndex] : undefined;
212
+ if (nextUnit) {
213
+ unit = nextUnit;
214
+ scaled = abs / unit.divisor;
215
+ }
216
+ }
217
+ return `${sign}${format(scaled, maxDecimals)}\u00A0${unit.suffix}`;
218
+ }
219
+ }
220
+
140
221
  /**
141
222
  * Generated bundle index. Do not edit.
142
223
  */
143
224
 
144
- export { AppUtils, StorageUtils, UuidUtils };
225
+ export { AppUtils, CsvUtils, NumberUtils, StorageUtils, UuidUtils };
145
226
  //# sourceMappingURL=elasticias-utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"elasticias-utils.mjs","sources":["../../../../libs/utils/src/lib/storage.utils.ts","../../../../libs/utils/src/lib/app.utils.ts","../../../../libs/utils/src/lib/uuid.utils.ts","../../../../libs/utils/src/elasticias-utils.ts"],"sourcesContent":["/**\n * Configurable prefix-based wrapper for localStorage and sessionStorage.\n * Default prefix is 'Elasticias_'. Override via `StorageUtils.configure()`.\n */\nexport class StorageUtils {\n private static _prefix = 'Elasticias_';\n\n static configure(prefix: string): void {\n StorageUtils._prefix = prefix;\n }\n\n static get prefix(): string {\n return StorageUtils._prefix;\n }\n\n private static getPrefixedKey(key: string): string {\n return `${StorageUtils._prefix}${key}`;\n }\n\n static setLocal(key: string, value: unknown): void {\n try {\n const localKey = this.getPrefixedKey(key);\n localStorage.setItem(localKey, JSON.stringify(value));\n } catch (error) {\n console.error('Error storing data in localStorage: ', key, error);\n }\n }\n\n static getLocal<T>(key: string, usePrefix = true): T | null {\n const localKey = usePrefix ? this.getPrefixedKey(key) : key;\n const item = localStorage.getItem(localKey);\n try {\n if (item) {\n return JSON.parse(item) as T;\n }\n } catch (error) {\n console.error('Error parsing JSON from localStorage: ', key, error);\n }\n return null;\n }\n\n static removeLocal(key: string): void {\n localStorage.removeItem(this.getPrefixedKey(key));\n }\n\n static clearLocal(): void {\n localStorage.clear();\n }\n\n static setSession(key: string, value: unknown): void {\n const sessionKey = this.getPrefixedKey(key);\n sessionStorage.setItem(sessionKey, JSON.stringify(value));\n }\n\n static getSession<T>(key: string): T | null {\n const sessionKey = this.getPrefixedKey(key);\n const item = sessionStorage.getItem(sessionKey);\n return item ? (JSON.parse(item) as T) : null;\n }\n\n static removeSession(key: string): void {\n sessionStorage.removeItem(this.getPrefixedKey(key));\n }\n\n static clearSession(): void {\n sessionStorage.clear();\n }\n\n static existsLocal(key: string): boolean {\n return localStorage.getItem(this.getPrefixedKey(key)) !== null;\n }\n\n static existsSession(key: string): boolean {\n return sessionStorage.getItem(this.getPrefixedKey(key)) !== null;\n }\n}\n","export class AppUtils {\n static toCamelCase(str: string): string {\n if (!str) return '';\n const words = str\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .split(/[\\s_]+/)\n .filter(Boolean);\n\n return words\n .map((word, index) => {\n word = word.toLowerCase();\n return index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1);\n })\n .join('');\n }\n\n static toPascalCase(str: string): string {\n const words = str.split(/[\\s_]+/).filter(Boolean);\n return words\n .map((word) => {\n word = word.toLowerCase();\n return word.charAt(0).toUpperCase() + word.slice(1);\n })\n .join('');\n }\n\n static toSnakeCase(str: string): string {\n const words = str.split(/[\\s_]+/).filter(Boolean);\n return words.map((word) => word.toLowerCase()).join('_');\n }\n\n static isNullOrEmpty(value: unknown): boolean {\n if (value == null) return true;\n if (Array.isArray(value) && value.length === 0) return true;\n if (typeof value === 'object' && Object.keys(value).length === 0) return true;\n return false;\n }\n\n static truncate(\n text: string | null | undefined,\n maxLength = 50,\n ellipsis = '...',\n preserveWords = false\n ): string {\n if (!text) return '';\n if (text.length <= maxLength) return text;\n\n if (maxLength < ellipsis.length + 2) {\n return text.slice(0, Math.max(0, maxLength - ellipsis.length)) + ellipsis;\n }\n\n const visibleChars = maxLength - ellipsis.length;\n const startChars = Math.ceil(visibleChars / 2);\n const endChars = Math.floor(visibleChars / 2);\n\n let start = text.slice(0, startChars);\n let end = text.slice(text.length - endChars);\n\n if (preserveWords) {\n const lastSpaceInStart = start.lastIndexOf(' ');\n if (lastSpaceInStart > visibleChars * 0.3) {\n start = start.slice(0, lastSpaceInStart);\n }\n const firstSpaceInEnd = end.indexOf(' ');\n if (firstSpaceInEnd !== -1 && firstSpaceInEnd < endChars * 0.7) {\n end = end.slice(firstSpaceInEnd + 1);\n }\n }\n\n return `${start}${ellipsis}${end}`;\n }\n}\n","export class UuidUtils {\n static randomUUID(): string {\n if (globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function') {\n return globalThis.crypto.randomUUID();\n }\n return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"elasticias-utils.mjs","sources":["../../../../libs/utils/src/lib/storage.utils.ts","../../../../libs/utils/src/lib/app.utils.ts","../../../../libs/utils/src/lib/uuid.utils.ts","../../../../libs/utils/src/lib/csv.utils.ts","../../../../libs/utils/src/lib/number.utils.ts","../../../../libs/utils/src/elasticias-utils.ts"],"sourcesContent":["/**\n * Configurable prefix-based wrapper for localStorage and sessionStorage.\n * Default prefix is 'Elasticias_'. Override via `StorageUtils.configure()`.\n */\nexport class StorageUtils {\n private static _prefix = 'Elasticias_';\n\n static configure(prefix: string): void {\n StorageUtils._prefix = prefix;\n }\n\n static get prefix(): string {\n return StorageUtils._prefix;\n }\n\n private static getPrefixedKey(key: string): string {\n return `${StorageUtils._prefix}${key}`;\n }\n\n static setLocal(key: string, value: unknown): void {\n try {\n const localKey = this.getPrefixedKey(key);\n localStorage.setItem(localKey, JSON.stringify(value));\n } catch (error) {\n console.error('Error storing data in localStorage: ', key, error);\n }\n }\n\n static getLocal<T>(key: string, usePrefix = true): T | null {\n const localKey = usePrefix ? this.getPrefixedKey(key) : key;\n const item = localStorage.getItem(localKey);\n try {\n if (item) {\n return JSON.parse(item) as T;\n }\n } catch (error) {\n console.error('Error parsing JSON from localStorage: ', key, error);\n }\n return null;\n }\n\n static removeLocal(key: string): void {\n localStorage.removeItem(this.getPrefixedKey(key));\n }\n\n static clearLocal(): void {\n localStorage.clear();\n }\n\n static setSession(key: string, value: unknown): void {\n const sessionKey = this.getPrefixedKey(key);\n sessionStorage.setItem(sessionKey, JSON.stringify(value));\n }\n\n static getSession<T>(key: string): T | null {\n const sessionKey = this.getPrefixedKey(key);\n const item = sessionStorage.getItem(sessionKey);\n return item ? (JSON.parse(item) as T) : null;\n }\n\n static removeSession(key: string): void {\n sessionStorage.removeItem(this.getPrefixedKey(key));\n }\n\n static clearSession(): void {\n sessionStorage.clear();\n }\n\n static existsLocal(key: string): boolean {\n return localStorage.getItem(this.getPrefixedKey(key)) !== null;\n }\n\n static existsSession(key: string): boolean {\n return sessionStorage.getItem(this.getPrefixedKey(key)) !== null;\n }\n}\n","export class AppUtils {\n static toCamelCase(str: string): string {\n if (!str) return '';\n const words = str\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .split(/[\\s_]+/)\n .filter(Boolean);\n\n return words\n .map((word, index) => {\n word = word.toLowerCase();\n return index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1);\n })\n .join('');\n }\n\n static toPascalCase(str: string): string {\n const words = str.split(/[\\s_]+/).filter(Boolean);\n return words\n .map((word) => {\n word = word.toLowerCase();\n return word.charAt(0).toUpperCase() + word.slice(1);\n })\n .join('');\n }\n\n static toSnakeCase(str: string): string {\n const words = str.split(/[\\s_]+/).filter(Boolean);\n return words.map((word) => word.toLowerCase()).join('_');\n }\n\n static isNullOrEmpty(value: unknown): boolean {\n if (value == null) return true;\n if (Array.isArray(value) && value.length === 0) return true;\n if (typeof value === 'object' && Object.keys(value).length === 0) return true;\n return false;\n }\n\n static truncate(\n text: string | null | undefined,\n maxLength = 50,\n ellipsis = '...',\n preserveWords = false\n ): string {\n if (!text) return '';\n if (text.length <= maxLength) return text;\n\n if (maxLength < ellipsis.length + 2) {\n return text.slice(0, Math.max(0, maxLength - ellipsis.length)) + ellipsis;\n }\n\n const visibleChars = maxLength - ellipsis.length;\n const startChars = Math.ceil(visibleChars / 2);\n const endChars = Math.floor(visibleChars / 2);\n\n let start = text.slice(0, startChars);\n let end = text.slice(text.length - endChars);\n\n if (preserveWords) {\n const lastSpaceInStart = start.lastIndexOf(' ');\n if (lastSpaceInStart > visibleChars * 0.3) {\n start = start.slice(0, lastSpaceInStart);\n }\n const firstSpaceInEnd = end.indexOf(' ');\n if (firstSpaceInEnd !== -1 && firstSpaceInEnd < endChars * 0.7) {\n end = end.slice(firstSpaceInEnd + 1);\n }\n }\n\n return `${start}${ellipsis}${end}`;\n }\n}\n","export class UuidUtils {\n static randomUUID(): string {\n if (globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function') {\n return globalThis.crypto.randomUUID();\n }\n return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n }\n}\n","export interface CsvColumn<T> {\n key: keyof T & string;\n header: string;\n}\n\nexport class CsvUtils {\n /** fr-MA Excel convention: ';' separator. Output has a header row, CRLF endings. */\n static toCsv<T>(\n rows: ReadonlyArray<T>,\n columns: ReadonlyArray<CsvColumn<T>>,\n separator = ';',\n ): string {\n // Excel/LibreOffice treat cells starting with =, +, -, @ or a tab as formulas.\n // Operator-entered client/product names could carry these as their first\n // character, so string values are neutralized (numbers are never affected —\n // the guard only applies before coercion, when `raw` is actually a string).\n const needsFormulaGuard = (value: string): boolean => /^[=+\\-@\\t]/.test(value);\n const escape = (raw: unknown): string => {\n if (raw === null || raw === undefined) return '';\n const isString = typeof raw === 'string';\n let s = String(raw);\n const guard = isString && needsFormulaGuard(s);\n if (guard) s = `'${s}`;\n return guard || s.includes(separator) || s.includes('\"') || s.includes('\\n')\n ? `\"${s.replace(/\"/g, '\"\"')}\"`\n : s;\n };\n const header = columns.map((c) => escape(c.header)).join(separator);\n const lines = rows.map((r) => columns.map((c) => escape(r[c.key])).join(separator));\n return [header, ...lines].join('\\r\\n');\n }\n\n /** UTF-8 BOM so Excel opens French accents / Arabic correctly. */\n static download(filename: string, csv: string): void {\n const blob = new Blob(['', csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.click();\n URL.revokeObjectURL(url);\n }\n}\n","interface CompactUnit {\n readonly threshold: number;\n readonly divisor: number;\n readonly suffix: string;\n}\n\n// Ordered largest-first so the first matching threshold wins.\nconst UNITS: readonly CompactUnit[] = [\n { threshold: 1_000_000_000, divisor: 1_000_000_000, suffix: 'Md' },\n { threshold: 1_000_000, divisor: 1_000_000, suffix: 'M' },\n { threshold: 1_000, divisor: 1_000, suffix: 'k' },\n];\n\nexport class NumberUtils {\n /**\n * Compact human-readable number: 1000 → \"1 k\", 2_500_000 → \"2,5 M\" (fr decimal comma).\n * maxDecimals applies after scaling; trailing zeros stripped. Below 1000 → localized plain number.\n */\n static compact(value: number, opts?: { locale?: string; maxDecimals?: number }): string {\n if (!Number.isFinite(value)) return '';\n\n const locale = opts?.locale ?? 'fr-MA';\n const maxDecimals = opts?.maxDecimals ?? 2;\n\n if (value === 0) return '0';\n\n const sign = value < 0 ? '-' : '';\n const abs = Math.abs(value);\n\n const format = (n: number, decimals: number): string =>\n n.toLocaleString(locale, {\n minimumFractionDigits: 0,\n maximumFractionDigits: decimals,\n });\n\n let unit = UNITS.find((u) => abs >= u.threshold);\n if (!unit) {\n return sign + format(abs, maxDecimals);\n }\n\n let scaled = abs / unit.divisor;\n // Rounding at maxDecimals can push the scaled value up to (or past) the\n // next unit's threshold (e.g. 999_999.9 @ 1 decimal -> 1000.0 k). Re-scale\n // in that case so the boundary bumps to the next unit instead.\n const rounded = Number(scaled.toFixed(maxDecimals));\n if (rounded >= 1000) {\n const nextUnitIndex = UNITS.indexOf(unit) - 1;\n const nextUnit = nextUnitIndex >= 0 ? UNITS[nextUnitIndex] : undefined;\n if (nextUnit) {\n unit = nextUnit;\n scaled = abs / unit.divisor;\n }\n }\n\n return `${sign}${format(scaled, maxDecimals)}\\u00A0${unit.suffix}`;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elasticias/utils",
3
- "version": "0.0.16",
3
+ "version": "1.0.0",
4
4
  "sideEffects": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,4 +31,27 @@ declare class UuidUtils {
31
31
  static randomUUID(): string;
32
32
  }
33
33
 
34
- export { AppUtils, StorageUtils, UuidUtils };
34
+ interface CsvColumn<T> {
35
+ key: keyof T & string;
36
+ header: string;
37
+ }
38
+ declare class CsvUtils {
39
+ /** fr-MA Excel convention: ';' separator. Output has a header row, CRLF endings. */
40
+ static toCsv<T>(rows: ReadonlyArray<T>, columns: ReadonlyArray<CsvColumn<T>>, separator?: string): string;
41
+ /** UTF-8 BOM so Excel opens French accents / Arabic correctly. */
42
+ static download(filename: string, csv: string): void;
43
+ }
44
+
45
+ declare class NumberUtils {
46
+ /**
47
+ * Compact human-readable number: 1000 → "1 k", 2_500_000 → "2,5 M" (fr decimal comma).
48
+ * maxDecimals applies after scaling; trailing zeros stripped. Below 1000 → localized plain number.
49
+ */
50
+ static compact(value: number, opts?: {
51
+ locale?: string;
52
+ maxDecimals?: number;
53
+ }): string;
54
+ }
55
+
56
+ export { AppUtils, CsvUtils, NumberUtils, StorageUtils, UuidUtils };
57
+ export type { CsvColumn };