sl-address 0.1.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/README.md +91 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +281 -0
- package/dist/postal-data.json +12758 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +66 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# sl-address
|
|
2
|
+
|
|
3
|
+
TypeScript utilities for exploring Sri Lanka provinces, districts, cities, and postal codes. Ship a single package and unlock lookups, fuzzy search, and readable formatting for any Sri Lankan address workflow.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Complete dataset covering every Sri Lankan postal code with its city, district, and province
|
|
8
|
+
- Friendly lookup helpers for provinces, districts, cities, and postal codes
|
|
9
|
+
- JSON-first responses that drop straight into APIs, CLIs, or UIs
|
|
10
|
+
- Fuzzy search, auto-complete, and normalization helpers to tidy user input
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install sl-address
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import postalService, { p } from 'sl-address';
|
|
22
|
+
|
|
23
|
+
// `p` and the default export reference the same PostalService instance.
|
|
24
|
+
console.log(p.getProvinces());
|
|
25
|
+
console.log(p.getInfoByPostal('10280'));
|
|
26
|
+
console.log(postalService.toReadable('10280')); // "Maharagama, Colombo, Western Province"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
CommonJS usage:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
const { p } = require('sl-address');
|
|
33
|
+
|
|
34
|
+
console.log(p.getDistrictsByProvince('Western Province'));
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
All methods return JSON-friendly data (arrays, objects, or strings) so they can be serialized or sent over the wire without extra work.
|
|
38
|
+
|
|
39
|
+
## API Overview
|
|
40
|
+
|
|
41
|
+
- `getProvinces()` – every province (`"Western Province"`, `"Central Province"`, ...)
|
|
42
|
+
- `getDistricts()` – every district name
|
|
43
|
+
- `getCities()` – every city name
|
|
44
|
+
- `getPostals()` – full dataset of postal records
|
|
45
|
+
- `getDistrictsByProvince(province)` – districts within a province
|
|
46
|
+
- `getCitiesByDistrict(district)` – cities in a district
|
|
47
|
+
- `getCitiesByProvince(province)` – cities in a province
|
|
48
|
+
- `getPostalsByDistrict(district)` – postal records for a district
|
|
49
|
+
- `getPostalsByProvince(province)` – postal records grouped by district
|
|
50
|
+
- `getPostalsByCity(city)` – postal records for a city
|
|
51
|
+
- `getInfoByPostal(code)` – full record for a code
|
|
52
|
+
- `getInfoByCity(city)` – all records matching a city (handles multiple codes)
|
|
53
|
+
- `getDistrictByPostal(code)` / `getProvinceByPostal(code)` / `getCityByPostal(code)` – individual fields
|
|
54
|
+
- `isValidPostal(code)` – boolean existence check
|
|
55
|
+
- `searchCity(query, limit?)` – fuzzy matches with scores
|
|
56
|
+
- `autocompleteCity(prefix, limit?)` – suggestion list for partial input
|
|
57
|
+
- `normalize(input)` – best-guess canonical city name
|
|
58
|
+
- `toReadable(code)` – formatted string `"City, District, Province"`
|
|
59
|
+
|
|
60
|
+
Inputs ignore case, spacing, and whether `"Province"` is appended (e.g., `"Western"` works).
|
|
61
|
+
|
|
62
|
+
### Fuzzy helpers
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
p.normalize('maha ragama'); // "Maharagama"
|
|
66
|
+
p.searchCity('galleo'); // close matches with similarity scores
|
|
67
|
+
p.autocompleteCity('kan'); // ["Kandy", "Kananke Bazaar", ...]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Dataset
|
|
71
|
+
|
|
72
|
+
The package includes `postal-data.json`, generated from the provided `postalcodes.txt`, covering every Sri Lankan postal code along with its city, district, and province. Need to refresh the dataset? Run:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm run generate:data
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm install
|
|
82
|
+
npm run lint
|
|
83
|
+
npm run build
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- `npm run generate:data` – convert `postalcodes.txt` into `src/postal-data.json`
|
|
87
|
+
- `npm run build` – compile TypeScript and copy the dataset to `dist/`
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT © Sumudu Kulathunga
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { CitySearchResult, PostalRecord } from './types';
|
|
2
|
+
export declare class PostalService {
|
|
3
|
+
private readonly records;
|
|
4
|
+
private readonly postalIndex;
|
|
5
|
+
private readonly cityIndex;
|
|
6
|
+
private readonly districtIndex;
|
|
7
|
+
private readonly provinceIndex;
|
|
8
|
+
private readonly cityAliases;
|
|
9
|
+
private readonly districtAliases;
|
|
10
|
+
private readonly provinceAliases;
|
|
11
|
+
constructor(records: PostalRecord[]);
|
|
12
|
+
getDistricts(): string[];
|
|
13
|
+
getProvinces(): string[];
|
|
14
|
+
getCities(): string[];
|
|
15
|
+
getPostals(): PostalRecord[];
|
|
16
|
+
getDistrictsByProvince(province: string): string[];
|
|
17
|
+
getCitiesByDistrict(district: string): string[];
|
|
18
|
+
getCitiesByProvince(province: string): string[];
|
|
19
|
+
getPostalsByDistrict(district: string): PostalRecord[];
|
|
20
|
+
getPostalsByProvince(province: string): Record<string, PostalRecord[]>;
|
|
21
|
+
getInfoByPostal(code: string | number): PostalRecord | null;
|
|
22
|
+
getInfoByCity(city: string): PostalRecord[];
|
|
23
|
+
getDistrictByPostal(code: string | number): string | null;
|
|
24
|
+
getProvinceByPostal(code: string | number): string | null;
|
|
25
|
+
getCityByPostal(code: string | number): string | null;
|
|
26
|
+
isValidPostal(code: string | number): boolean;
|
|
27
|
+
getPostalsByCity(city: string): PostalRecord[];
|
|
28
|
+
searchCity(query: string, limit?: number): CitySearchResult[];
|
|
29
|
+
autocompleteCity(input: string, limit?: number): string[];
|
|
30
|
+
normalize(input: string): string;
|
|
31
|
+
toReadable(code: string | number): string | null;
|
|
32
|
+
private addToIndex;
|
|
33
|
+
private formatRecord;
|
|
34
|
+
private lookupProvince;
|
|
35
|
+
private lookupDistrictRecords;
|
|
36
|
+
private lookupCityRecords;
|
|
37
|
+
private bestMatch;
|
|
38
|
+
}
|
|
39
|
+
declare const service: PostalService;
|
|
40
|
+
export declare const p: PostalService;
|
|
41
|
+
export default service;
|
|
42
|
+
export type { PostalRecord, CitySearchResult } from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This npm package provides a comprehensive service for handling postal codes
|
|
3
|
+
// It includes functionalities for retrieving postal codes, cities, districts, and provinces, as well as searching and autocompleting city names.
|
|
4
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
|
+
};
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.p = exports.PostalService = void 0;
|
|
9
|
+
// Author: Sumudu Kulathunga
|
|
10
|
+
// GitHub: https://github.com/sumudu-k/sl-address
|
|
11
|
+
const postal_data_json_1 = __importDefault(require("./postal-data.json"));
|
|
12
|
+
const utils_1 = require("./utils");
|
|
13
|
+
function sanitizeRecord(record) {
|
|
14
|
+
return {
|
|
15
|
+
postalCode: record.postalCode.trim(),
|
|
16
|
+
city: record.city.trim(),
|
|
17
|
+
district: record.district.trim(),
|
|
18
|
+
province: record.province.trim(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function normalizeProvinceKey(value) {
|
|
22
|
+
return (0, utils_1.normalizeKey)(value.replace(/province$/i, ''));
|
|
23
|
+
}
|
|
24
|
+
function normalizePostalCode(value) {
|
|
25
|
+
const digits = String(value).replace(/[^0-9]/g, '');
|
|
26
|
+
if (!digits) {
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
return digits.padStart(5, '0');
|
|
30
|
+
}
|
|
31
|
+
class PostalService {
|
|
32
|
+
constructor(records) {
|
|
33
|
+
this.postalIndex = new Map();
|
|
34
|
+
this.cityIndex = new Map();
|
|
35
|
+
this.districtIndex = new Map();
|
|
36
|
+
this.provinceIndex = new Map();
|
|
37
|
+
this.cityAliases = new Map();
|
|
38
|
+
this.districtAliases = new Map();
|
|
39
|
+
this.provinceAliases = new Map();
|
|
40
|
+
const sanitized = records.map(sanitizeRecord);
|
|
41
|
+
sanitized.sort((a, b) => a.postalCode.localeCompare(b.postalCode));
|
|
42
|
+
this.records = sanitized;
|
|
43
|
+
for (const record of sanitized) {
|
|
44
|
+
this.addToIndex(this.postalIndex, record.postalCode, record);
|
|
45
|
+
const cityKey = (0, utils_1.normalizeKey)(record.city);
|
|
46
|
+
this.addToIndex(this.cityIndex, cityKey, record);
|
|
47
|
+
if (!this.cityAliases.has(cityKey)) {
|
|
48
|
+
this.cityAliases.set(cityKey, record.city);
|
|
49
|
+
}
|
|
50
|
+
const districtKey = (0, utils_1.normalizeKey)(record.district);
|
|
51
|
+
this.addToIndex(this.districtIndex, districtKey, record);
|
|
52
|
+
if (!this.districtAliases.has(districtKey)) {
|
|
53
|
+
this.districtAliases.set(districtKey, record.district);
|
|
54
|
+
}
|
|
55
|
+
const provinceKey = normalizeProvinceKey(record.province);
|
|
56
|
+
this.addToIndex(this.provinceIndex, provinceKey, record);
|
|
57
|
+
if (!this.provinceAliases.has(provinceKey)) {
|
|
58
|
+
this.provinceAliases.set(provinceKey, record.province);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
getDistricts() {
|
|
63
|
+
return Array.from(this.districtAliases.values()).sort();
|
|
64
|
+
}
|
|
65
|
+
getProvinces() {
|
|
66
|
+
const provinces = Array.from(this.provinceAliases.values())
|
|
67
|
+
.map((province) => (0, utils_1.formatProvinceName)(province));
|
|
68
|
+
return Array.from(new Set(provinces)).sort();
|
|
69
|
+
}
|
|
70
|
+
getCities() {
|
|
71
|
+
return Array.from(this.cityAliases.values()).sort();
|
|
72
|
+
}
|
|
73
|
+
getPostals() {
|
|
74
|
+
return this.records.map((record) => this.formatRecord(record));
|
|
75
|
+
}
|
|
76
|
+
getDistrictsByProvince(province) {
|
|
77
|
+
var _a;
|
|
78
|
+
const matched = this.lookupProvince(province);
|
|
79
|
+
if (!matched) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const records = (_a = this.provinceIndex.get(matched)) !== null && _a !== void 0 ? _a : [];
|
|
83
|
+
const districts = new Set(records.map((record) => record.district));
|
|
84
|
+
return Array.from(districts).sort();
|
|
85
|
+
}
|
|
86
|
+
getCitiesByDistrict(district) {
|
|
87
|
+
const records = this.lookupDistrictRecords(district);
|
|
88
|
+
const cities = new Set(records.map((record) => record.city));
|
|
89
|
+
return Array.from(cities).sort();
|
|
90
|
+
}
|
|
91
|
+
getCitiesByProvince(province) {
|
|
92
|
+
var _a;
|
|
93
|
+
const matched = this.lookupProvince(province);
|
|
94
|
+
if (!matched) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const records = (_a = this.provinceIndex.get(matched)) !== null && _a !== void 0 ? _a : [];
|
|
98
|
+
const cities = new Set(records.map((record) => record.city));
|
|
99
|
+
return Array.from(cities).sort();
|
|
100
|
+
}
|
|
101
|
+
getPostalsByDistrict(district) {
|
|
102
|
+
const records = this.lookupDistrictRecords(district);
|
|
103
|
+
return records.map((record) => this.formatRecord(record));
|
|
104
|
+
}
|
|
105
|
+
getPostalsByProvince(province) {
|
|
106
|
+
var _a;
|
|
107
|
+
const matched = this.lookupProvince(province);
|
|
108
|
+
if (!matched) {
|
|
109
|
+
return {};
|
|
110
|
+
}
|
|
111
|
+
const records = (_a = this.provinceIndex.get(matched)) !== null && _a !== void 0 ? _a : [];
|
|
112
|
+
const byDistrict = {};
|
|
113
|
+
for (const record of records) {
|
|
114
|
+
if (!byDistrict[record.district]) {
|
|
115
|
+
byDistrict[record.district] = [];
|
|
116
|
+
}
|
|
117
|
+
byDistrict[record.district].push(this.formatRecord(record));
|
|
118
|
+
}
|
|
119
|
+
for (const district of Object.keys(byDistrict)) {
|
|
120
|
+
byDistrict[district].sort((a, b) => a.city.localeCompare(b.city));
|
|
121
|
+
}
|
|
122
|
+
return byDistrict;
|
|
123
|
+
}
|
|
124
|
+
getInfoByPostal(code) {
|
|
125
|
+
const key = normalizePostalCode(code);
|
|
126
|
+
const matches = this.postalIndex.get(key);
|
|
127
|
+
if (!matches || matches.length === 0) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return this.formatRecord(matches[0]);
|
|
131
|
+
}
|
|
132
|
+
getInfoByCity(city) {
|
|
133
|
+
const records = this.lookupCityRecords(city);
|
|
134
|
+
return records.map((record) => this.formatRecord(record));
|
|
135
|
+
}
|
|
136
|
+
getDistrictByPostal(code) {
|
|
137
|
+
const info = this.getInfoByPostal(code);
|
|
138
|
+
return info ? info.district : null;
|
|
139
|
+
}
|
|
140
|
+
getProvinceByPostal(code) {
|
|
141
|
+
const info = this.getInfoByPostal(code);
|
|
142
|
+
return info ? info.province : null;
|
|
143
|
+
}
|
|
144
|
+
getCityByPostal(code) {
|
|
145
|
+
const info = this.getInfoByPostal(code);
|
|
146
|
+
return info ? info.city : null;
|
|
147
|
+
}
|
|
148
|
+
isValidPostal(code) {
|
|
149
|
+
const key = normalizePostalCode(code);
|
|
150
|
+
return this.postalIndex.has(key);
|
|
151
|
+
}
|
|
152
|
+
getPostalsByCity(city) {
|
|
153
|
+
return this.getInfoByCity(city);
|
|
154
|
+
}
|
|
155
|
+
searchCity(query, limit = 10) {
|
|
156
|
+
const normalizedQuery = (0, utils_1.normalizeKey)(query);
|
|
157
|
+
if (!normalizedQuery) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
const results = [];
|
|
161
|
+
for (const [key, records] of this.cityIndex.entries()) {
|
|
162
|
+
const score = (0, utils_1.getScore)(normalizedQuery, key);
|
|
163
|
+
if (score <= 0.6) {
|
|
164
|
+
results.push({
|
|
165
|
+
city: records[0].city,
|
|
166
|
+
district: records[0].district,
|
|
167
|
+
province: (0, utils_1.formatProvinceName)(records[0].province),
|
|
168
|
+
postalCodes: records.map((record) => record.postalCode),
|
|
169
|
+
score,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
results.sort((a, b) => a.score - b.score || a.city.localeCompare(b.city));
|
|
174
|
+
return results.slice(0, limit);
|
|
175
|
+
}
|
|
176
|
+
autocompleteCity(input, limit = 10) {
|
|
177
|
+
const lowerInput = input.trim().toLowerCase();
|
|
178
|
+
if (!lowerInput) {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
const matches = new Set();
|
|
182
|
+
for (const city of this.cityAliases.values()) {
|
|
183
|
+
if (city.toLowerCase().startsWith(lowerInput)) {
|
|
184
|
+
matches.add(city);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (matches.size < limit) {
|
|
188
|
+
for (const city of this.cityAliases.values()) {
|
|
189
|
+
if (city.toLowerCase().includes(lowerInput)) {
|
|
190
|
+
matches.add(city);
|
|
191
|
+
}
|
|
192
|
+
if (matches.size >= limit) {
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return Array.from(matches).sort().slice(0, limit);
|
|
198
|
+
}
|
|
199
|
+
normalize(input) {
|
|
200
|
+
var _a;
|
|
201
|
+
const best = this.bestMatch(input, this.cityAliases);
|
|
202
|
+
return best ? (_a = this.cityAliases.get(best.key)) !== null && _a !== void 0 ? _a : input.trim() : input.trim();
|
|
203
|
+
}
|
|
204
|
+
toReadable(code) {
|
|
205
|
+
const info = this.getInfoByPostal(code);
|
|
206
|
+
if (!info) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
return `${info.city}, ${info.district}, ${info.province}`;
|
|
210
|
+
}
|
|
211
|
+
addToIndex(map, key, record) {
|
|
212
|
+
if (!map.has(key)) {
|
|
213
|
+
map.set(key, []);
|
|
214
|
+
}
|
|
215
|
+
map.get(key).push(record);
|
|
216
|
+
}
|
|
217
|
+
formatRecord(record) {
|
|
218
|
+
return {
|
|
219
|
+
postalCode: record.postalCode,
|
|
220
|
+
city: record.city,
|
|
221
|
+
district: record.district,
|
|
222
|
+
province: (0, utils_1.formatProvinceName)(record.province),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
lookupProvince(province) {
|
|
226
|
+
const key = normalizeProvinceKey(province);
|
|
227
|
+
if (this.provinceAliases.has(key)) {
|
|
228
|
+
return key;
|
|
229
|
+
}
|
|
230
|
+
const best = this.bestMatch(key, this.provinceAliases);
|
|
231
|
+
return best === null || best === void 0 ? void 0 : best.key;
|
|
232
|
+
}
|
|
233
|
+
lookupDistrictRecords(district) {
|
|
234
|
+
var _a;
|
|
235
|
+
const key = (0, utils_1.normalizeKey)(district);
|
|
236
|
+
if (this.districtIndex.has(key)) {
|
|
237
|
+
return this.districtIndex.get(key);
|
|
238
|
+
}
|
|
239
|
+
const best = this.bestMatch(key, this.districtAliases);
|
|
240
|
+
if (best) {
|
|
241
|
+
return (_a = this.districtIndex.get(best.key)) !== null && _a !== void 0 ? _a : [];
|
|
242
|
+
}
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
lookupCityRecords(city) {
|
|
246
|
+
var _a;
|
|
247
|
+
const key = (0, utils_1.normalizeKey)(city);
|
|
248
|
+
if (this.cityIndex.has(key)) {
|
|
249
|
+
return this.cityIndex.get(key);
|
|
250
|
+
}
|
|
251
|
+
const best = this.bestMatch(key, this.cityAliases);
|
|
252
|
+
if (best) {
|
|
253
|
+
return (_a = this.cityIndex.get(best.key)) !== null && _a !== void 0 ? _a : [];
|
|
254
|
+
}
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
bestMatch(query, aliases) {
|
|
258
|
+
const normalizedQuery = (0, utils_1.normalizeKey)(query);
|
|
259
|
+
if (!normalizedQuery) {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
if (aliases.has(normalizedQuery)) {
|
|
263
|
+
return { key: normalizedQuery, score: 0 };
|
|
264
|
+
}
|
|
265
|
+
let best;
|
|
266
|
+
for (const key of aliases.keys()) {
|
|
267
|
+
const score = (0, utils_1.getScore)(normalizedQuery, key);
|
|
268
|
+
if (score > 0.6) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (!best || score < best.score) {
|
|
272
|
+
best = { key, score };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return best;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
exports.PostalService = PostalService;
|
|
279
|
+
const service = new PostalService(postal_data_json_1.default);
|
|
280
|
+
exports.p = service;
|
|
281
|
+
exports.default = service;
|