core-services-sdk 1.3.23 → 1.3.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "core-services-sdk",
3
- "version": "1.3.23",
3
+ "version": "1.3.24",
4
4
  "main": "src/index.js",
5
5
  "type": "module",
6
6
  "types": "types/index.d.ts",
@@ -51,4 +51,4 @@
51
51
  "url": "^0.11.4",
52
52
  "vitest": "^3.2.4"
53
53
  }
54
- }
54
+ }
package/src/core/index.js CHANGED
@@ -5,3 +5,4 @@ export * from './normalize-min-max.js'
5
5
  export * from './normalize-to-array.js'
6
6
  export * from './combine-unique-arrays.js'
7
7
  export * from './normalize-phone-number.js'
8
+ export * from './normalize-premitives-types-or-default.js'
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Normalize Utilities
3
+ *
4
+ * Small helpers to guarantee stable, predictable values from user/config inputs.
5
+ * When an incoming value is missing, malformed, or in a different-but-supported
6
+ * representation (e.g., number/boolean as string), these utilities either accept
7
+ * it (after safe normalization) or return a default you control.
8
+ *
9
+ * Design highlights:
10
+ * - Keep call sites compact and intention-revealing.
11
+ * - Be strict for strings (no implicit type coercion).
12
+ * - Be permissive for number/boolean when the input is a string in an accepted form.
13
+ * - Fast, side-effect free, no deep cloning — values are returned by reference when valid.
14
+ */
15
+
16
+ /**
17
+ * Generic predicate-based normalization.
18
+ *
19
+ * Purpose:
20
+ * Ensure a value conforms to a caller-provided predicate; otherwise return a provided default.
21
+ * Useful when you need a single reusable pattern for custom shapes or policies
22
+ * (e.g., "must be a non-empty array of strings", "must be a plain object", etc.).
23
+ *
24
+ * Behavior:
25
+ * - Calls `isValid(value)`.
26
+ * - If the predicate returns true → returns `value` (as-is).
27
+ * - Otherwise → returns `defaultValue` (as-is).
28
+ *
29
+ * Performance & Safety:
30
+ * - O(1) aside from your predicate cost.
31
+ * - No cloning or sanitization is performed.
32
+ * - Ensure `isValid` is pure and fast; avoid throwing inside it.
33
+ *
34
+ * @template T
35
+ * @param {any} value
36
+ * Candidate input to validate.
37
+ * @param {(v:any)=>boolean} isValid
38
+ * Validation predicate. Return true iff the input is acceptable.
39
+ * @param {T} defaultValue
40
+ * Fallback to return when `value` fails validation.
41
+ * @returns {T}
42
+ * The original `value` when valid; otherwise `defaultValue`.
43
+ *
44
+ * @example
45
+ * // Ensure a finite number
46
+ * normalizeOrDefault(5, v => typeof v === 'number' && Number.isFinite(v), 0) // → 5
47
+ * normalizeOrDefault('x', v => typeof v === 'number', 0) // → 0
48
+ *
49
+ * @example
50
+ * // Ensure an object (non-null, non-array)
51
+ * const cfg = normalizeOrDefault(
52
+ * maybeCfg,
53
+ * v => v && typeof v === 'object' && !Array.isArray(v),
54
+ * {}
55
+ * )
56
+ */
57
+ export function normalizeOrDefault(value, isValid, defaultValue) {
58
+ return isValid(value) ? value : defaultValue
59
+ }
60
+
61
+ /**
62
+ * Normalize a value to a non-empty, trimmed string; otherwise return a default (also trimmed).
63
+ *
64
+ * Purpose:
65
+ * Guarantee that downstream code receives a usable, non-empty string
66
+ * without performing implicit type coercion.
67
+ *
68
+ * Acceptance Criteria:
69
+ * - Accept only actual strings whose `trim()` length is > 0.
70
+ * - Return `value.trim()` when valid.
71
+ * - Otherwise return `defaultValue.trim()`.
72
+ *
73
+ * Why strict for strings?
74
+ * - Silent coercion from non-strings to string can hide bugs.
75
+ * - If you need to stringify other types, do it explicitly at the call site.
76
+ *
77
+ * Edge Cases:
78
+ * - If `defaultValue` is empty or whitespace-only, the function returns an empty string.
79
+ * Prefer providing a meaningful, non-empty default for clarity.
80
+ *
81
+ * @param {any} value
82
+ * Candidate to normalize (must be a string to be accepted).
83
+ * @param {string} defaultValue
84
+ * Fallback used when `value` is not a non-empty string. Will be `trim()`ed.
85
+ * @returns {string}
86
+ * A trimmed non-empty string when `value` is valid; otherwise `defaultValue.trim()`.
87
+ *
88
+ * @example
89
+ * normalizeStringOrDefault(' user-roles-management:edit ', 'fallback')
90
+ * // → 'user-roles-management:edit'
91
+ *
92
+ * @example
93
+ * normalizeStringOrDefault('', 'user-roles-management:edit')
94
+ * // → 'user-roles-management:edit'
95
+ *
96
+ * @example
97
+ * normalizeStringOrDefault(42, 'user-roles-management:edit')
98
+ * // → 'user-roles-management:edit'
99
+ */
100
+ export function normalizeStringOrDefault(value, defaultValue) {
101
+ const def = typeof defaultValue === 'string' ? defaultValue.trim() : ''
102
+ if (typeof value === 'string') {
103
+ const trimmed = value.trim()
104
+ if (trimmed.length > 0) {
105
+ return trimmed
106
+ }
107
+ }
108
+ return def
109
+ }
110
+
111
+ /**
112
+ * Normalize a value to a valid number (with safe string coercion); otherwise return a default.
113
+ *
114
+ * Purpose:
115
+ * Accept numeric inputs that may arrive as strings (e.g., from env vars or config files)
116
+ * while keeping semantics explicit and predictable.
117
+ *
118
+ * Acceptance Criteria:
119
+ * - Accepts finite numbers (`typeof value === 'number' && Number.isFinite(value)`).
120
+ * - Accepts strings that become a finite number via `Number(trimmed)`.
121
+ * Examples: "42", " 3.14 ", "1e3", "-7", "0x10" (JS Number semantics).
122
+ * - Rejects non-numeric strings (e.g., "", " ", "abc") and non-number types.
123
+ * - Returns `defaultValue` when not acceptable.
124
+ *
125
+ * Parsing Semantics:
126
+ * - Uses `Number(s)` which requires the whole trimmed string to be numeric.
127
+ * - Honors JavaScript numeric literal rules (including hex and scientific notation).
128
+ * - If you want base-10 only or looser parsing, do it explicitly before calling.
129
+ *
130
+ * @param {any} value
131
+ * Candidate to normalize (number or numeric string).
132
+ * @param {number} defaultValue
133
+ * Fallback used when `value` is neither a finite number nor a numeric string.
134
+ * @returns {number}
135
+ * A finite number derived from `value`, or `defaultValue`.
136
+ *
137
+ * @example
138
+ * normalizeNumberOrDefault(42, 0) // → 42
139
+ * normalizeNumberOrDefault(' 3.14 ', 0) // → 3.14
140
+ * normalizeNumberOrDefault('1e3', 0) // → 1000
141
+ * normalizeNumberOrDefault('-7', 0) // → -7
142
+ *
143
+ * @example
144
+ * normalizeNumberOrDefault('abc', 7) // → 7
145
+ * normalizeNumberOrDefault(NaN, 7) // → 7
146
+ * normalizeNumberOrDefault({}, 7) // → 7
147
+ */
148
+ export function normalizeNumberOrDefault(value, defaultValue) {
149
+ if (typeof value === 'number' && Number.isFinite(value)) {
150
+ return value
151
+ }
152
+ if (typeof value === 'string') {
153
+ const s = value.trim()
154
+ if (s.length > 0) {
155
+ const n = Number(s)
156
+ if (Number.isFinite(n)) {
157
+ return n
158
+ }
159
+ }
160
+ }
161
+ return defaultValue
162
+ }
163
+
164
+ /**
165
+ * Normalize a value to a boolean (with "true"/"false" string support); otherwise return a default.
166
+ *
167
+ * Purpose:
168
+ * Stabilize feature flags and binary config values that might be provided as either booleans
169
+ * or as canonical strings.
170
+ *
171
+ * Acceptance Criteria:
172
+ * - Accepts actual booleans (`true` / `false`) → returned as-is.
173
+ * - Accepts strings equal to "true" or "false" (case-insensitive, trimmed).
174
+ * "true" → true
175
+ * "false" → false
176
+ * - Rejects other strings ("yes", "1", "0", etc.) and other types → returns `defaultValue`.
177
+ *
178
+ * Rationale:
179
+ * - Limiting string forms to the canonical words avoids accidental truthiness/falseyness.
180
+ * - If you need to accept "1"/"0" or other variants, coerce at the call site so intent is explicit.
181
+ *
182
+ * @param {any} value
183
+ * Candidate to normalize (boolean or "true"/"false" string).
184
+ * @param {boolean} defaultValue
185
+ * Fallback used when `value` is neither a boolean nor an accepted string form.
186
+ * @returns {boolean}
187
+ * A boolean derived from `value`, or `defaultValue`.
188
+ *
189
+ * @example
190
+ * normalizeBooleanOrDefault(true, false) // → true
191
+ * normalizeBooleanOrDefault(false, true) // → false
192
+ *
193
+ * @example
194
+ * normalizeBooleanOrDefault('true', false) // → true
195
+ * normalizeBooleanOrDefault(' FALSE ', true) // → false
196
+ *
197
+ * @example
198
+ * normalizeBooleanOrDefault('yes', false) // → false (rejected → default)
199
+ * normalizeBooleanOrDefault(1, true) // → true (rejected → default)
200
+ */
201
+ export function normalizeBooleanOrDefault(value, defaultValue) {
202
+ if (typeof value === 'boolean') {
203
+ return value
204
+ }
205
+ if (typeof value === 'string') {
206
+ const s = value.trim().toLowerCase()
207
+ if (s === 'true') {
208
+ return true
209
+ }
210
+ if (s === 'false') {
211
+ return false
212
+ }
213
+ }
214
+ return defaultValue
215
+ }
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import {
4
+ normalizeOrDefault,
5
+ normalizeStringOrDefault,
6
+ normalizeNumberOrDefault,
7
+ normalizeBooleanOrDefault,
8
+ } from '../../src/core/normalize-premitives-types-or-default.js'
9
+
10
+ describe('normalizeOrDefault (generic)', () => {
11
+ it('returns value if predicate is true', () => {
12
+ const out = normalizeOrDefault(5, (v) => typeof v === 'number', 0)
13
+ expect(out).toBe(5)
14
+ })
15
+ it('returns default if predicate is false', () => {
16
+ const out = normalizeOrDefault('x', (v) => typeof v === 'number', 0)
17
+ expect(out).toBe(0)
18
+ })
19
+ })
20
+
21
+ describe('normalizeStringOrDefault (strict, no coercion)', () => {
22
+ it('keeps a non-empty trimmed string', () => {
23
+ expect(normalizeStringOrDefault(' hello ', 'fallback')).toBe('hello')
24
+ })
25
+ it('returns default for empty / whitespace-only', () => {
26
+ expect(normalizeStringOrDefault('', 'fallback')).toBe('fallback')
27
+ expect(normalizeStringOrDefault(' ', 'fallback')).toBe('fallback')
28
+ })
29
+ it('returns default for non-strings', () => {
30
+ expect(normalizeStringOrDefault(123, 'fallback')).toBe('fallback')
31
+ expect(normalizeStringOrDefault(null, 'fallback')).toBe('fallback')
32
+ })
33
+ it('trims default defensively', () => {
34
+ expect(normalizeStringOrDefault('', ' fallback ')).toBe('fallback')
35
+ })
36
+ })
37
+
38
+ describe('normalizeNumberOrDefault (coercive for strings)', () => {
39
+ it('keeps valid number', () => {
40
+ expect(normalizeNumberOrDefault(42, 0)).toBe(42)
41
+ })
42
+ it('coerces valid numeric string', () => {
43
+ expect(normalizeNumberOrDefault('42', 0)).toBe(42)
44
+ expect(normalizeNumberOrDefault(' 3.14 ', 0)).toBe(3.14)
45
+ expect(normalizeNumberOrDefault('1e3', 0)).toBe(1000)
46
+ expect(normalizeNumberOrDefault('-7', 0)).toBe(-7)
47
+ })
48
+ it('returns default for invalid number inputs', () => {
49
+ expect(normalizeNumberOrDefault(NaN, 7)).toBe(7)
50
+ expect(normalizeNumberOrDefault(' ', 7)).toBe(7)
51
+ expect(normalizeNumberOrDefault('abc', 7)).toBe(7)
52
+ expect(normalizeNumberOrDefault({}, 7)).toBe(7)
53
+ })
54
+ })
55
+
56
+ describe('normalizeBooleanOrDefault (coercive for "true"/"false" strings)', () => {
57
+ it('keeps actual booleans', () => {
58
+ expect(normalizeBooleanOrDefault(true, false)).toBe(true)
59
+ expect(normalizeBooleanOrDefault(false, true)).toBe(false)
60
+ })
61
+ it('coerces "true"/"false" strings (case-insensitive)', () => {
62
+ expect(normalizeBooleanOrDefault('true', false)).toBe(true)
63
+ expect(normalizeBooleanOrDefault('FALSE', true)).toBe(false)
64
+ expect(normalizeBooleanOrDefault(' TrUe ', false)).toBe(true)
65
+ })
66
+ it('returns default for other strings / types', () => {
67
+ expect(normalizeBooleanOrDefault('yes', false)).toBe(false)
68
+ expect(normalizeBooleanOrDefault('0', true)).toBe(true)
69
+ expect(normalizeBooleanOrDefault(1, false)).toBe(false)
70
+ expect(normalizeBooleanOrDefault(null, true)).toBe(true)
71
+ })
72
+ })
@@ -5,3 +5,4 @@ export * from "./normalize-min-max.js";
5
5
  export * from "./normalize-to-array.js";
