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 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
@@ -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;