@mailwoman/codex 7.2.0 → 7.3.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 +68 -0
- package/au/delivery-service.ts +179 -0
- package/au/index.ts +15 -0
- package/au/level-designator.ts +209 -0
- package/au/postcode.ts +51 -0
- package/au/state.ts +35 -0
- package/ca/index.ts +12 -0
- package/ca/postal-code.ts +121 -0
- package/ca/province.ts +99 -0
- package/ca/street-type.ts +167 -0
- package/country/codes.ts +534 -0
- package/country/country.ts +125 -0
- package/country/index.ts +15 -0
- package/country/names.ts +274 -0
- package/country/official-languages.ts +397 -0
- package/country/reference-data.ts +267 -0
- package/country/reference.ts +47 -0
- package/country/subdivision.ts +93 -0
- package/de/bundesland.ts +102 -0
- package/de/index.ts +12 -0
- package/de/postleitzahl.ts +91 -0
- package/de/street-type.ts +83 -0
- package/fr/cedex.ts +56 -0
- package/fr/code-postal.ts +105 -0
- package/fr/departement.ts +142 -0
- package/fr/index.ts +14 -0
- package/fr/region.ts +93 -0
- package/fr/voie.ts +98 -0
- package/gb/country.ts +74 -0
- package/gb/index.ts +14 -0
- package/gb/postcode-area.ts +107 -0
- package/gb/postcode.ts +109 -0
- package/gb/street-type.ts +90 -0
- package/index.ts +38 -0
- package/jp/address-unit.ts +87 -0
- package/jp/index.ts +13 -0
- package/jp/postal-code.ts +93 -0
- package/jp/prefecture.ts +173 -0
- package/level-semantics.ts +623 -0
- package/nz/delivery-service.ts +211 -0
- package/nz/index.ts +12 -0
- package/nz/postcode.ts +42 -0
- package/out/country/index.d.ts +1 -0
- package/out/country/index.d.ts.map +1 -1
- package/out/country/index.js +1 -0
- package/out/country/index.js.map +1 -1
- package/out/country/subdivision.d.ts +44 -0
- package/out/country/subdivision.d.ts.map +1 -0
- package/out/country/subdivision.js +76 -0
- package/out/country/subdivision.js.map +1 -0
- package/package.json +81 -37
- package/postcode-systems.ts +68 -0
- package/tools/build-country-surface-lexicon.ts +166 -0
- package/tools/export-country-surfaces.ts +46 -0
- package/tools/generate-country-reference.ts +153 -0
- package/tools/generate-official-languages.ts +188 -0
- package/tools/index.ts +12 -0
- package/us/floor-designator.ts +119 -0
- package/us/index.ts +19 -0
- package/us/military-address.ts +199 -0
- package/us/po-box.ts +82 -0
- package/us/state.ts +156 -0
- package/us/street-directional.ts +220 -0
- package/us/street-suffix.ts +345 -0
- package/us/unit-designator.ts +223 -0
- package/us/zipcode.ts +212 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* NZ Post delivery-service types — the second half of the Commonwealth po_box vocabulary: `PO Box
|
|
7
|
+
* 24999`, `Private Bag 106999`, `CMB B99`, plus the identifier-less counter services, plus the
|
|
8
|
+
* colloquial "Private Box" alias documented here for recognition with its 'invalid' citation.
|
|
9
|
+
*
|
|
10
|
+
* Sourcing (accessed 2026-06-11):
|
|
11
|
+
*
|
|
12
|
+
* - NZ Post Address Standards (ADV358, October 2021 edition hosted on nzpost.co.nz) is the
|
|
13
|
+
* authoritative document. Verbatim: "The Delivery Service Type is mandatory. It may be PO
|
|
14
|
+
* Box, Private Bag, CMB, Response Bag, Counter Delivery or Poste Restante." Its Delivery
|
|
15
|
+
* Service Elements table gives the descriptions reproduced in
|
|
16
|
+
* {@link NZ_DELIVERY_SERVICE_TYPES}, and the identifier rules: "The Delivery Service
|
|
17
|
+
* Identifier must have no leading zeros and no spaces, separators or other punctuation"; "The
|
|
18
|
+
* Delivery Service Identifier is not used for Counter Delivery or Poste Restante. It is also
|
|
19
|
+
* not used for Private Bags that do not have an identifier allocated by New Zealand Post".
|
|
20
|
+
* Examples: `PO Box 24999`, `Private Bag 106999`, `Response Bag 500999`, `CMB B99`, `Counter
|
|
21
|
+
* Delivery`, `Poste Restante`. The standard's incorrect-form examples show `P O Box 4 099`
|
|
22
|
+
* and `PB 39990` as wrong — `PB` is a common error, not a designator, so it is NOT in this
|
|
23
|
+
* table.
|
|
24
|
+
* - The live addressing-standards page repeats the format rules: "PO Box and Private Bag numbers are
|
|
25
|
+
* space-free (eg. 'PO Box 23226', not 'PO Box 23 226')", "'PO' is space-free … and
|
|
26
|
+
* punctuation-free".
|
|
27
|
+
*
|
|
28
|
+
* All six types are CURRENT in the October 2021 ADV358 (including CMB — no legacy flag is needed
|
|
29
|
+
* for the NZ slice).
|
|
30
|
+
*
|
|
31
|
+
* **"Private Box" — colloquial alias, officially invalid:** ADV358 does not list "Private Box" as a
|
|
32
|
+
* Delivery Service Type and NZ Post's live standards pages (addressing-standards, how-to-address-
|
|
33
|
+
* mail, accessed 2026-06-11) name only the six types above. However, real NZ mail and the postal
|
|
34
|
+
* arena's gold rows carry "Private Box" as a colloquial synonym for a numbered PO Box (same
|
|
35
|
+
* numbered format, different label). Operator ruling 2026-06-11: "Private Box = recognize-as-used
|
|
36
|
+
* — codex documents it unofficial/colloquial WITH the 'officially invalid' citation; the shard
|
|
37
|
+
* teaches recognition (validity and recognition are separate disciplines, same as the AU legacy
|
|
38
|
+
* flags)." This is encoded in {@link NZ_PRIVATE_BOX_ALIAS} — a distinct export from
|
|
39
|
+
* {@link NZ_DELIVERY_SERVICE_TYPES} so callers can choose whether to include the unofficial form.
|
|
40
|
+
* @see {@link https://www.nzpost.co.nz/sites/nz/files/2021-10/adv358-address-standards.pdf NZ Post Address Standards (ADV358, Oct 2021)}
|
|
41
|
+
* @see {@link https://www.nzpost.co.nz/business/shipping-in-nz/addressing-standards NZ Post addressing standards}
|
|
42
|
+
* @see {@link https://www.nzpost.co.nz/personal/sending-in-nz/how-to-address-mail NZ Post — how to address mail}
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/** Identifier requirement per ADV358's Delivery Service Elements rules. */
|
|
46
|
+
export type NzIdentifierRule = "required-if-allocated" | "optional" | "not-used"
|
|
47
|
+
|
|
48
|
+
/** One Delivery Service Type row from ADV358. */
|
|
49
|
+
export interface NzDeliveryServiceType {
|
|
50
|
+
/** The Delivery Service Type, verbatim casing per ADV358 ("PO Box", "Private Bag", "CMB"). */
|
|
51
|
+
type: string
|
|
52
|
+
/** The ADV358 description, verbatim. */
|
|
53
|
+
description: string
|
|
54
|
+
/**
|
|
55
|
+
* Identifier rule: PO Box/Response Bag/CMB identifiers are mandatory if allocated; Private Bag may legitimately have
|
|
56
|
+
* none ("not used … for Private Bags that do not have an identifier allocated by New Zealand Post"); Counter Delivery
|
|
57
|
+
* and Poste Restante never carry one.
|
|
58
|
+
*/
|
|
59
|
+
identifier: NzIdentifierRule
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The six Delivery Service Types, verbatim from ADV358 (see the module header). */
|
|
63
|
+
export const NZ_DELIVERY_SERVICE_TYPES = [
|
|
64
|
+
{ type: "PO Box", description: "Post Box, PO Box", identifier: "required-if-allocated" },
|
|
65
|
+
{ type: "Private Bag", description: "Private Bag", identifier: "optional" },
|
|
66
|
+
{ type: "Response Bag", description: "Response Bag (used for competitions)", identifier: "required-if-allocated" },
|
|
67
|
+
{
|
|
68
|
+
type: "CMB",
|
|
69
|
+
description: "Community Mail Box in postal outlet or on a thoroughfare",
|
|
70
|
+
identifier: "required-if-allocated",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "Counter Delivery",
|
|
74
|
+
description: "Hold for Counter Delivery collection - domestic mail",
|
|
75
|
+
identifier: "not-used",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: "Poste Restante",
|
|
79
|
+
description: "Hold for Poste Restante collection - international mail",
|
|
80
|
+
identifier: "not-used",
|
|
81
|
+
},
|
|
82
|
+
] as const satisfies readonly NzDeliveryServiceType[]
|
|
83
|
+
|
|
84
|
+
/** A canonical NZ Delivery Service Type. */
|
|
85
|
+
export type NzDeliveryServiceTypeName = (typeof NZ_DELIVERY_SERVICE_TYPES)[number]["type"]
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Metadata for the colloquial "Private Box" alias (see the module header and operator ruling 2026-06-11). Kept separate
|
|
89
|
+
* from {@link NZ_DELIVERY_SERVICE_TYPES} because it is NOT a valid ADV358 Delivery Service Type — recognition and
|
|
90
|
+
* validity are separate concerns.
|
|
91
|
+
*
|
|
92
|
+
* Sourcing: ADV358 (Oct 2021) omits "Private Box" from its Delivery Service Type list entirely. NZ Post's live
|
|
93
|
+
* standards pages (nzpost.co.nz/business/shipping-in-nz/addressing-standards and
|
|
94
|
+
* nzpost.co.nz/personal/sending-in-nz/how-to-address-mail, accessed 2026-06-11) do not list it as a valid type. Real NZ
|
|
95
|
+
* mail and the postal arena's gold rows nonetheless carry it as a colloquial synonym for a numbered PO Box. Operator
|
|
96
|
+
* ruling 2026-06-11 authorizes its inclusion here for recognition only, with this citation; corpus synthesis should
|
|
97
|
+
* treat it as a non-prescriptive form.
|
|
98
|
+
*/
|
|
99
|
+
export const NZ_PRIVATE_BOX_ALIAS = {
|
|
100
|
+
/** The surface form as it appears on real mail and in postal-arena gold rows. */
|
|
101
|
+
type: "Private Box",
|
|
102
|
+
/**
|
|
103
|
+
* The description of validity status — NOT a valid ADV358 Delivery Service Type; a colloquial NZ synonym for a
|
|
104
|
+
* numbered PO Box (same format as "PO Box <number>").
|
|
105
|
+
*/
|
|
106
|
+
description: "Colloquial NZ synonym for a numbered PO Box — NOT a valid ADV358 Delivery Service Type",
|
|
107
|
+
/** Identifier rule mirrors PO Box: a number is expected when the alias is used with one. */
|
|
108
|
+
identifier: "required-if-allocated" satisfies NzIdentifierRule,
|
|
109
|
+
/**
|
|
110
|
+
* True — this form is NOT valid per ADV358 or NZ Post's live standards pages (accessed 2026-06-11).
|
|
111
|
+
*/
|
|
112
|
+
officiallyInvalid: true,
|
|
113
|
+
} as const
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Per-type surface patterns (designator phrase only). Recognition is deliberately wider than the prescriptive standard
|
|
117
|
+
* — mail in the wild writes "P.O. Box" even though ADV358 says `PO` is punctuation-free — but it does NOT admit forms
|
|
118
|
+
* the standard names as errors of TYPE (`PB`).
|
|
119
|
+
*
|
|
120
|
+
* The colloquial "Private Box" alias is included for recognition (see {@link NZ_PRIVATE_BOX_ALIAS} and operator ruling
|
|
121
|
+
* 2026-06-11); it maps to a distinct synthetic type string so callers can distinguish it from the ADV358 types.
|
|
122
|
+
*/
|
|
123
|
+
const TYPE_PATTERNS: ReadonlyArray<readonly [NzDeliveryServiceTypeName | "Private Box", string]> = [
|
|
124
|
+
["PO Box", String.raw`p\.?\s*o\.?\s*box|post\s+box`],
|
|
125
|
+
["Private Bag", String.raw`private\s+bag`],
|
|
126
|
+
["Private Box", String.raw`private\s+box`],
|
|
127
|
+
["Response Bag", String.raw`response\s+bag`],
|
|
128
|
+
["CMB", String.raw`community\s+mail\s+box|cmb`],
|
|
129
|
+
["Counter Delivery", String.raw`counter\s+delivery`],
|
|
130
|
+
["Poste Restante", String.raw`poste\s+restante`],
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
/** Extended type name union including the colloquial alias recognized for parsing. */
|
|
134
|
+
export type NzDeliveryServiceMatchTypeName = NzDeliveryServiceTypeName | "Private Box"
|
|
135
|
+
|
|
136
|
+
const IDENTIFIER_RULES = new Map<NzDeliveryServiceMatchTypeName, NzIdentifierRule>([
|
|
137
|
+
...NZ_DELIVERY_SERVICE_TYPES.map((t) => [t.type, t.identifier] as const),
|
|
138
|
+
// "Private Box" mirrors PO Box identifier rules (required-if-allocated) per the alias metadata.
|
|
139
|
+
["Private Box", NZ_PRIVATE_BOX_ALIAS.identifier],
|
|
140
|
+
])
|
|
141
|
+
|
|
142
|
+
// One anchored regex per type. The identifier shape follows ADV358 (alphanumeric, no spaces or
|
|
143
|
+
// separators — `24999`, `B99`); the identifier-less counter services take no tail at all.
|
|
144
|
+
const MATCHERS: ReadonlyArray<{ type: NzDeliveryServiceMatchTypeName; re: RegExp }> = TYPE_PATTERNS.map(
|
|
145
|
+
([type, src]) => {
|
|
146
|
+
const rule = IDENTIFIER_RULES.get(type)!
|
|
147
|
+
const tail = rule === "not-used" ? "" : String.raw`(?:\s+([\dA-Za-z]+))${rule === "optional" ? "?" : ""}`
|
|
148
|
+
|
|
149
|
+
return { type, re: new RegExp(String.raw`^\s*(${src})${tail}\s*$`, "i") }
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
/** Result of an NZ delivery-service parse. */
|
|
154
|
+
export interface NzDeliveryServiceMatch {
|
|
155
|
+
/** The designator phrase as it appeared ("PO Box", "private bag", "Private Box"). */
|
|
156
|
+
matched: string
|
|
157
|
+
/**
|
|
158
|
+
* The canonical Delivery Service Type or recognized alias ("PO Box", "Private Bag", "CMB", …, "Private Box"). When
|
|
159
|
+
* `type` is "Private Box", `colloquial` is true and `officiallyInvalid` is true — the form is not a valid ADV358
|
|
160
|
+
* type.
|
|
161
|
+
*/
|
|
162
|
+
type: NzDeliveryServiceMatchTypeName
|
|
163
|
+
/** The Delivery Service Identifier when present ("24999", "B99"). */
|
|
164
|
+
id?: string
|
|
165
|
+
/**
|
|
166
|
+
* True when the matched form is the colloquial "Private Box" alias — not a valid ADV358 Delivery Service Type. Absent
|
|
167
|
+
* (undefined) for all standard ADV358 types.
|
|
168
|
+
*/
|
|
169
|
+
colloquial?: true
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* If `input` is a standalone NZ delivery-service phrase ("PO Box 24999", "Private Bag 106999", "CMB B99", bare "Private
|
|
174
|
+
* Bag", "Counter Delivery", "Private Box 102"), return the canonical type and identifier. Null otherwise — including
|
|
175
|
+
* for "PB 39990" (an error of form per ADV358).
|
|
176
|
+
*
|
|
177
|
+
* When `type` is "Private Box", the result carries `colloquial: true` — indicating the colloquial alias (not an ADV358
|
|
178
|
+
* Delivery Service Type; see {@link NZ_PRIVATE_BOX_ALIAS} and operator ruling 2026-06-11). Callers that want only
|
|
179
|
+
* formally-valid ADV358 types should check `!result.colloquial`.
|
|
180
|
+
*/
|
|
181
|
+
export function matchNzDeliveryService(input: unknown): NzDeliveryServiceMatch | null {
|
|
182
|
+
if (typeof input !== "string") return null
|
|
183
|
+
|
|
184
|
+
for (const { type, re } of MATCHERS) {
|
|
185
|
+
const m = re.exec(input)
|
|
186
|
+
|
|
187
|
+
if (!m) continue
|
|
188
|
+
const colloquial = type === "Private Box" ? ({ colloquial: true } as const) : {}
|
|
189
|
+
|
|
190
|
+
return { matched: m[1]!.trim(), type, ...(m[2] ? { id: m[2] } : {}), ...colloquial }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Type-predicate: does the input look like a standalone NZ delivery-service address line? */
|
|
197
|
+
export function isNzDeliveryService(input: unknown): boolean {
|
|
198
|
+
return matchNzDeliveryService(input) !== null
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Normalize a recognized phrase to the ADV358 form (`"p.o. box 24999"` → `"PO Box 24999"`). Returns the input unchanged
|
|
203
|
+
* if it isn't a delivery-service phrase.
|
|
204
|
+
*/
|
|
205
|
+
export function normalizeNzDeliveryService(input: string): string {
|
|
206
|
+
const m = matchNzDeliveryService(input)
|
|
207
|
+
|
|
208
|
+
if (!m) return input
|
|
209
|
+
|
|
210
|
+
return m.id ? `${m.type} ${m.id.toUpperCase()}` : m.type
|
|
211
|
+
}
|
package/nz/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The New Zealand address system (NZ Post / ISO 3166-2:NZ; ADV358 Address Standards):
|
|
7
|
+
* delivery-service types (PO Box, Private Bag, CMB, Response Bag, Counter Delivery, Poste
|
|
8
|
+
* Restante) and the 4-digit postcode. NZ addresses carry no state/region line.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export * from "./delivery-service.ts"
|
|
12
|
+
export * from "./postcode.ts"
|
package/nz/postcode.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* New Zealand postcodes: four digits, after the town/city on the last line (`Timaru 7942`).
|
|
7
|
+
* Verbatim from NZ Post's Address Standards (ADV358, Oct 2021, accessed 2026-06-11): "Postcode is
|
|
8
|
+
* mandatory and is four digits. It follows the city or town." Note NZ has no state/region line —
|
|
9
|
+
* "The province, region, district or territory is not to be used."
|
|
10
|
+
*
|
|
11
|
+
* The shape collides with Australia's (also 4 digits) — `candidateSystemsForPostcode` returns both,
|
|
12
|
+
* by design (shape test, not membership test).
|
|
13
|
+
* @see {@link https://www.nzpost.co.nz/sites/nz/files/2021-10/adv358-address-standards.pdf NZ Post Address Standards (ADV358, Oct 2021)}
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Tagged } from "type-fest"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A New Zealand postcode: four digits.
|
|
20
|
+
*
|
|
21
|
+
* @category Postal
|
|
22
|
+
* @type string
|
|
23
|
+
* @title New Zealand postcode
|
|
24
|
+
* @pattern ^\d{4}$
|
|
25
|
+
*/
|
|
26
|
+
export type NzPostcode = Tagged<string, "NzPostcode">
|
|
27
|
+
|
|
28
|
+
/** The NZ postcode shape: exactly four digits. */
|
|
29
|
+
export const NZ_POSTCODE_PATTERN = /^\d{4}$/
|
|
30
|
+
|
|
31
|
+
/** Normalize a postcode surface form (trim only — NZ has no country-prefix courtesy form). */
|
|
32
|
+
export function normalizeNzPostcode(raw: unknown): NzPostcode | null {
|
|
33
|
+
if (typeof raw !== "string") return null
|
|
34
|
+
const s = raw.trim()
|
|
35
|
+
|
|
36
|
+
return NZ_POSTCODE_PATTERN.test(s) ? (s as NzPostcode) : null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Type-predicate for a (normalized) New Zealand postcode. */
|
|
40
|
+
export function isNzPostcode(input: unknown): input is NzPostcode {
|
|
41
|
+
return typeof input === "string" && NZ_POSTCODE_PATTERN.test(input)
|
|
42
|
+
}
|
package/out/country/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../country/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../country/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA"}
|
package/out/country/index.js
CHANGED
package/out/country/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../country/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../country/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* ISO 3166-2 subdivision → country reference, the cross-country complement to `country.ts`'s
|
|
7
|
+
* ISO 3166-1 `matchCountry`. A subdivision token ("QC", "Ontario", "Illinois") names a first-level
|
|
8
|
+
* admin unit whose COUNTRY is the piece the resolver needs when a region qualifier is the only
|
|
9
|
+
* signal that the locale-inferred default country is wrong ("Montreal QC" under a US locale).
|
|
10
|
+
*
|
|
11
|
+
* Scope is deliberately minimal: the two subdivision systems whose two-letter codes people write
|
|
12
|
+
* ON THE ADDRESS LINE and whose homonymous localities collide across the border — US states
|
|
13
|
+
* (`us/state.ts`) and Canadian provinces (`ca/province.ts`). Both directions are covered: the ISO
|
|
14
|
+
* code (`QC` → Quebec) and the full name (`Quebec` / `Québec` → the `QC` record), so a resolver
|
|
15
|
+
* can expand the abbreviation the gazetteer FTS index lacks ("QC" is not an alt-name of Québec)
|
|
16
|
+
* into the full name it does carry. This is a soft prior, not a routing decision — the gazetteer
|
|
17
|
+
* still does the geographic confirmation (per the registry-backed-soft-prior doctrine).
|
|
18
|
+
*
|
|
19
|
+
* The US and Canadian code sets are disjoint (no two-letter code, and no full name, collides
|
|
20
|
+
* between the two), so the combined lookup below is unambiguous. `CA` resolves to California the
|
|
21
|
+
* US state (Canada's provinces carry no `CA` subdivision code), never to Canada the country —
|
|
22
|
+
* country recognition stays with `matchCountry`.
|
|
23
|
+
*
|
|
24
|
+
* Source: the underlying `US_STATE_BY_ABBREVIATION` (USPS Publication 28, Appendix B) and
|
|
25
|
+
* `CA_PROVINCES` (ISO 3166-2:CA) tables. No new provenance is introduced here — this module only
|
|
26
|
+
* re-keys those two existing tables into one subdivision→country view.
|
|
27
|
+
*/
|
|
28
|
+
/** A resolved subdivision: its ISO 3166-2 code (sans country prefix), canonical English name, and ISO 3166-1 country. */
|
|
29
|
+
export interface SubdivisionMatch {
|
|
30
|
+
/** ISO 3166-2 subdivision code without the country prefix (e.g. `QC` for `CA-QC`, `IL` for `US-IL`). */
|
|
31
|
+
code: string;
|
|
32
|
+
/** Canonical English name (e.g. `Quebec`, `Illinois`). */
|
|
33
|
+
name: string;
|
|
34
|
+
/** ISO 3166-1 alpha-2 country the subdivision belongs to (`CA`, `US`). */
|
|
35
|
+
country: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a first-level subdivision surface form (ISO 3166-2 code, English name, or co-official French name for CA;
|
|
39
|
+
* accents optional) to its `{ code, name, country }`. Case- and diacritic-insensitive. Returns null for anything that
|
|
40
|
+
* isn't a US state or Canadian province/territory — including bare country tokens (use {@link matchCountry} for
|
|
41
|
+
* those).
|
|
42
|
+
*/
|
|
43
|
+
export declare function matchSubdivision(token: string | null | undefined): SubdivisionMatch | null;
|
|
44
|
+
//# sourceMappingURL=subdivision.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subdivision.d.ts","sourceRoot":"","sources":["../../country/subdivision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,yHAAyH;AACzH,MAAM,WAAW,gBAAgB;IAChC,wGAAwG;IACxG,IAAI,EAAE,MAAM,CAAA;IACZ,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAA;IACZ,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAA;CACf;AA2CD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,gBAAgB,GAAG,IAAI,CAI1F"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* ISO 3166-2 subdivision → country reference, the cross-country complement to `country.ts`'s
|
|
7
|
+
* ISO 3166-1 `matchCountry`. A subdivision token ("QC", "Ontario", "Illinois") names a first-level
|
|
8
|
+
* admin unit whose COUNTRY is the piece the resolver needs when a region qualifier is the only
|
|
9
|
+
* signal that the locale-inferred default country is wrong ("Montreal QC" under a US locale).
|
|
10
|
+
*
|
|
11
|
+
* Scope is deliberately minimal: the two subdivision systems whose two-letter codes people write
|
|
12
|
+
* ON THE ADDRESS LINE and whose homonymous localities collide across the border — US states
|
|
13
|
+
* (`us/state.ts`) and Canadian provinces (`ca/province.ts`). Both directions are covered: the ISO
|
|
14
|
+
* code (`QC` → Quebec) and the full name (`Quebec` / `Québec` → the `QC` record), so a resolver
|
|
15
|
+
* can expand the abbreviation the gazetteer FTS index lacks ("QC" is not an alt-name of Québec)
|
|
16
|
+
* into the full name it does carry. This is a soft prior, not a routing decision — the gazetteer
|
|
17
|
+
* still does the geographic confirmation (per the registry-backed-soft-prior doctrine).
|
|
18
|
+
*
|
|
19
|
+
* The US and Canadian code sets are disjoint (no two-letter code, and no full name, collides
|
|
20
|
+
* between the two), so the combined lookup below is unambiguous. `CA` resolves to California the
|
|
21
|
+
* US state (Canada's provinces carry no `CA` subdivision code), never to Canada the country —
|
|
22
|
+
* country recognition stays with `matchCountry`.
|
|
23
|
+
*
|
|
24
|
+
* Source: the underlying `US_STATE_BY_ABBREVIATION` (USPS Publication 28, Appendix B) and
|
|
25
|
+
* `CA_PROVINCES` (ISO 3166-2:CA) tables. No new provenance is introduced here — this module only
|
|
26
|
+
* re-keys those two existing tables into one subdivision→country view.
|
|
27
|
+
*/
|
|
28
|
+
import { CA_PROVINCES } from "../ca/province.js";
|
|
29
|
+
import { US_STATE_BY_ABBREVIATION } from "../us/state.js";
|
|
30
|
+
/** Strip diacritics + lowercase so `Québec`, `Quebec`, and `quebec` all key alike (mirrors `ca/province.ts`). */
|
|
31
|
+
function foldName(s) {
|
|
32
|
+
return s
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.normalize("NFD")
|
|
35
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
36
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
37
|
+
.trim();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Folded surface form (ISO code, English name, or — for CA — co-official French name) → subdivision. Built once. US
|
|
41
|
+
* states are inserted first and never overwritten, so on the (currently empty) event of a future code/name collision
|
|
42
|
+
* the US entry wins deterministically; today the two sets are disjoint.
|
|
43
|
+
*/
|
|
44
|
+
const SUBDIVISION_LOOKUP = (() => {
|
|
45
|
+
const out = new Map();
|
|
46
|
+
const put = (key, match) => {
|
|
47
|
+
const folded = foldName(key);
|
|
48
|
+
if (folded.length > 0 && !out.has(folded)) {
|
|
49
|
+
out.set(folded, match);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
for (const [code, name] of Object.entries(US_STATE_BY_ABBREVIATION)) {
|
|
53
|
+
const match = { code, name, country: "US" };
|
|
54
|
+
put(code, match);
|
|
55
|
+
put(name, match);
|
|
56
|
+
}
|
|
57
|
+
for (const info of Object.values(CA_PROVINCES)) {
|
|
58
|
+
const match = { code: info.code, name: info.name, country: "CA" };
|
|
59
|
+
put(info.code, match);
|
|
60
|
+
put(info.name, match);
|
|
61
|
+
put(info.french, match);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
})();
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a first-level subdivision surface form (ISO 3166-2 code, English name, or co-official French name for CA;
|
|
67
|
+
* accents optional) to its `{ code, name, country }`. Case- and diacritic-insensitive. Returns null for anything that
|
|
68
|
+
* isn't a US state or Canadian province/territory — including bare country tokens (use {@link matchCountry} for
|
|
69
|
+
* those).
|
|
70
|
+
*/
|
|
71
|
+
export function matchSubdivision(token) {
|
|
72
|
+
if (!token || typeof token !== "string")
|
|
73
|
+
return null;
|
|
74
|
+
return SUBDIVISION_LOOKUP.get(foldName(token)) ?? null;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=subdivision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subdivision.js","sourceRoot":"","sources":["../../country/subdivision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAYzD,iHAAiH;AACjH,SAAS,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC;SACN,WAAW,EAAE;SACb,SAAS,CAAC,KAAK,CAAC;SAChB,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;SAC/B,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,IAAI,EAAE,CAAA;AACT,CAAC;AAED;;;;GAIG;AACH,MAAM,kBAAkB,GAA0C,CAAC,GAAG,EAAE;IACvE,MAAM,GAAG,GAAG,IAAI,GAAG,EAA4B,CAAA;IAC/C,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,KAAuB,EAAQ,EAAE;QAC1D,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;QAE5B,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3C,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;QACvB,CAAC;IACF,CAAC,CAAA;IAED,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,wBAAwB,CAAC,EAAE,CAAC;QACrE,MAAM,KAAK,GAAqB,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;QAC7D,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QAChB,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IACjB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;QAChD,MAAM,KAAK,GAAqB,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;QACnF,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QACrB,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;QACrB,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IACxB,CAAC;IAED,OAAO,GAAG,CAAA;AACX,CAAC,CAAC,EAAE,CAAA;AAEJ;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAgC;IAChE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAEpD,OAAO,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,CAAA;AACvD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mailwoman/codex",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.3.0",
|
|
4
4
|
"description": "Per-address-system postal reference data + branded types (USPS street suffixes, US ZIP codes). Pure, zero-runtime-dep — the shared canonical home for postal-system primitives the parser, resolver, and synthesis layers all reach for.",
|
|
5
5
|
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
|
6
6
|
"repository": {
|
|
@@ -12,74 +12,118 @@
|
|
|
12
12
|
"out/**/*.js",
|
|
13
13
|
"out/**/*.js.map",
|
|
14
14
|
"out/**/*.d.ts",
|
|
15
|
-
"out/**/*.d.ts.map"
|
|
15
|
+
"out/**/*.d.ts.map",
|
|
16
|
+
"*.ts",
|
|
17
|
+
"*.tsx",
|
|
18
|
+
"**/*.ts",
|
|
19
|
+
"**/*.tsx",
|
|
20
|
+
"!*.test.ts",
|
|
21
|
+
"!*.test.tsx",
|
|
22
|
+
"!**/*.test.ts",
|
|
23
|
+
"!**/*.test.tsx"
|
|
16
24
|
],
|
|
17
25
|
"type": "module",
|
|
18
26
|
"exports": {
|
|
19
27
|
"./package.json": "./package.json",
|
|
20
28
|
".": {
|
|
21
|
-
"
|
|
22
|
-
"default": "./out/index.js"
|
|
23
|
-
"types": "./out/index.d.ts"
|
|
29
|
+
"types": "./out/index.d.ts",
|
|
30
|
+
"default": "./out/index.js"
|
|
24
31
|
},
|
|
25
32
|
"./country": {
|
|
26
|
-
"
|
|
27
|
-
"default": "./out/country/index.js"
|
|
28
|
-
"types": "./out/country/index.d.ts"
|
|
33
|
+
"types": "./out/country/index.d.ts",
|
|
34
|
+
"default": "./out/country/index.js"
|
|
29
35
|
},
|
|
30
36
|
"./us": {
|
|
31
|
-
"
|
|
32
|
-
"default": "./out/us/index.js"
|
|
33
|
-
"types": "./out/us/index.d.ts"
|
|
37
|
+
"types": "./out/us/index.d.ts",
|
|
38
|
+
"default": "./out/us/index.js"
|
|
34
39
|
},
|
|
35
40
|
"./de": {
|
|
36
|
-
"
|
|
37
|
-
"default": "./out/de/index.js"
|
|
38
|
-
"types": "./out/de/index.d.ts"
|
|
41
|
+
"types": "./out/de/index.d.ts",
|
|
42
|
+
"default": "./out/de/index.js"
|
|
39
43
|
},
|
|
40
44
|
"./fr": {
|
|
41
|
-
"
|
|
42
|
-
"default": "./out/fr/index.js"
|
|
43
|
-
"types": "./out/fr/index.d.ts"
|
|
45
|
+
"types": "./out/fr/index.d.ts",
|
|
46
|
+
"default": "./out/fr/index.js"
|
|
44
47
|
},
|
|
45
48
|
"./ca": {
|
|
46
|
-
"
|
|
47
|
-
"default": "./out/ca/index.js"
|
|
48
|
-
"types": "./out/ca/index.d.ts"
|
|
49
|
+
"types": "./out/ca/index.d.ts",
|
|
50
|
+
"default": "./out/ca/index.js"
|
|
49
51
|
},
|
|
50
52
|
"./gb": {
|
|
51
|
-
"
|
|
52
|
-
"default": "./out/gb/index.js"
|
|
53
|
-
"types": "./out/gb/index.d.ts"
|
|
53
|
+
"types": "./out/gb/index.d.ts",
|
|
54
|
+
"default": "./out/gb/index.js"
|
|
54
55
|
},
|
|
55
56
|
"./jp": {
|
|
56
|
-
"
|
|
57
|
-
"default": "./out/jp/index.js"
|
|
58
|
-
"types": "./out/jp/index.d.ts"
|
|
57
|
+
"types": "./out/jp/index.d.ts",
|
|
58
|
+
"default": "./out/jp/index.js"
|
|
59
59
|
},
|
|
60
60
|
"./au": {
|
|
61
|
-
"
|
|
62
|
-
"default": "./out/au/index.js"
|
|
63
|
-
"types": "./out/au/index.d.ts"
|
|
61
|
+
"types": "./out/au/index.d.ts",
|
|
62
|
+
"default": "./out/au/index.js"
|
|
64
63
|
},
|
|
65
64
|
"./nz": {
|
|
66
|
-
"
|
|
67
|
-
"default": "./out/nz/index.js"
|
|
68
|
-
"types": "./out/nz/index.d.ts"
|
|
65
|
+
"types": "./out/nz/index.d.ts",
|
|
66
|
+
"default": "./out/nz/index.js"
|
|
69
67
|
},
|
|
70
68
|
"./tools": {
|
|
71
|
-
"
|
|
72
|
-
"default": "./out/tools/index.js"
|
|
73
|
-
"types": "./out/tools/index.d.ts"
|
|
69
|
+
"types": "./out/tools/index.d.ts",
|
|
70
|
+
"default": "./out/tools/index.js"
|
|
74
71
|
}
|
|
75
72
|
},
|
|
76
73
|
"publishConfig": {
|
|
77
|
-
"access": "public"
|
|
74
|
+
"access": "public",
|
|
75
|
+
"exports": {
|
|
76
|
+
"./package.json": "./package.json",
|
|
77
|
+
".": {
|
|
78
|
+
"types": "./out/index.d.ts",
|
|
79
|
+
"default": "./out/index.js"
|
|
80
|
+
},
|
|
81
|
+
"./country": {
|
|
82
|
+
"types": "./out/country/index.d.ts",
|
|
83
|
+
"default": "./out/country/index.js"
|
|
84
|
+
},
|
|
85
|
+
"./us": {
|
|
86
|
+
"types": "./out/us/index.d.ts",
|
|
87
|
+
"default": "./out/us/index.js"
|
|
88
|
+
},
|
|
89
|
+
"./de": {
|
|
90
|
+
"types": "./out/de/index.d.ts",
|
|
91
|
+
"default": "./out/de/index.js"
|
|
92
|
+
},
|
|
93
|
+
"./fr": {
|
|
94
|
+
"types": "./out/fr/index.d.ts",
|
|
95
|
+
"default": "./out/fr/index.js"
|
|
96
|
+
},
|
|
97
|
+
"./ca": {
|
|
98
|
+
"types": "./out/ca/index.d.ts",
|
|
99
|
+
"default": "./out/ca/index.js"
|
|
100
|
+
},
|
|
101
|
+
"./gb": {
|
|
102
|
+
"types": "./out/gb/index.d.ts",
|
|
103
|
+
"default": "./out/gb/index.js"
|
|
104
|
+
},
|
|
105
|
+
"./jp": {
|
|
106
|
+
"types": "./out/jp/index.d.ts",
|
|
107
|
+
"default": "./out/jp/index.js"
|
|
108
|
+
},
|
|
109
|
+
"./au": {
|
|
110
|
+
"types": "./out/au/index.d.ts",
|
|
111
|
+
"default": "./out/au/index.js"
|
|
112
|
+
},
|
|
113
|
+
"./nz": {
|
|
114
|
+
"types": "./out/nz/index.d.ts",
|
|
115
|
+
"default": "./out/nz/index.js"
|
|
116
|
+
},
|
|
117
|
+
"./tools": {
|
|
118
|
+
"types": "./out/tools/index.d.ts",
|
|
119
|
+
"default": "./out/tools/index.js"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
78
122
|
},
|
|
79
123
|
"dependencies": {
|
|
80
124
|
"type-fest": "^5.8.0"
|
|
81
125
|
},
|
|
82
126
|
"devDependencies": {
|
|
83
|
-
"@mailwoman/annotations": "7.
|
|
127
|
+
"@mailwoman/annotations": "7.3.0"
|
|
84
128
|
}
|
|
85
129
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The inverse of the per-slice postcode patterns: given a postcode string, which address SYSTEMS
|
|
7
|
+
* could it belong to? Each codex slice owns its own postcode shape (`us` accepts
|
|
8
|
+
* `\d{5}(-\d{4})?`, `ca` accepts `A1A 1A1`, `jp` accepts `NNN-NNNN`, …); this is the single place
|
|
9
|
+
* that asks all of them at once and collects the matches.
|
|
10
|
+
*
|
|
11
|
+
* It is the shared source of truth for "which systems can this shape be" — consumed by the postcode
|
|
12
|
+
* anchor (to narrow which systems' street vocabularies it checks) and, in time, by the runtime
|
|
13
|
+
* pipeline's locale gate (so its format→locale scoring derives from the same patterns rather than
|
|
14
|
+
* a second, divergent copy). The point is to unify the DATA, not to couple the modules: callers
|
|
15
|
+
* depend on this pure function, never on each other.
|
|
16
|
+
*
|
|
17
|
+
* Note this is a SHAPE test, not a gazetteer-membership test. A bare `68161` matches the US,
|
|
18
|
+
* German, AND French 5-digit shapes, so it returns `["us", "de", "fr"]` — the shape alone cannot
|
|
19
|
+
* split the numeric-postcode systems. The anchor uses real gazetteer membership for the finer
|
|
20
|
+
* call; this function answers the coarser, model-free "which systems is this shape even eligible
|
|
21
|
+
* for".
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { normalizeAuPostcode } from "./au/index.ts"
|
|
25
|
+
import { normalizeCaPostalCode } from "./ca/index.ts"
|
|
26
|
+
import { normalizePLZ } from "./de/index.ts"
|
|
27
|
+
import { normalizeCodePostal } from "./fr/index.ts"
|
|
28
|
+
import { normalizeUkPostcode } from "./gb/index.ts"
|
|
29
|
+
import { normalizeJpPostalCode } from "./jp/index.ts"
|
|
30
|
+
import { normalizeNzPostcode } from "./nz/index.ts"
|
|
31
|
+
import { isZipCode } from "./us/index.ts"
|
|
32
|
+
|
|
33
|
+
/** A codex address-system code — the subpath under `@mailwoman/codex/<system>`. */
|
|
34
|
+
export type SystemCode = "us" | "de" | "fr" | "ca" | "gb" | "jp" | "au" | "nz"
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Per-system membership test: each entry returns true when the string is accepted by that system's own postcode shape
|
|
38
|
+
* (after that system's normalization — so `D-68161` reaches `de`, `1012 LM` reaches nothing here since NL has no slice
|
|
39
|
+
* yet, etc.). Ordered for a stable, alphabetical-ish result.
|
|
40
|
+
*/
|
|
41
|
+
const SYSTEM_ACCEPTS: ReadonlyArray<readonly [SystemCode, (s: string) => boolean]> = [
|
|
42
|
+
["us", (s) => isZipCode(s)],
|
|
43
|
+
["de", (s) => normalizePLZ(s) !== null],
|
|
44
|
+
["fr", (s) => normalizeCodePostal(s) !== null],
|
|
45
|
+
["ca", (s) => normalizeCaPostalCode(s) !== null],
|
|
46
|
+
["gb", (s) => normalizeUkPostcode(s) !== null],
|
|
47
|
+
["jp", (s) => normalizeJpPostalCode(s) !== null],
|
|
48
|
+
["au", (s) => normalizeAuPostcode(s) !== null],
|
|
49
|
+
["nz", (s) => normalizeNzPostcode(s) !== null],
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Every address system whose own postcode shape accepts `postcode`. Empty when no system recognizes the shape (e.g. a
|
|
54
|
+
* bare `27`, or a 7-digit run). O(number of systems) — a handful of cheap regex tests, run only on the few
|
|
55
|
+
* postcode-shaped spans an address contains.
|
|
56
|
+
*/
|
|
57
|
+
export function candidateSystemsForPostcode(postcode: string): SystemCode[] {
|
|
58
|
+
if (typeof postcode !== "string" || postcode.length === 0) return []
|
|
59
|
+
const out: SystemCode[] = []
|
|
60
|
+
|
|
61
|
+
for (const [system, accepts] of SYSTEM_ACCEPTS) {
|
|
62
|
+
if (accepts(postcode)) {
|
|
63
|
+
out.push(system)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return out
|
|
68
|
+
}
|