6
6
  export * from "./combine-unique-arrays.js";
7
7
  export * from "./normalize-phone-number.js";
8
+ export * from "./normalize-premitives-types-or-default.js";
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Normalize Utilities
3
+ *
4
+ * Small helpers to guarantee stable, predictable values from user/config inputs.
5
+ * When an incoming value is missing, malformed, or in a different-but-supported
6
+ * representation (e.g., number/boolean as string), these utilities either accept
7
+ * it (after safe normalization) or return a default you control.
8
+ *
9
+ * Design highlights:
10
+ * - Keep call sites compact and intention-revealing.
11
+ * - Be strict for strings (no implicit type coercion).
12
+ * - Be permissive for number/boolean when the input is a string in an accepted form.
13
+ * - Fast, side-effect free, no deep cloning — values are returned by reference when valid.
14
+ */
15
+ /**
16
+ * Generic predicate-based normalization.
17
+ *
18
+ * Purpose:
19
+ * Ensure a value conforms to a caller-provided predicate; otherwise return a provided default.
20
+ * Useful when you need a single reusable pattern for custom shapes or policies
21
+ * (e.g., "must be a non-empty array of strings", "must be a plain object", etc.).
22
+ *
23
+ * Behavior:
24
+ * - Calls `isValid(value)`.
25
+ * - If the predicate returns true → returns `value` (as-is).
26
+ * - Otherwise → returns `defaultValue` (as-is).
27
+ *
28
+ * Performance & Safety:
29
+ * - O(1) aside from your predicate cost.
30
+ * - No cloning or sanitization is performed.
31
+ * - Ensure `isValid` is pure and fast; avoid throwing inside it.
32
+ *
33
+ * @template T
34
+ * @param {any} value
35
+ * Candidate input to validate.
36
+ * @param {(v:any)=>boolean} isValid
37
+ * Validation predicate. Return true iff the input is acceptable.
38
+ * @param {T} defaultValue
39
+ * Fallback to return when `value` fails validation.
40
+ * @returns {T}
41
+ * The original `value` when valid; otherwise `defaultValue`.
42
+ *
43
+ * @example
44
+ * // Ensure a finite number
45
+ * normalizeOrDefault(5, v => typeof v === 'number' && Number.isFinite(v), 0) // → 5
46
+ * normalizeOrDefault('x', v => typeof v === 'number', 0) // → 0
47
+ *
48
+ * @example
49
+ * // Ensure an object (non-null, non-array)
50
+ * const cfg = normalizeOrDefault(
51
+ * maybeCfg,
52
+ * v => v && typeof v === 'object' && !Array.isArray(v),
53
+ * {}
54
+ * )
55
+ */
56
+ export function normalizeOrDefault<T>(value: any, isValid: (v: any) => boolean, defaultValue: T): T;
57
+ /**
58
+ * Normalize a value to a non-empty, trimmed string; otherwise return a default (also trimmed).
59
+ *
60
+ * Purpose:
61
+ * Guarantee that downstream code receives a usable, non-empty string
62
+ * without performing implicit type coercion.
63
+ *
64
+ * Acceptance Criteria:
65
+ * - Accept only actual strings whose `trim()` length is > 0.
66
+ * - Return `value.trim()` when valid.
67
+ * - Otherwise return `defaultValue.trim()`.
68
+ *
69
+ * Why strict for strings?
70
+ * - Silent coercion from non-strings to string can hide bugs.
71
+ * - If you need to stringify other types, do it explicitly at the call site.
72
+ *
73
+ * Edge Cases:
74
+ * - If `defaultValue` is empty or whitespace-only, the function returns an empty string.
75
+ * Prefer providing a meaningful, non-empty default for clarity.
76
+ *
77
+ * @param {any} value
78
+ * Candidate to normalize (must be a string to be accepted).
79
+ * @param {string} defaultValue
80
+ * Fallback used when `value` is not a non-empty string. Will be `trim()`ed.
81
+ * @returns {string}
82
+ * A trimmed non-empty string when `value` is valid; otherwise `defaultValue.trim()`.
83
+ *
84
+ * @example
85
+ * normalizeStringOrDefault(' user-roles-management:edit ', 'fallback')
86
+ * // → 'user-roles-management:edit'
87
+ *
88
+ * @example
89
+ * normalizeStringOrDefault('', 'user-roles-management:edit')
90
+ * // → 'user-roles-management:edit'
91
+ *
92
+ * @example
93
+ * normalizeStringOrDefault(42, 'user-roles-management:edit')
94
+ * // → 'user-roles-management:edit'
95
+ */
96
+ export function normalizeStringOrDefault(value: any, defaultValue: string): string;
97
+ /**
98
+ * Normalize a value to a valid number (with safe string coercion); otherwise return a default.
99
+ *
100
+ * Purpose:
101
+ * Accept numeric inputs that may arrive as strings (e.g., from env vars or config files)
102
+ * while keeping semantics explicit and predictable.
103
+ *
104
+ * Acceptance Criteria:
105
+ * - Accepts finite numbers (`typeof value === 'number' && Number.isFinite(value)`).
106
+ * - Accepts strings that become a finite number via `Number(trimmed)`.
107
+ * Examples: "42", " 3.14 ", "1e3", "-7", "0x10" (JS Number semantics).
108
+ * - Rejects non-numeric strings (e.g., "", " ", "abc") and non-number types.
109
+ * - Returns `defaultValue` when not acceptable.
110
+ *
111
+ * Parsing Semantics:
112
+ * - Uses `Number(s)` which requires the whole trimmed string to be numeric.
113
+ * - Honors JavaScript numeric literal rules (including hex and scientific notation).
114
+ * - If you want base-10 only or looser parsing, do it explicitly before calling.
115
+ *
116
+ * @param {any} value
117
+ * Candidate to normalize (number or numeric string).
118
+ * @param {number} defaultValue
119
+ * Fallback used when `value` is neither a finite number nor a numeric string.
120
+ * @returns {number}
121
+ * A finite number derived from `value`, or `defaultValue`.
122
+ *
123
+ * @example
124
+ * normalizeNumberOrDefault(42, 0) // → 42
125
+ * normalizeNumberOrDefault(' 3.14 ', 0) // → 3.14
126
+ * normalizeNumberOrDefault('1e3', 0) // → 1000
127
+ * normalizeNumberOrDefault('-7', 0) // → -7
128
+ *
129
+ * @example
130
+ * normalizeNumberOrDefault('abc', 7) // → 7
131
+ * normalizeNumberOrDefault(NaN, 7) // → 7
132
+ * normalizeNumberOrDefault({}, 7) // → 7
133
+ */
134
+ export function normalizeNumberOrDefault(value: any, defaultValue: number): number;
135
+ /**
136
+ * Normalize a value to a boolean (with "true"/"false" string support); otherwise return a default.
137
+ *
138
+ * Purpose:
139
+ * Stabilize feature flags and binary config values that might be provided as either booleans
140
+ * or as canonical strings.
141
+ *
142
+ * Acceptance Criteria:
143
+ * - Accepts actual booleans (`true` / `false`) → returned as-is.
144
+ * - Accepts strings equal to "true" or "false" (case-insensitive, trimmed).
145
+ * "true" → true
146
+ * "false" → false
147
+ * - Rejects other strings ("yes", "1", "0", etc.) and other types → returns `defaultValue`.
148
+ *
149
+ * Rationale:
150
+ * - Limiting string forms to the canonical words avoids accidental truthiness/falseyness.
151
+ * - If you need to accept "1"/"0" or other variants, coerce at the call site so intent is explicit.
152
+ *
153
+ * @param {any} value
154
+ * Candidate to normalize (boolean or "true"/"false" string).
155
+ * @param {boolean} defaultValue
156
+ * Fallback used when `value` is neither a boolean nor an accepted string form.
157
+ * @returns {boolean}
158
+ * A boolean derived from `value`, or `defaultValue`.
159
+ *
160
+ * @example
161
+ * normalizeBooleanOrDefault(true, false) // → true
162
+ * normalizeBooleanOrDefault(false, true) // → false
163
+ *
164
+ * @example
165
+ * normalizeBooleanOrDefault('true', false) // → true
166
+ * normalizeBooleanOrDefault(' FALSE ', true) // → false
167
+ *
168
+ * @example
169
+ * normalizeBooleanOrDefault('yes', false) // → false (rejected → default)
170
+ * normalizeBooleanOrDefault(1, true) // → true (rejected → default)
171
+ */
172
+ export function normalizeBooleanOrDefault(value: any, defaultValue: boolean): boolean;