@mailwoman/codex 8.1.0 → 8.2.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.
- package/address-system-conventions.ts +7 -1
- package/au/delivery-service.ts +34 -12
- package/au/level-designator.ts +38 -14
- package/au/postcode.ts +9 -3
- package/au/state.ts +9 -3
- package/ca/postal-code.ts +10 -3
- package/ca/province.ts +23 -9
- package/ca/street-type.ts +9 -5
- package/country/country.ts +14 -8
- package/country/official-languages.ts +12 -4
- package/country/reference-data.ts +7 -3
- package/country/reference.ts +1 -1
- package/country/subdivision.ts +19 -8
- package/de/bundesland.ts +19 -6
- package/de/postleitzahl.ts +21 -7
- package/de/street-type.ts +14 -3
- package/fr/cedex.ts +15 -5
- package/fr/code-postal.ts +17 -4
- package/fr/departement.ts +18 -6
- package/fr/region.ts +23 -9
- package/fr/voie.ts +8 -4
- package/gb/country.ts +25 -9
- package/gb/postcode-area.ts +6 -2
- package/gb/postcode.ts +10 -3
- package/gb/street-type.ts +4 -2
- package/index.ts +1 -0
- package/jp/address-unit.ts +8 -4
- package/jp/postal-code.ts +7 -3
- package/jp/prefecture.ts +27 -10
- package/level-semantics.ts +55 -19
- package/nz/delivery-service.ts +43 -15
- package/nz/postcode.ts +9 -3
- package/out/address-system-conventions.d.ts +7 -1
- package/out/address-system-conventions.d.ts.map +1 -1
- package/out/address-system-conventions.js +7 -1
- package/out/address-system-conventions.js.map +1 -1
- package/out/au/delivery-service.d.ts +30 -10
- package/out/au/delivery-service.d.ts.map +1 -1
- package/out/au/delivery-service.js +7 -3
- package/out/au/delivery-service.js.map +1 -1
- package/out/au/level-designator.d.ts +27 -9
- package/out/au/level-designator.d.ts.map +1 -1
- package/out/au/level-designator.js +10 -6
- package/out/au/level-designator.js.map +1 -1
- package/out/au/postcode.d.ts +9 -3
- package/out/au/postcode.d.ts.map +1 -1
- package/out/au/postcode.js +9 -3
- package/out/au/postcode.js.map +1 -1
- package/out/au/state.d.ts +9 -3
- package/out/au/state.d.ts.map +1 -1
- package/out/au/state.js +6 -2
- package/out/au/state.js.map +1 -1
- package/out/ca/postal-code.d.ts +3 -1
- package/out/ca/postal-code.d.ts.map +1 -1
- package/out/ca/postal-code.js +9 -3
- package/out/ca/postal-code.js.map +1 -1
- package/out/ca/province.d.ts +18 -6
- package/out/ca/province.d.ts.map +1 -1
- package/out/ca/province.js +8 -4
- package/out/ca/province.js.map +1 -1
- package/out/ca/street-type.d.ts.map +1 -1
- package/out/ca/street-type.js +9 -5
- package/out/ca/street-type.js.map +1 -1
- package/out/country/country.d.ts +11 -7
- package/out/country/country.d.ts.map +1 -1
- package/out/country/country.js +7 -4
- package/out/country/country.js.map +1 -1
- package/out/country/official-languages.d.ts +12 -4
- package/out/country/official-languages.d.ts.map +1 -1
- package/out/country/official-languages.js +3 -1
- package/out/country/official-languages.js.map +1 -1
- package/out/country/reference-data.d.ts +6 -2
- package/out/country/reference-data.d.ts.map +1 -1
- package/out/country/reference-data.js +4 -2
- package/out/country/reference-data.js.map +1 -1
- package/out/country/reference.js +1 -1
- package/out/country/reference.js.map +1 -1
- package/out/country/subdivision.d.ts +12 -4
- package/out/country/subdivision.d.ts.map +1 -1
- package/out/country/subdivision.js +6 -4
- package/out/country/subdivision.js.map +1 -1
- package/out/de/bundesland.d.ts +18 -6
- package/out/de/bundesland.d.ts.map +1 -1
- package/out/de/bundesland.js +3 -1
- package/out/de/bundesland.js.map +1 -1
- package/out/de/postleitzahl.d.ts +21 -7
- package/out/de/postleitzahl.d.ts.map +1 -1
- package/out/de/postleitzahl.js +9 -3
- package/out/de/postleitzahl.js.map +1 -1
- package/out/de/street-type.d.ts +5 -4
- package/out/de/street-type.d.ts.map +1 -1
- package/out/de/street-type.js +10 -2
- package/out/de/street-type.js.map +1 -1
- package/out/fr/cedex.d.ts +15 -5
- package/out/fr/cedex.d.ts.map +1 -1
- package/out/fr/cedex.js +6 -2
- package/out/fr/cedex.js.map +1 -1
- package/out/fr/code-postal.d.ts +8 -11
- package/out/fr/code-postal.d.ts.map +1 -1
- package/out/fr/code-postal.js +24 -4
- package/out/fr/code-postal.js.map +1 -1
- package/out/fr/departement.d.ts +18 -6
- package/out/fr/departement.d.ts.map +1 -1
- package/out/fr/departement.js +3 -1
- package/out/fr/departement.js.map +1 -1
- package/out/fr/region.d.ts +18 -6
- package/out/fr/region.d.ts.map +1 -1
- package/out/fr/region.js +11 -5
- package/out/fr/region.js.map +1 -1
- package/out/fr/voie.d.ts +3 -1
- package/out/fr/voie.d.ts.map +1 -1
- package/out/fr/voie.js +3 -3
- package/out/fr/voie.js.map +1 -1
- package/out/gb/country.d.ts +18 -6
- package/out/gb/country.d.ts.map +1 -1
- package/out/gb/country.js +13 -5
- package/out/gb/country.js.map +1 -1
- package/out/gb/postcode-area.d.ts.map +1 -1
- package/out/gb/postcode-area.js +6 -2
- package/out/gb/postcode-area.js.map +1 -1
- package/out/gb/postcode.d.ts +3 -11
- package/out/gb/postcode.d.ts.map +1 -1
- package/out/gb/postcode.js +19 -3
- package/out/gb/postcode.js.map +1 -1
- package/out/gb/street-type.d.ts +3 -1
- package/out/gb/street-type.d.ts.map +1 -1
- package/out/gb/street-type.js +1 -1
- package/out/gb/street-type.js.map +1 -1
- package/out/index.d.ts.map +1 -1
- package/out/index.js.map +1 -1
- package/out/jp/address-unit.d.ts +6 -2
- package/out/jp/address-unit.d.ts.map +1 -1
- package/out/jp/address-unit.js +2 -2
- package/out/jp/address-unit.js.map +1 -1
- package/out/jp/postal-code.d.ts +6 -2
- package/out/jp/postal-code.d.ts.map +1 -1
- package/out/jp/postal-code.js +7 -3
- package/out/jp/postal-code.js.map +1 -1
- package/out/jp/prefecture.d.ts +24 -8
- package/out/jp/prefecture.d.ts.map +1 -1
- package/out/jp/prefecture.js +5 -3
- package/out/jp/prefecture.js.map +1 -1
- package/out/level-semantics.d.ts +48 -16
- package/out/level-semantics.d.ts.map +1 -1
- package/out/level-semantics.js +34 -12
- package/out/level-semantics.js.map +1 -1
- package/out/nz/delivery-service.d.ts +39 -13
- package/out/nz/delivery-service.d.ts.map +1 -1
- package/out/nz/delivery-service.js +16 -6
- package/out/nz/delivery-service.js.map +1 -1
- package/out/nz/postcode.d.ts +9 -3
- package/out/nz/postcode.d.ts.map +1 -1
- package/out/nz/postcode.js +9 -3
- package/out/nz/postcode.js.map +1 -1
- package/out/postcode-systems.d.ts +3 -1
- package/out/postcode-systems.d.ts.map +1 -1
- package/out/postcode-systems.js +1 -1
- package/out/postcode-systems.js.map +1 -1
- package/out/tools/build-country-surface-lexicon.js +26 -12
- package/out/tools/build-country-surface-lexicon.js.map +1 -1
- package/out/tools/export-country-surfaces.js.map +1 -1
- package/out/tools/generate-country-reference.d.ts +12 -4
- package/out/tools/generate-country-reference.d.ts.map +1 -1
- package/out/tools/generate-country-reference.js +10 -8
- package/out/tools/generate-country-reference.js.map +1 -1
- package/out/tools/generate-official-languages.d.ts +18 -6
- package/out/tools/generate-official-languages.d.ts.map +1 -1
- package/out/tools/generate-official-languages.js +9 -7
- package/out/tools/generate-official-languages.js.map +1 -1
- package/out/us/floor-designator.d.ts +15 -5
- package/out/us/floor-designator.d.ts.map +1 -1
- package/out/us/floor-designator.js +3 -1
- package/out/us/floor-designator.js.map +1 -1
- package/out/us/military-address.d.ts +36 -12
- package/out/us/military-address.d.ts.map +1 -1
- package/out/us/military-address.js +26 -15
- package/out/us/military-address.js.map +1 -1
- package/out/us/po-box.d.ts +9 -3
- package/out/us/po-box.d.ts.map +1 -1
- package/out/us/po-box.js +4 -2
- package/out/us/po-box.js.map +1 -1
- package/out/us/state.d.ts +6 -2
- package/out/us/state.d.ts.map +1 -1
- package/out/us/state.js +3 -1
- package/out/us/state.js.map +1 -1
- package/out/us/street-directional.d.ts +12 -3
- package/out/us/street-directional.d.ts.map +1 -1
- package/out/us/street-directional.js +8 -3
- package/out/us/street-directional.js.map +1 -1
- package/out/us/street-suffix.d.ts +24 -8
- package/out/us/street-suffix.d.ts.map +1 -1
- package/out/us/street-suffix.js +7 -3
- package/out/us/street-suffix.js.map +1 -1
- package/out/us/unit-designator.d.ts +24 -8
- package/out/us/unit-designator.d.ts.map +1 -1
- package/out/us/unit-designator.js +3 -1
- package/out/us/unit-designator.js.map +1 -1
- package/package.json +2 -2
- package/postcode-systems.ts +4 -2
- package/tools/build-country-surface-lexicon.ts +30 -12
- package/tools/export-country-surfaces.ts +2 -0
- package/tools/generate-country-reference.ts +27 -13
- package/tools/generate-official-languages.ts +27 -12
- package/us/floor-designator.ts +15 -5
- package/us/military-address.ts +53 -24
- package/us/po-box.ts +13 -5
- package/us/state.ts +6 -2
- package/us/street-directional.ts +14 -5
- package/us/street-suffix.ts +27 -9
- package/us/unit-designator.ts +25 -8
package/de/postleitzahl.ts
CHANGED
|
@@ -28,7 +28,9 @@ import type { Tagged } from "type-fest"
|
|
|
28
28
|
*/
|
|
29
29
|
export type Postleitzahl = Tagged<string, "Postleitzahl">
|
|
30
30
|
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* The PLZ shape: exactly five digits.
|
|
33
|
+
*/
|
|
32
34
|
export const PLZ_PATTERN = /^\d{5}$/
|
|
33
35
|
|
|
34
36
|
/**
|
|
@@ -42,20 +44,30 @@ export function normalizePLZ(raw: unknown): Postleitzahl | null {
|
|
|
42
44
|
return PLZ_PATTERN.test(s) ? (s as Postleitzahl) : null
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Type-predicate for a (normalized) German postal code.
|
|
49
|
+
*/
|
|
46
50
|
export function isPostleitzahl(input: unknown): input is Postleitzahl {
|
|
47
51
|
return typeof input === "string" && PLZ_PATTERN.test(input)
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* A PLZ Leitzone first digit.
|
|
56
|
+
*/
|
|
51
57
|
export type LeitzoneDigit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
|
52
58
|
|
|
53
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Per-Leitzone descriptor: the routing region and a few anchor cities (coarse, postal not admin).
|
|
61
|
+
*/
|
|
54
62
|
export interface LeitzoneInfo {
|
|
55
63
|
digit: LeitzoneDigit
|
|
56
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Coarse routing-region label.
|
|
66
|
+
*/
|
|
57
67
|
region: string
|
|
58
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Well-known anchor cities in the zone (illustrative, not exhaustive).
|
|
70
|
+
*/
|
|
59
71
|
cities: readonly string[]
|
|
60
72
|
}
|
|
61
73
|
|
|
@@ -81,7 +93,9 @@ export const PLZ_LEITZONEN = {
|
|
|
81
93
|
9: { digit: 9, region: "nördliches Bayern / Oberpfalz", cities: ["Nürnberg", "Würzburg", "Regensburg"] },
|
|
82
94
|
} as const satisfies Record<LeitzoneDigit, LeitzoneInfo>
|
|
83
95
|
|
|
84
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* The Leitzone of a PLZ (its first digit's routing region), or null if the input is not a PLZ.
|
|
98
|
+
*/
|
|
85
99
|
export function leitzoneOf(plz: unknown): LeitzoneInfo | null {
|
|
86
100
|
const normalized = normalizePLZ(plz)
|
|
87
101
|
|
package/de/street-type.ts
CHANGED
|
@@ -25,6 +25,15 @@
|
|
|
25
25
|
* spelling split). The full reference table, used for synthesis/expansion. For the "is this token part of a street"
|
|
26
26
|
* test, use {@link DE_STREET_SUFFIXES} / {@link isGermanStreetToken}, which exclude the place-name-colliding suffixes.
|
|
27
27
|
*/
|
|
28
|
+
/**
|
|
29
|
+
* Shortest token still plausible as a German street-type suffix (`weg`).
|
|
30
|
+
*/
|
|
31
|
+
const MIN_STREET_TYPE_LENGTH = 3
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* German street-type suffixes mapped to their surface variants, including the compounded forms that attach directly to
|
|
35
|
+
* the name (`-strasse`, `-str.`).
|
|
36
|
+
*/
|
|
28
37
|
export const DE_STREET_TYPE_VARIANTS = {
|
|
29
38
|
Straße: ["Str.", "Str", "Strasse"],
|
|
30
39
|
Weg: [],
|
|
@@ -41,7 +50,9 @@ export const DE_STREET_TYPE_VARIANTS = {
|
|
|
41
50
|
Twiete: [],
|
|
42
51
|
} as const satisfies Record<string, readonly string[]>
|
|
43
52
|
|
|
44
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* A canonical German street type (e.g. `Straße`, `Weg`, `Platz`).
|
|
55
|
+
*/
|
|
45
56
|
export type GermanStreetType = keyof typeof DE_STREET_TYPE_VARIANTS
|
|
46
57
|
|
|
47
58
|
/**
|
|
@@ -75,9 +86,9 @@ export const DE_STREET_SUFFIXES = [
|
|
|
75
86
|
*/
|
|
76
87
|
export function isGermanStreetToken(token: unknown): boolean {
|
|
77
88
|
if (typeof token !== "string") return false
|
|
78
|
-
const t = token.toLowerCase().
|
|
89
|
+
const t = token.toLowerCase().replaceAll(/[^a-zà-ÿß]/g, "")
|
|
79
90
|
|
|
80
|
-
if (t.length <
|
|
91
|
+
if (t.length < MIN_STREET_TYPE_LENGTH) return false
|
|
81
92
|
|
|
82
93
|
return DE_STREET_SUFFIXES.some((s) => t.endsWith(s))
|
|
83
94
|
}
|
package/fr/cedex.ts
CHANGED
|
@@ -14,16 +14,24 @@
|
|
|
14
14
|
* from here (the provenance-first discipline: one provenanced source).
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Matches a CEDEX phrase: the keyword plus an optional 1–2 digit office number.
|
|
19
|
+
*/
|
|
18
20
|
export const CEDEX_PATTERN = /\bCEDEX(?:\s+(\d{1,2}))?\b/i
|
|
19
21
|
|
|
20
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* A matched CEDEX phrase with its char range and optional office number.
|
|
24
|
+
*/
|
|
21
25
|
export interface CedexMatch {
|
|
22
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* The full matched phrase as it appears ("CEDEX 08", "Cedex").
|
|
28
|
+
*/
|
|
23
29
|
matched: string
|
|
24
30
|
start: number
|
|
25
31
|
end: number
|
|
26
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* The office number when present ("08"), undefined for bare CEDEX.
|
|
34
|
+
*/
|
|
27
35
|
office?: string
|
|
28
36
|
}
|
|
29
37
|
|
|
@@ -47,7 +55,9 @@ export function matchCedex(text: string): CedexMatch | null {
|
|
|
47
55
|
return match
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* True when the string is exactly a CEDEX phrase (the component-value validator).
|
|
60
|
+
*/
|
|
51
61
|
export function isCedex(input: unknown): boolean {
|
|
52
62
|
if (typeof input !== "string") return false
|
|
53
63
|
const m = input.trim().match(CEDEX_PATTERN)
|
package/fr/code-postal.ts
CHANGED
|
@@ -38,9 +38,17 @@ import { FR_REGIONS, type FrenchRegionInfo } from "./region.ts"
|
|
|
38
38
|
* @title Code postal
|
|
39
39
|
* @pattern ^\d{5}$
|
|
40
40
|
*/
|
|
41
|
+
/**
|
|
42
|
+
* Postcode at which Corsica splits between departments 2A (Corse-du-Sud) and 2B (Haute-Corse). Both share the 20xxx
|
|
43
|
+
* range, so the numeric boundary is the only way to tell them apart.
|
|
44
|
+
*/
|
|
45
|
+
const CORSICA_2A_2B_BOUNDARY = 20_200
|
|
46
|
+
|
|
41
47
|
export type CodePostal = Tagged<string, "CodePostal">
|
|
42
48
|
|
|
43
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* The code-postal shape: exactly five digits.
|
|
51
|
+
*/
|
|
44
52
|
export const CODE_POSTAL_PATTERN = /^\d{5}$/
|
|
45
53
|
|
|
46
54
|
/**
|
|
@@ -54,7 +62,9 @@ export function normalizeCodePostal(raw: unknown): CodePostal | null {
|
|
|
54
62
|
return CODE_POSTAL_PATTERN.test(s) ? (s as CodePostal) : null
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Type-predicate for a (normalized) French postal code.
|
|
67
|
+
*/
|
|
58
68
|
export function isCodePostal(input: unknown): input is CodePostal {
|
|
59
69
|
return typeof input === "string" && CODE_POSTAL_PATTERN.test(input)
|
|
60
70
|
}
|
|
@@ -76,7 +86,7 @@ export function departementOfCodePostal(codePostal: unknown): DepartementCode |
|
|
|
76
86
|
|
|
77
87
|
if (cp.startsWith("20")) {
|
|
78
88
|
// Corsica: prefix 20 covers both départements; the numeric value splits them.
|
|
79
|
-
return Number(cp) <
|
|
89
|
+
return Number(cp) < CORSICA_2A_2B_BOUNDARY ? "2A" : "2B"
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
if (cp.startsWith("97") || cp.startsWith("98")) {
|
|
@@ -85,12 +95,15 @@ export function departementOfCodePostal(codePostal: unknown): DepartementCode |
|
|
|
85
95
|
|
|
86
96
|
return departementInfo(dom) ? (dom as DepartementCode) : null
|
|
87
97
|
}
|
|
98
|
+
|
|
88
99
|
const dd = cp.slice(0, 2)
|
|
89
100
|
|
|
90
101
|
return departementInfo(dd) ? (dd as DepartementCode) : null
|
|
91
102
|
}
|
|
92
103
|
|
|
93
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* The full département record a postal code resolves to (name + région), or null.
|
|
106
|
+
*/
|
|
94
107
|
export function departementForCodePostal(codePostal: unknown): DepartementInfo | null {
|
|
95
108
|
return departementInfo(departementOfCodePostal(codePostal))
|
|
96
109
|
}
|
package/fr/departement.ts
CHANGED
|
@@ -13,17 +13,27 @@
|
|
|
13
13
|
|
|
14
14
|
import type { FrenchRegionCode } from "./region.ts"
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Per-département record: code (2-digit, or `2A`/`2B`, or 3-digit DOM) + name + its région.
|
|
18
|
+
*/
|
|
17
19
|
export interface DepartementInfo {
|
|
18
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Département code: `01`–`95` (metropolitan), `2A`/`2B` (Corsica), or `971`–`976` (overseas).
|
|
22
|
+
*/
|
|
19
23
|
code: string
|
|
20
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* French name (e.g. `Bouches-du-Rhône`).
|
|
26
|
+
*/
|
|
21
27
|
name: string
|
|
22
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* The ISO 3166-2:FR code of the région this département belongs to.
|
|
30
|
+
*/
|
|
23
31
|
region: FrenchRegionCode
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Département code → info. 96 metropolitan (incl. 2A/2B) + 5 overseas = 101.
|
|
36
|
+
*/
|
|
27
37
|
export const FR_DEPARTEMENTS = {
|
|
28
38
|
"01": { code: "01", name: "Ain", region: "ARA" },
|
|
29
39
|
"02": { code: "02", name: "Aisne", region: "HDF" },
|
|
@@ -128,7 +138,9 @@ export const FR_DEPARTEMENTS = {
|
|
|
128
138
|
"976": { code: "976", name: "Mayotte", region: "MAY" },
|
|
129
139
|
} as const satisfies Record<string, DepartementInfo>
|
|
130
140
|
|
|
131
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* A French département code (`01`–`95`, `2A`/`2B`, or `971`–`976`).
|
|
143
|
+
*/
|
|
132
144
|
export type DepartementCode = keyof typeof FR_DEPARTEMENTS
|
|
133
145
|
|
|
134
146
|
/**
|
package/fr/region.ts
CHANGED
|
@@ -13,15 +13,23 @@
|
|
|
13
13
|
* `code-postal.ts`).
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Per-region record: ISO 3166-2:FR code (sans `FR-` prefix) + French name.
|
|
18
|
+
*/
|
|
17
19
|
export interface FrenchRegionInfo {
|
|
18
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* ISO 3166-2:FR region code without the `FR-` prefix (e.g. `IDF` for `FR-IDF`).
|
|
22
|
+
*/
|
|
19
23
|
code: string
|
|
20
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* French name (e.g. `Île-de-France`).
|
|
26
|
+
*/
|
|
21
27
|
name: string
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* ISO 3166-2:FR region code → info, for all 18 régions (13 metropolitan + 5 overseas).
|
|
32
|
+
*/
|
|
25
33
|
export const FR_REGIONS = {
|
|
26
34
|
ARA: { code: "ARA", name: "Auvergne-Rhône-Alpes" },
|
|
27
35
|
BFC: { code: "BFC", name: "Bourgogne-Franche-Comté" },
|
|
@@ -43,23 +51,29 @@ export const FR_REGIONS = {
|
|
|
43
51
|
MAY: { code: "MAY", name: "Mayotte" },
|
|
44
52
|
} as const satisfies Record<string, FrenchRegionInfo>
|
|
45
53
|
|
|
46
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* An ISO 3166-2:FR region code (`ARA`, `IDF`, `PAC`, …).
|
|
56
|
+
*/
|
|
47
57
|
export type FrenchRegionCode = keyof typeof FR_REGIONS
|
|
48
58
|
|
|
49
59
|
const REGION_CODE_SET: ReadonlySet<string> = new Set(Object.keys(FR_REGIONS))
|
|
50
60
|
|
|
51
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Type-predicate for an ISO 3166-2:FR region code. Case-insensitive.
|
|
63
|
+
*/
|
|
52
64
|
export function isFrenchRegionCode(input: unknown): input is FrenchRegionCode {
|
|
53
65
|
return typeof input === "string" && REGION_CODE_SET.has(input.toUpperCase())
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
/**
|
|
68
|
+
/**
|
|
69
|
+
* Strip diacritics + lowercase so `Île-de-France`, `ile-de-france`, `Ile de France` all key alike.
|
|
70
|
+
*/
|
|
57
71
|
function foldName(s: string): string {
|
|
58
72
|
return s
|
|
59
73
|
.toLowerCase()
|
|
60
74
|
.normalize("NFD")
|
|
61
|
-
.
|
|
62
|
-
.
|
|
75
|
+
.replaceAll(/[\u0300-\u036F]/g, "")
|
|
76
|
+
.replaceAll(/[^a-z0-9]+/g, " ")
|
|
63
77
|
.trim()
|
|
64
78
|
}
|
|
65
79
|
|
package/fr/voie.ts
CHANGED
|
@@ -55,7 +55,9 @@ export const FR_VOIE_TYPES = {
|
|
|
55
55
|
lotissement: ["lot"],
|
|
56
56
|
} as const satisfies Record<string, readonly string[]>
|
|
57
57
|
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* A canonical French voie type (e.g. `rue`, `avenue`, `boulevard`).
|
|
60
|
+
*/
|
|
59
61
|
export type FrenchVoieType = keyof typeof FR_VOIE_TYPES
|
|
60
62
|
|
|
61
63
|
/**
|
|
@@ -67,7 +69,8 @@ const VOIE_TOKEN_SET: ReadonlySet<string> = (() => {
|
|
|
67
69
|
s
|
|
68
70
|
.toLowerCase()
|
|
69
71
|
.normalize("NFD")
|
|
70
|
-
.
|
|
72
|
+
.replaceAll(/[\u0300-\u036F]/g, "")
|
|
73
|
+
|
|
71
74
|
const out = new Set<string>()
|
|
72
75
|
|
|
73
76
|
for (const canonical of Object.keys(FR_VOIE_TYPES) as FrenchVoieType[]) {
|
|
@@ -88,11 +91,12 @@ const VOIE_TOKEN_SET: ReadonlySet<string> = (() => {
|
|
|
88
91
|
*/
|
|
89
92
|
export function isFrenchStreetWord(token: unknown): boolean {
|
|
90
93
|
if (typeof token !== "string") return false
|
|
94
|
+
|
|
91
95
|
const t = token
|
|
92
96
|
.toLowerCase()
|
|
93
97
|
.normalize("NFD")
|
|
94
|
-
.
|
|
95
|
-
.
|
|
98
|
+
.replaceAll(/[\u0300-\u036F]/g, "")
|
|
99
|
+
.replaceAll(/[^a-z-]/g, "")
|
|
96
100
|
|
|
97
101
|
return t.length > 0 && VOIE_TOKEN_SET.has(t)
|
|
98
102
|
}
|
package/gb/country.ts
CHANGED
|
@@ -14,15 +14,23 @@
|
|
|
14
14
|
* postcode is the thing that actually carries the geography.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Per-country record: ISO 3166-2:GB code (sans `GB-` prefix) + English name.
|
|
19
|
+
*/
|
|
18
20
|
export interface UkCountryInfo {
|
|
19
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* ISO 3166-2:GB country code without the `GB-` prefix (e.g. `ENG` for `GB-ENG`).
|
|
23
|
+
*/
|
|
20
24
|
code: string
|
|
21
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* English name (e.g. `Scotland`).
|
|
27
|
+
*/
|
|
22
28
|
name: string
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* ISO 3166-2:GB country code → info, for all four constituent countries.
|
|
33
|
+
*/
|
|
26
34
|
export const GB_COUNTRIES = {
|
|
27
35
|
ENG: { code: "ENG", name: "England" },
|
|
28
36
|
SCT: { code: "SCT", name: "Scotland" },
|
|
@@ -30,25 +38,33 @@ export const GB_COUNTRIES = {
|
|
|
30
38
|
NIR: { code: "NIR", name: "Northern Ireland" },
|
|
31
39
|
} as const satisfies Record<string, UkCountryInfo>
|
|
32
40
|
|
|
33
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* An ISO 3166-2:GB constituent-country code (`ENG`, `SCT`, `WLS`, `NIR`).
|
|
43
|
+
*/
|
|
34
44
|
export type UkCountryCode = keyof typeof GB_COUNTRIES
|
|
35
45
|
|
|
36
46
|
const COUNTRY_CODE_SET: ReadonlySet<string> = new Set(Object.keys(GB_COUNTRIES))
|
|
37
47
|
|
|
38
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Type-predicate for an ISO 3166-2:GB country code. Case-insensitive.
|
|
50
|
+
*/
|
|
39
51
|
export function isUkCountryCode(input: unknown): input is UkCountryCode {
|
|
40
52
|
return typeof input === "string" && COUNTRY_CODE_SET.has(input.toUpperCase())
|
|
41
53
|
}
|
|
42
54
|
|
|
43
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Lowercase + collapse non-alphanumerics so `Northern Ireland`, `northern-ireland` key alike.
|
|
57
|
+
*/
|
|
44
58
|
function foldName(s: string): string {
|
|
45
59
|
return s
|
|
46
60
|
.toLowerCase()
|
|
47
|
-
.
|
|
61
|
+
.replaceAll(/[^a-z0-9]+/g, " ")
|
|
48
62
|
.trim()
|
|
49
63
|
}
|
|
50
64
|
|
|
51
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Folded country name / code → ISO 3166-2:GB code, so a surface form maps regardless of casing.
|
|
67
|
+
*/
|
|
52
68
|
const COUNTRY_NAME_TO_CODE: ReadonlyMap<string, UkCountryCode> = (() => {
|
|
53
69
|
const out = new Map<string, UkCountryCode>()
|
|
54
70
|
|
package/gb/postcode-area.ts
CHANGED
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
|
|
25
25
|
import type { UkCountryCode } from "./country.ts"
|
|
26
26
|
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Northern Ireland is a single postcode area: BT (Belfast).
|
|
29
|
+
*/
|
|
28
30
|
const NORTHERN_IRELAND_AREAS = ["BT"] as const
|
|
29
31
|
|
|
30
32
|
/**
|
|
@@ -74,7 +76,9 @@ export const GB_POSTCODE_AREA_COUNTRY: Record<string, UkCountryCode> = {
|
|
|
74
76
|
...Object.fromEntries(WALES_AREAS.map((a) => [a, "WLS" as const])),
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* True when `area` looks like a valid postcode-area string: one or two ASCII letters.
|
|
81
|
+
*/
|
|
78
82
|
function isAreaShape(area: unknown): area is string {
|
|
79
83
|
return typeof area === "string" && /^[A-Z]{1,2}$/i.test(area)
|
|
80
84
|
}
|
package/gb/postcode.ts
CHANGED
|
@@ -39,6 +39,11 @@ import type { Tagged } from "type-fest"
|
|
|
39
39
|
* @title UK postcode
|
|
40
40
|
* @pattern ^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$
|
|
41
41
|
*/
|
|
42
|
+
/**
|
|
43
|
+
* Shortest valid UK postcode once spaces are stripped, e.g. `M11AE`.
|
|
44
|
+
*/
|
|
45
|
+
const MIN_POSTCODE_LENGTH = 5
|
|
46
|
+
|
|
42
47
|
export type Postcode = Tagged<string, "UkPostcode">
|
|
43
48
|
|
|
44
49
|
/**
|
|
@@ -57,15 +62,17 @@ export const UK_POSTCODE_PATTERN = /^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$/i
|
|
|
57
62
|
export function normalizeUkPostcode(raw: unknown): Postcode | null {
|
|
58
63
|
if (typeof raw !== "string") return null
|
|
59
64
|
// Drop all whitespace, uppercase, then re-insert the single canonical space before the inward 3.
|
|
60
|
-
const compact = raw.
|
|
65
|
+
const compact = raw.replaceAll(/\s+/g, "").toUpperCase()
|
|
61
66
|
|
|
62
|
-
if (compact.length <
|
|
67
|
+
if (compact.length < MIN_POSTCODE_LENGTH) return null
|
|
63
68
|
const spaced = `${compact.slice(0, -3)} ${compact.slice(-3)}`
|
|
64
69
|
|
|
65
70
|
return UK_POSTCODE_PATTERN.test(spaced) ? (spaced as Postcode) : null
|
|
66
71
|
}
|
|
67
72
|
|
|
68
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Type-predicate for a UK postcode surface form (space optional).
|
|
75
|
+
*/
|
|
69
76
|
export function isUkPostcode(input: unknown): input is Postcode {
|
|
70
77
|
return typeof input === "string" && UK_POSTCODE_PATTERN.test(input.trim())
|
|
71
78
|
}
|
package/gb/street-type.ts
CHANGED
|
@@ -72,7 +72,9 @@ export const GB_STREET_TYPES = [
|
|
|
72
72
|
"gait",
|
|
73
73
|
] as const
|
|
74
74
|
|
|
75
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* A canonical British thoroughfare word (e.g. `street`, `crescent`, `mews`).
|
|
77
|
+
*/
|
|
76
78
|
export type BritishStreetType = (typeof GB_STREET_TYPES)[number]
|
|
77
79
|
|
|
78
80
|
const STREET_TYPE_SET: ReadonlySet<string> = new Set(GB_STREET_TYPES)
|
|
@@ -84,7 +86,7 @@ const STREET_TYPE_SET: ReadonlySet<string> = new Set(GB_STREET_TYPES)
|
|
|
84
86
|
*/
|
|
85
87
|
export function isBritishStreetWord(token: unknown): boolean {
|
|
86
88
|
if (typeof token !== "string") return false
|
|
87
|
-
const t = token.toLowerCase().
|
|
89
|
+
const t = token.toLowerCase().replaceAll(/[^a-z]/g, "")
|
|
88
90
|
|
|
89
91
|
return t.length > 0 && STREET_TYPE_SET.has(t)
|
|
90
92
|
}
|
package/index.ts
CHANGED
package/jp/address-unit.ts
CHANGED
|
@@ -43,7 +43,9 @@
|
|
|
43
43
|
*/
|
|
44
44
|
export const JP_ADMIN_SUFFIXES = ["都", "道", "府", "県", "市", "区", "郡", "町", "村"] as const
|
|
45
45
|
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* A single administrative-area suffix kanji (`都`, `市`, `区`, …).
|
|
48
|
+
*/
|
|
47
49
|
export type JapaneseAdminSuffix = (typeof JP_ADMIN_SUFFIXES)[number]
|
|
48
50
|
|
|
49
51
|
/**
|
|
@@ -57,7 +59,9 @@ export type JapaneseAdminSuffix = (typeof JP_ADMIN_SUFFIXES)[number]
|
|
|
57
59
|
*/
|
|
58
60
|
export const JP_BLOCK_MARKERS = ["丁目", "番地", "番", "号"] as const
|
|
59
61
|
|
|
60
|
-
/**
|
|
62
|
+
/**
|
|
63
|
+
* A numbered-tail marker (`丁目`, `番地`, `番`, `号`).
|
|
64
|
+
*/
|
|
61
65
|
export type JapaneseBlockMarker = (typeof JP_BLOCK_MARKERS)[number]
|
|
62
66
|
|
|
63
67
|
const ADMIN_SUFFIX_SET: ReadonlySet<string> = new Set(JP_ADMIN_SUFFIXES)
|
|
@@ -78,10 +82,10 @@ export function isJapaneseAdminSuffix(ch: unknown): ch is JapaneseAdminSuffix {
|
|
|
78
82
|
* than clipped to `北海` — mirroring the same carve-out in `prefecture.ts`.
|
|
79
83
|
*/
|
|
80
84
|
export function stripAdminSuffix(name: string): string {
|
|
81
|
-
if (typeof name !== "string" || name.length
|
|
85
|
+
if (typeof name !== "string" || !name.length) return name
|
|
82
86
|
|
|
83
87
|
if (name === "北海道") return name
|
|
84
|
-
const last = name
|
|
88
|
+
const last = name.at(-1)!
|
|
85
89
|
|
|
86
90
|
return ADMIN_SUFFIX_SET.has(last) ? name.slice(0, -1) : name
|
|
87
91
|
}
|
package/jp/postal-code.ts
CHANGED
|
@@ -36,7 +36,9 @@ import type { Tagged } from "type-fest"
|
|
|
36
36
|
*/
|
|
37
37
|
export type PostalCode = Tagged<string, "JpPostalCode">
|
|
38
38
|
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* The postal-code shape: `NNN-NNNN`, the hyphen optional on input (`1000001` or `100-0001`).
|
|
41
|
+
*/
|
|
40
42
|
export const JP_POSTAL_CODE_PATTERN = /^\d{3}-?\d{4}$/
|
|
41
43
|
|
|
42
44
|
/**
|
|
@@ -47,14 +49,16 @@ export const JP_POSTAL_CODE_PATTERN = /^\d{3}-?\d{4}$/
|
|
|
47
49
|
export function normalizeJpPostalCode(raw: unknown): PostalCode | null {
|
|
48
50
|
if (typeof raw !== "string") return null
|
|
49
51
|
// Drop the 〒 mark and all whitespace, then keep only the digits.
|
|
50
|
-
const digits = raw.
|
|
52
|
+
const digits = raw.replaceAll("〒", "").replaceAll(/\s+/g, "").replaceAll("-", "")
|
|
51
53
|
|
|
52
54
|
if (!/^\d{7}$/.test(digits)) return null
|
|
53
55
|
|
|
54
56
|
return `${digits.slice(0, 3)}-${digits.slice(3)}` as PostalCode
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Type-predicate for a Japanese postal code (hyphen optional, `100-0001` or `1000001`).
|
|
61
|
+
*/
|
|
58
62
|
export function isJpPostalCode(input: unknown): input is PostalCode {
|
|
59
63
|
return typeof input === "string" && JP_POSTAL_CODE_PATTERN.test(input)
|
|
60
64
|
}
|
package/jp/prefecture.ts
CHANGED
|
@@ -23,18 +23,30 @@
|
|
|
23
23
|
* `postal-code.ts` for why the postcode is nonetheless the single most reliable anchor.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* A to/dō/fu/ken classification of the top-level admin unit.
|
|
28
|
+
*/
|
|
27
29
|
export type JapanesePrefectureType = "to" | "do" | "fu" | "ken"
|
|
28
30
|
|
|
29
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Per-prefecture record: ISO 3166-2:JP numeric code + kanji + romaji + to/dō/fu/ken type.
|
|
33
|
+
*/
|
|
30
34
|
export interface JapanesePrefectureInfo {
|
|
31
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* ISO 3166-2:JP code without the `JP-` prefix: a two-digit numeric string (`"13"` for `JP-13`).
|
|
37
|
+
*/
|
|
32
38
|
code: string
|
|
33
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Kanji name, including its 都/道/府/県 suffix (e.g. `東京都`).
|
|
41
|
+
*/
|
|
34
42
|
kanji: string
|
|
35
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Macron-free romaji name, suffix-less (e.g. `Tokyo`).
|
|
45
|
+
*/
|
|
36
46
|
romaji: string
|
|
37
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Which of the four flavours of top-level unit this is.
|
|
49
|
+
*/
|
|
38
50
|
type: JapanesePrefectureType
|
|
39
51
|
}
|
|
40
52
|
|
|
@@ -92,12 +104,16 @@ export const JP_PREFECTURES = {
|
|
|
92
104
|
"47": { code: "47", kanji: "沖縄県", romaji: "Okinawa", type: "ken" },
|
|
93
105
|
} as const satisfies Record<string, JapanesePrefectureInfo>
|
|
94
106
|
|
|
95
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* An ISO 3166-2:JP prefecture code (`"01"`..`"47"`).
|
|
109
|
+
*/
|
|
96
110
|
export type JapanesePrefectureCode = keyof typeof JP_PREFECTURES
|
|
97
111
|
|
|
98
112
|
const PREFECTURE_CODE_SET: ReadonlySet<string> = new Set(Object.keys(JP_PREFECTURES))
|
|
99
113
|
|
|
100
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Type-predicate for an ISO 3166-2:JP prefecture code (`"01"`..`"47"`).
|
|
116
|
+
*/
|
|
101
117
|
export function isJapanesePrefectureCode(input: unknown): input is JapanesePrefectureCode {
|
|
102
118
|
return typeof input === "string" && PREFECTURE_CODE_SET.has(input)
|
|
103
119
|
}
|
|
@@ -117,11 +133,12 @@ function foldRomaji(s: string): string {
|
|
|
117
133
|
const lowered = s
|
|
118
134
|
.toLowerCase()
|
|
119
135
|
.normalize("NFD")
|
|
120
|
-
.
|
|
136
|
+
.replaceAll(/[\u0300-\u036F]/g, "")
|
|
137
|
+
|
|
121
138
|
// Strip an appended type-suffix only when a separator (hyphen / space / middle dot) precedes it.
|
|
122
139
|
const desuffixed = lowered.replace(/[-\s·][\s]*(to|do|fu|ken)$/, "")
|
|
123
140
|
|
|
124
|
-
return desuffixed.
|
|
141
|
+
return desuffixed.replaceAll(/[^a-z]/g, "")
|
|
125
142
|
}
|
|
126
143
|
|
|
127
144
|
/**
|