core-services-sdk 1.3.63 → 1.3.65
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 +1 -1
- package/src/core/normalize-phone-number.js +144 -25
- package/src/postgresql/core/get-table-name.js +10 -0
- package/src/postgresql/filters/apply-filter-object.js +19 -0
- package/src/postgresql/filters/apply-filter-snake-case.js +16 -0
- package/src/postgresql/filters/apply-filter.js +33 -0
- package/src/postgresql/filters/operators.js +32 -0
- package/src/postgresql/index.js +5 -2
- package/src/postgresql/modifiers/apply-order-by.js +19 -0
- package/src/postgresql/modifiers/apply-pagination.js +12 -0
- package/src/postgresql/pagination/paginate.js +48 -0
- package/tests/core/normalize-phone-number.unit.test.js +45 -0
- package/tests/postgresql/apply-filter-snake-case.integration.test.js +220 -0
- package/tests/postgresql/apply-filter.integration.test.js +34 -353
- package/tests/postgresql/core/get-table-name.unit.test.js +20 -0
- package/tests/postgresql/filters/apply-filter-object.test.js +23 -0
- package/tests/postgresql/filters/operators.unit.test.js +23 -0
- package/tests/postgresql/modifiers/apply-order-by.test.js +80 -0
- package/tests/postgresql/modifiers/apply-pagination.unit.test.js +18 -0
- package/tests/postgresql/paginate.integration.test.js +10 -18
- package/tests/postgresql/pagination/paginate.js +48 -0
- package/tests/postgresql/validate-schema.integration.test.js +9 -5
- package/types/core/normalize-phone-number.d.ts +97 -27
- package/types/postgresql/apply-filter.d.ts +131 -20
- package/types/postgresql/core/get-table-name.d.ts +10 -0
- package/types/postgresql/filters/apply-filter-object.d.ts +15 -0
- package/types/postgresql/filters/apply-filter-snake-case.d.ts +14 -0
- package/types/postgresql/filters/apply-filter.d.ts +15 -0
- package/types/postgresql/filters/operators.d.ts +14 -0
- package/types/postgresql/index.d.ts +5 -2
- package/types/postgresql/modifiers/apply-order-by.d.ts +17 -0
- package/types/postgresql/modifiers/apply-pagination.d.ts +17 -0
- package/types/postgresql/pagination/paginate.d.ts +29 -0
- package/src/postgresql/apply-filter.js +0 -275
- package/src/postgresql/paginate.js +0 -61
package/package.json
CHANGED
|
@@ -3,7 +3,46 @@
|
|
|
3
3
|
|
|
4
4
|
import * as raw from 'google-libphonenumber'
|
|
5
5
|
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {Object} NormalizedPhone
|
|
8
|
+
* @property {string} e164
|
|
9
|
+
* @property {number} type
|
|
10
|
+
* @property {string} national
|
|
11
|
+
* @property {number} countryCode
|
|
12
|
+
* @property {string} nationalClean
|
|
13
|
+
* @property {string} international
|
|
14
|
+
* @property {string} countryCodeE164
|
|
15
|
+
* @property {string} internationalClean
|
|
16
|
+
* @property {string | undefined} regionCode
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* normalize-phone-number.js
|
|
21
|
+
*
|
|
22
|
+
* Utilities for parsing, validating, and normalizing phone numbers
|
|
23
|
+
* using google-libphonenumber.
|
|
24
|
+
*
|
|
25
|
+
* Supports both ESM and CJS interop builds.
|
|
26
|
+
* All normalization outputs are canonical and safe for persistence,
|
|
27
|
+
* comparison, indexing, and login identifiers.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve google-libphonenumber exports regardless of ESM / CJS shape.
|
|
32
|
+
*
|
|
33
|
+
* Some builds expose:
|
|
34
|
+
* - raw.PhoneNumberUtil
|
|
35
|
+
* Others expose:
|
|
36
|
+
* - raw.default.PhoneNumberUtil
|
|
37
|
+
*
|
|
38
|
+
* This helper guarantees a consistent API.
|
|
39
|
+
*
|
|
40
|
+
* @returns {{
|
|
41
|
+
* PhoneNumberUtil: any,
|
|
42
|
+
* PhoneNumberFormat: any
|
|
43
|
+
* }}
|
|
44
|
+
* @throws {Error} If required exports are missing
|
|
45
|
+
*/
|
|
7
46
|
export function getLib() {
|
|
8
47
|
// Prefer direct (CJS-style or ESM w/ named), else default
|
|
9
48
|
// e.g. raw.PhoneNumberUtil OR raw.default.PhoneNumberUtil
|
|
@@ -19,6 +58,15 @@ export function getLib() {
|
|
|
19
58
|
|
|
20
59
|
let _util // lazy singleton
|
|
21
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Lazy singleton accessor for PhoneNumberUtil.
|
|
63
|
+
*
|
|
64
|
+
* Ensures:
|
|
65
|
+
* - Single instance per process
|
|
66
|
+
* - No eager initialization cost
|
|
67
|
+
*
|
|
68
|
+
* @returns {any} PhoneNumberUtil instance
|
|
69
|
+
*/
|
|
22
70
|
export function phoneUtil() {
|
|
23
71
|
if (!_util) {
|
|
24
72
|
const { PhoneNumberUtil } = getLib()
|
|
@@ -27,15 +75,25 @@ export function phoneUtil() {
|
|
|
27
75
|
return _util
|
|
28
76
|
}
|
|
29
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Internal helper returning PhoneNumberFormat enum.
|
|
80
|
+
*
|
|
81
|
+
* @returns {any} PhoneNumberFormat enum
|
|
82
|
+
*/
|
|
30
83
|
function formats() {
|
|
31
84
|
const { PhoneNumberFormat } = getLib()
|
|
32
85
|
return PhoneNumberFormat
|
|
33
86
|
}
|
|
34
87
|
|
|
35
88
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
89
|
+
* Cleans user input before parsing:
|
|
90
|
+
* - Trims whitespace
|
|
91
|
+
* - Removes invisible RTL/LTR markers
|
|
92
|
+
*
|
|
93
|
+
* Does NOT remove digits, plus sign, or formatting characters.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} input Raw user input
|
|
96
|
+
* @returns {string} Cleaned input string
|
|
39
97
|
*/
|
|
40
98
|
function clean(input) {
|
|
41
99
|
return String(input)
|
|
@@ -44,27 +102,69 @@ function clean(input) {
|
|
|
44
102
|
}
|
|
45
103
|
|
|
46
104
|
/**
|
|
47
|
-
*
|
|
105
|
+
* Converts a parsed google-libphonenumber object into a normalized result.
|
|
106
|
+
*
|
|
107
|
+
* Returned formats:
|
|
108
|
+
* - e164: Canonical phone number in E.164 format.
|
|
109
|
+
* Intended for storage, indexing, comparison, login identifiers, and OTP flows.
|
|
110
|
+
*
|
|
111
|
+
* - national: Local, human-readable phone number representation.
|
|
112
|
+
* May include formatting characters such as dashes or spaces.
|
|
113
|
+
*
|
|
114
|
+
* - nationalClean: Local phone number containing digits only.
|
|
115
|
+
*
|
|
116
|
+
* - international: Human-readable international representation.
|
|
117
|
+
*
|
|
118
|
+
* - internationalClean: International phone number containing digits only,
|
|
119
|
+
* without '+' or formatting characters.
|
|
120
|
+
*
|
|
121
|
+
* - regionCode: ISO 3166-1 alpha-2 region code (e.g. "IL").
|
|
122
|
+
*
|
|
123
|
+
* - countryCallingCode: Numeric international dialing code (e.g. 972).
|
|
124
|
+
*
|
|
125
|
+
* - countryCallingCodeE164: International dialing code with '+' prefix (e.g. "+972").
|
|
126
|
+
*
|
|
127
|
+
* Notes:
|
|
128
|
+
* - Only `e164` should be persisted or used for identity comparison.
|
|
129
|
+
* - All other formats are intended strictly for UI, display, copy, or integrations.
|
|
130
|
+
*
|
|
48
131
|
* @param {any} parsed
|
|
49
|
-
*
|
|
132
|
+
* Parsed phone number object returned by google-libphonenumber.
|
|
133
|
+
*
|
|
134
|
+
* @returns {NormalizedPhone}
|
|
50
135
|
*/
|
|
136
|
+
|
|
51
137
|
function toResult(parsed) {
|
|
52
138
|
const PNF = formats()
|
|
53
139
|
const util = phoneUtil()
|
|
54
|
-
|
|
140
|
+
|
|
141
|
+
const results = {
|
|
142
|
+
type: util.getNumberType(parsed),
|
|
55
143
|
e164: util.format(parsed, PNF.E164),
|
|
56
144
|
national: util.format(parsed, PNF.NATIONAL),
|
|
57
|
-
international: util.format(parsed, PNF.INTERNATIONAL),
|
|
58
145
|
regionCode: util.getRegionCodeForNumber(parsed),
|
|
59
|
-
|
|
146
|
+
international: util.format(parsed, PNF.INTERNATIONAL),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const countryCode = `${parsed.getCountryCode()}`
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
...results,
|
|
153
|
+
countryCode,
|
|
154
|
+
countryCodeE164: `+${countryCode}`,
|
|
155
|
+
nationalClean: results.national.replace(/\D/g, ''),
|
|
156
|
+
internationalClean: results.e164.replace(/\D/g, ''),
|
|
60
157
|
}
|
|
61
158
|
}
|
|
62
159
|
|
|
63
160
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
161
|
+
* Normalize and validate an international phone number.
|
|
162
|
+
*
|
|
163
|
+
* Input MUST start with '+'.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} input International phone number (E.164-like)
|
|
166
|
+
* @returns {NormalizedPhone}
|
|
167
|
+
* @throws {Error} If the phone number is invalid
|
|
68
168
|
*/
|
|
69
169
|
export function normalizePhoneOrThrowIntl(input) {
|
|
70
170
|
try {
|
|
@@ -82,11 +182,22 @@ export function normalizePhoneOrThrowIntl(input) {
|
|
|
82
182
|
}
|
|
83
183
|
|
|
84
184
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
185
|
+
* Normalize and validate a national phone number using a region hint.
|
|
186
|
+
*
|
|
187
|
+
* Example:
|
|
188
|
+
* input: "0523444444"
|
|
189
|
+
* defaultRegion: "IL"
|
|
190
|
+
*
|
|
191
|
+
* @param {string} input National phone number
|
|
192
|
+
* @param {string} defaultRegion ISO 3166-1 alpha-2 country code
|
|
193
|
+
* @returns {{
|
|
194
|
+
* e164: string,
|
|
195
|
+
* national: string,
|
|
196
|
+
* international: string,
|
|
197
|
+
* regionCode: string | undefined,
|
|
198
|
+
* type: number
|
|
199
|
+
* }}
|
|
200
|
+
* @throws {Error} If the phone number is invalid
|
|
90
201
|
*/
|
|
91
202
|
export function normalizePhoneOrThrowWithRegion(input, defaultRegion) {
|
|
92
203
|
try {
|
|
@@ -104,13 +215,21 @@ export function normalizePhoneOrThrowWithRegion(input, defaultRegion) {
|
|
|
104
215
|
}
|
|
105
216
|
|
|
106
217
|
/**
|
|
107
|
-
* Smart normalization
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
218
|
+
* Smart normalization entry point.
|
|
219
|
+
*
|
|
220
|
+
* Behavior:
|
|
221
|
+
* - If input starts with '+', parses as international
|
|
222
|
+
* - Otherwise requires defaultRegion and parses as national
|
|
223
|
+
*
|
|
224
|
+
* This is the recommended function for login, signup, and verification flows.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} input Phone number (international or national)
|
|
227
|
+
* @param {{
|
|
228
|
+
* defaultRegion?: string
|
|
229
|
+
* }} [opts]
|
|
230
|
+
*
|
|
231
|
+
* @returns {NormalizedPhone}
|
|
232
|
+
* @throws {Error} If invalid or defaultRegion is missing
|
|
114
233
|
*/
|
|
115
234
|
export function normalizePhoneOrThrow(input, opts = {}) {
|
|
116
235
|
const cleaned = clean(input)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts base table name from Knex QueryBuilder.
|
|
3
|
+
* Relies on Knex internal structure.
|
|
4
|
+
*
|
|
5
|
+
* @param {import('knex').Knex.QueryBuilder} query
|
|
6
|
+
* @returns {string|undefined}
|
|
7
|
+
*/
|
|
8
|
+
export function getTableNameFromQuery(query) {
|
|
9
|
+
return query?._single?.table
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { OPERATORS } from './operators.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Applies multiple operators on the same column.
|
|
5
|
+
*
|
|
6
|
+
* @param {import('knex').Knex.QueryBuilder} query
|
|
7
|
+
* @param {string} qualifiedKey
|
|
8
|
+
* @param {Object<string, *>} value
|
|
9
|
+
* @returns {import('knex').Knex.QueryBuilder}
|
|
10
|
+
*/
|
|
11
|
+
export function applyFilterObject(query, qualifiedKey, value) {
|
|
12
|
+
return Object.entries(value).reduce((q, [operator, val]) => {
|
|
13
|
+
if (!OPERATORS[operator]) {
|
|
14
|
+
return q
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return OPERATORS[operator](q, qualifiedKey, val)
|
|
18
|
+
}, query)
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { toSnakeCase } from '../../core/case-mapper.js'
|
|
2
|
+
import { applyFilter } from './apply-filter.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Applies filters with automatic camelCase to snake_case conversion.
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} params
|
|
8
|
+
* @param {import('knex').Knex.QueryBuilder} params.query
|
|
9
|
+
* @param {Object} params.filter
|
|
10
|
+
*/
|
|
11
|
+
export function applyFilterSnakeCase({ query, filter }) {
|
|
12
|
+
return applyFilter({
|
|
13
|
+
query,
|
|
14
|
+
filter: toSnakeCase(filter),
|
|
15
|
+
})
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getTableNameFromQuery } from '../core/get-table-name.js'
|
|
2
|
+
import { OPERATORS } from './operators.js'
|
|
3
|
+
import { applyFilterObject } from './apply-filter-object.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Applies MongoDB-style filters to a Knex QueryBuilder.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} params
|
|
9
|
+
* @param {import('knex').Knex.QueryBuilder} params.query
|
|
10
|
+
* @param {Object} [params.filter]
|
|
11
|
+
* @returns {import('knex').Knex.QueryBuilder}
|
|
12
|
+
*/
|
|
13
|
+
export function applyFilter({ query, filter = {} }) {
|
|
14
|
+
const tableName = getTableNameFromQuery(query)
|
|
15
|
+
|
|
16
|
+
if (!filter || Object.keys(filter).length === 0) {
|
|
17
|
+
return query
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return Object.entries(filter).reduce((q, [key, value]) => {
|
|
21
|
+
const qualifiedKey = tableName ? `${tableName}.${key}` : key
|
|
22
|
+
|
|
23
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
24
|
+
return applyFilterObject(q, qualifiedKey, value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return OPERATORS.in(q, qualifiedKey, value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return OPERATORS.eq(q, qualifiedKey, value)
|
|
32
|
+
}, query)
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Function} OperatorFunction
|
|
3
|
+
* @param {import('knex').Knex.QueryBuilder} q
|
|
4
|
+
* @param {string} key
|
|
5
|
+
* @param {*} value
|
|
6
|
+
* @returns {import('knex').Knex.QueryBuilder}
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @type {Object<string, OperatorFunction>}
|
|
11
|
+
*/
|
|
12
|
+
export const OPERATORS = {
|
|
13
|
+
in: (q, key, value) => q.whereIn(key, value),
|
|
14
|
+
nin: (q, key, value) => q.whereNotIn(key, value),
|
|
15
|
+
|
|
16
|
+
eq: (q, key, value) => q.where(key, '=', value),
|
|
17
|
+
ne: (q, key, value) => q.where(key, '!=', value),
|
|
18
|
+
neq: (q, key, value) => q.where(key, '!=', value),
|
|
19
|
+
|
|
20
|
+
gt: (q, key, value) => q.where(key, '>', value),
|
|
21
|
+
gte: (q, key, value) => q.where(key, '>=', value),
|
|
22
|
+
lt: (q, key, value) => q.where(key, '<', value),
|
|
23
|
+
lte: (q, key, value) => q.where(key, '<=', value),
|
|
24
|
+
|
|
25
|
+
like: (q, key, value) => q.where(key, 'like', value),
|
|
26
|
+
ilike: (q, key, value) => q.where(key, 'ilike', value),
|
|
27
|
+
|
|
28
|
+
isNull: (q, key, value) => (value ? q.whereNull(key) : q.whereNotNull(key)),
|
|
29
|
+
|
|
30
|
+
isNotNull: (q, key, value) =>
|
|
31
|
+
value ? q.whereNotNull(key) : q.whereNull(key),
|
|
32
|
+
}
|
package/src/postgresql/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export * from './paginate.js'
|
|
2
|
-
export * from './apply-filter.js'
|
|
3
1
|
export * from './connect-to-pg.js'
|
|
4
2
|
export * from './validate-schema.js'
|
|
3
|
+
export * from './pagination/paginate.js'
|
|
4
|
+
export * from './filters/apply-filter.js'
|
|
5
|
+
export * from './modifiers/apply-order-by.js'
|
|
5
6
|
export * from './start-stop-postgres-docker.js'
|
|
7
|
+
export * from './modifiers/apply-pagination.js'
|
|
8
|
+
export * from './filters/apply-filter-snake-case.js'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getTableNameFromQuery } from '../core/get-table-name.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Applies ORDER BY clause.
|
|
5
|
+
*
|
|
6
|
+
* @param {Object} params
|
|
7
|
+
* @param {import('knex').Knex.QueryBuilder} params.query
|
|
8
|
+
* @param {{ column: string, direction?: 'asc'|'desc' }} params.orderBy
|
|
9
|
+
*/
|
|
10
|
+
export function applyOrderBy({ query, orderBy }) {
|
|
11
|
+
if (!orderBy?.column) {
|
|
12
|
+
return query
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const tableName = getTableNameFromQuery(query)
|
|
16
|
+
const column = tableName ? `${tableName}.${orderBy.column}` : orderBy.column
|
|
17
|
+
|
|
18
|
+
return query.orderBy(column, orderBy.direction || 'asc')
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies LIMIT and OFFSET.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} params
|
|
5
|
+
* @param {import('knex').Knex.QueryBuilder} params.query
|
|
6
|
+
* @param {number} [params.page=1]
|
|
7
|
+
* @param {number} [params.limit=10]
|
|
8
|
+
*/
|
|
9
|
+
export function applyPagination({ query, page = 1, limit = 10 }) {
|
|
10
|
+
const offset = (page - 1) * limit
|
|
11
|
+
return query.limit(limit).offset(offset)
|
|
12
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { applyFilter } from '../filters/apply-filter.js'
|
|
2
|
+
import { applyFilterSnakeCase } from '../filters/apply-filter-snake-case.js'
|
|
3
|
+
import { applyOrderBy } from '../modifiers/apply-order-by.js'
|
|
4
|
+
import { applyPagination } from '../modifiers/apply-pagination.js'
|
|
5
|
+
import { normalizeNumberOrDefault } from '../../core/normalize-premitives-types-or-default.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Executes paginated query.
|
|
9
|
+
*
|
|
10
|
+
* @async
|
|
11
|
+
*/
|
|
12
|
+
export async function sqlPaginate({
|
|
13
|
+
mapRow,
|
|
14
|
+
orderBy,
|
|
15
|
+
page = 1,
|
|
16
|
+
baseQuery,
|
|
17
|
+
limit = 10,
|
|
18
|
+
filter = {},
|
|
19
|
+
snakeCase = true,
|
|
20
|
+
}) {
|
|
21
|
+
const listQuery = baseQuery.clone()
|
|
22
|
+
const countQuery = baseQuery.clone()
|
|
23
|
+
|
|
24
|
+
const applyFilterFn = snakeCase ? applyFilterSnakeCase : applyFilter
|
|
25
|
+
|
|
26
|
+
applyFilterFn({ query: listQuery, filter })
|
|
27
|
+
applyFilterFn({ query: countQuery, filter })
|
|
28
|
+
|
|
29
|
+
applyOrderBy({ query: listQuery, orderBy })
|
|
30
|
+
applyPagination({ query: listQuery, page, limit })
|
|
31
|
+
|
|
32
|
+
const [rows, countResult] = await Promise.all([
|
|
33
|
+
listQuery.select('*'),
|
|
34
|
+
countQuery.count('* as count').first(),
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
const totalCount = normalizeNumberOrDefault(countResult?.count || 0)
|
|
38
|
+
const totalPages = Math.ceil(totalCount / limit)
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
totalCount,
|
|
42
|
+
totalPages,
|
|
43
|
+
currentPage: page,
|
|
44
|
+
hasPrevious: page > 1,
|
|
45
|
+
hasNext: page < totalPages,
|
|
46
|
+
list: mapRow ? rows.map(mapRow) : rows,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -58,6 +58,51 @@ describe('phone normalization', () => {
|
|
|
58
58
|
expect(out.regionCode).toBe('IL')
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
+
describe('normalizePhoneOrThrow - international input', () => {
|
|
62
|
+
it('returns full normalized data for international input without defaultRegion', () => {
|
|
63
|
+
const out = normalizePhoneOrThrow('+972523443413')
|
|
64
|
+
|
|
65
|
+
// canonical
|
|
66
|
+
expect(out.e164).toBe('+972523443413')
|
|
67
|
+
// clean formats
|
|
68
|
+
|
|
69
|
+
expect(out.internationalClean).toBe('972523443413')
|
|
70
|
+
expect(out.nationalClean).toBe('0523443413')
|
|
71
|
+
|
|
72
|
+
// formatted (do not assert exact formatting)
|
|
73
|
+
expect(typeof out.national).toBe('string')
|
|
74
|
+
expect(typeof out.international).toBe('string')
|
|
75
|
+
|
|
76
|
+
// metadata
|
|
77
|
+
expect(out.regionCode).toBe('IL')
|
|
78
|
+
expect(out.countryCode).toBe('972')
|
|
79
|
+
expect(out.countryCodeE164).toBe('+972')
|
|
80
|
+
expect(typeof out.type).toBe('number')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('returns identical normalized data when defaultRegion is provided', () => {
|
|
84
|
+
const out = normalizePhoneOrThrow('0523443413', {
|
|
85
|
+
defaultRegion: 'IL',
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// canonical
|
|
89
|
+
expect(out.e164).toBe('+972523443413')
|
|
90
|
+
|
|
91
|
+
// clean formats
|
|
92
|
+
expect(out.internationalClean).toBe('972523443413')
|
|
93
|
+
expect(out.nationalClean).toBe('0523443413')
|
|
94
|
+
|
|
95
|
+
// formatted (do not assert exact formatting)
|
|
96
|
+
expect(typeof out.national).toBe('string')
|
|
97
|
+
expect(typeof out.international).toBe('string')
|
|
98
|
+
|
|
99
|
+
// metadata
|
|
100
|
+
expect(out.regionCode).toBe('IL')
|
|
101
|
+
expect(out.countryCode).toBe('972')
|
|
102
|
+
expect(out.countryCodeE164).toBe('+972')
|
|
103
|
+
expect(typeof out.type).toBe('number')
|
|
104
|
+
})
|
|
105
|
+
})
|
|
61
106
|
it('normalizePhoneOrThrow (smart): throws if national with no defaultRegion', () => {
|
|
62
107
|
expect(() => normalizePhoneOrThrow('054-123-4567')).toThrow(
|
|
63
108
|
/defaultRegion is required/i,
|