thaizip 0.6.0 → 0.7.3

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/dist/index.cjs CHANGED
@@ -4,8 +4,13 @@
4
4
  var TONE_MARKS = /[่-๋็์]/g;
5
5
  var ADDRESS_PREFIXES = /^(จังหวัด|อำเภอ|ตำบล|แขวง|เขต|จ\.|อ\.|ต\.|ข\.)/;
6
6
  function normalizeThaiAddressText(input) {
7
- if (!input) return "";
8
- let text = input.trim();
7
+ if (typeof input !== "string") {
8
+ throw new TypeError(
9
+ `[thaizip] normalizeThaiAddressText expected string, got ${input === null ? "null" : typeof input}`
10
+ );
11
+ }
12
+ if (input.length === 0) return "";
13
+ let text = input.normalize("NFC").trim();
9
14
  text = text.replace(ADDRESS_PREFIXES, "").trim();
10
15
  text = text.replace(TONE_MARKS, "");
11
16
  text = text.toLowerCase();
@@ -30,6 +35,69 @@ function extractTrigramsNormalized(text) {
30
35
  }
31
36
 
32
37
  // src/core/indexer.ts
38
+ function typeErr(table, id, field, expected, value) {
39
+ return new TypeError(`[thaizip] ${table} ${id}: expected ${expected} for ${field}, got ${typeof value}`);
40
+ }
41
+ function typeName(value) {
42
+ return value === null ? "null" : typeof value;
43
+ }
44
+ function validateRawData(data) {
45
+ if (data === null || typeof data !== "object") {
46
+ throw new TypeError(`[thaizip] validateRawData expected a RawData object, got ${typeName(data)}`);
47
+ }
48
+ const { provinces, amphures, tambons } = data;
49
+ if (!Array.isArray(provinces)) {
50
+ throw new TypeError(`[thaizip] RawData.provinces must be an array, got ${typeName(provinces)}`);
51
+ }
52
+ if (!Array.isArray(amphures)) {
53
+ throw new TypeError(`[thaizip] RawData.amphures must be an array, got ${typeName(amphures)}`);
54
+ }
55
+ if (!Array.isArray(tambons)) {
56
+ throw new TypeError(`[thaizip] RawData.tambons must be an array, got ${typeName(tambons)}`);
57
+ }
58
+ const seenProvinceIds = /* @__PURE__ */ new Set();
59
+ for (let i = 0; i < provinces.length; i++) {
60
+ const p = provinces[i];
61
+ if (p === null || typeof p !== "object") {
62
+ throw new TypeError(`[thaizip] province[${i}]: expected object, got ${typeName(p)}`);
63
+ }
64
+ if (typeof p.id !== "number") throw typeErr("province", p.id, "id", "number", p.id);
65
+ if (seenProvinceIds.has(p.id)) throw new TypeError(`[thaizip] duplicate province id: ${p.id}`);
66
+ seenProvinceIds.add(p.id);
67
+ if (typeof p.name_th !== "string") throw typeErr("province", p.id, "name_th", "string", p.name_th);
68
+ if (typeof p.name_en !== "string") throw typeErr("province", p.id, "name_en", "string", p.name_en);
69
+ }
70
+ const seenAmphureIds = /* @__PURE__ */ new Set();
71
+ for (let i = 0; i < amphures.length; i++) {
72
+ const a = amphures[i];
73
+ if (a === null || typeof a !== "object") {
74
+ throw new TypeError(`[thaizip] amphure[${i}]: expected object, got ${typeName(a)}`);
75
+ }
76
+ if (typeof a.id !== "number") throw typeErr("amphure", a.id, "id", "number", a.id);
77
+ if (seenAmphureIds.has(a.id)) throw new TypeError(`[thaizip] duplicate amphure id: ${a.id}`);
78
+ seenAmphureIds.add(a.id);
79
+ if (typeof a.name_th !== "string") throw typeErr("amphure", a.id, "name_th", "string", a.name_th);
80
+ if (typeof a.name_en !== "string") throw typeErr("amphure", a.id, "name_en", "string", a.name_en);
81
+ if (typeof a.province_id !== "number") throw typeErr("amphure", a.id, "province_id", "number", a.province_id);
82
+ }
83
+ const seenTambonIds = /* @__PURE__ */ new Set();
84
+ for (let i = 0; i < tambons.length; i++) {
85
+ const t = tambons[i];
86
+ if (t === null || typeof t !== "object") {
87
+ throw new TypeError(`[thaizip] tambon[${i}]: expected object, got ${typeName(t)}`);
88
+ }
89
+ if (typeof t.id !== "number") throw typeErr("tambon", t.id, "id", "number", t.id);
90
+ if (seenTambonIds.has(t.id)) throw new TypeError(`[thaizip] duplicate tambon id: ${t.id}`);
91
+ seenTambonIds.add(t.id);
92
+ if (typeof t.name_th !== "string") throw typeErr("tambon", t.id, "name_th", "string", t.name_th);
93
+ if (typeof t.name_en !== "string") throw typeErr("tambon", t.id, "name_en", "string", t.name_en);
94
+ const zipType = typeof t.zip_code;
95
+ if (zipType !== "string" && zipType !== "number") {
96
+ throw typeErr("tambon", t.id, "zip_code", "string or number", t.zip_code);
97
+ }
98
+ if (typeof t.amphure_id !== "number") throw typeErr("tambon", t.id, "amphure_id", "number", t.amphure_id);
99
+ }
100
+ }
33
101
  function addTrigrams(map, trigrams, idx) {
34
102
  for (const trigram of trigrams) {
35
103
  let set = map.get(trigram);
@@ -47,6 +115,9 @@ function combinedTrigrams(nameTh, nameEn) {
47
115
  return s;
48
116
  }
49
117
  function buildThaiAddressIndex(data, options) {
118
+ if (options?.validate !== false) {
119
+ validateRawData(data);
120
+ }
50
121
  const { provinces, amphures, tambons } = data;
51
122
  const provMap = new Map(
52
123
  provinces.filter((p) => !p.deleted_at).map((p) => [p.id, p])
@@ -65,6 +136,10 @@ function buildThaiAddressIndex(data, options) {
65
136
  const records = [];
66
137
  const map = /* @__PURE__ */ new Map();
67
138
  const zipIndex = /* @__PURE__ */ new Map();
139
+ const normTambon = [];
140
+ const normTambonEn = [];
141
+ const byProvince = /* @__PURE__ */ new Map();
142
+ const byAmphure = /* @__PURE__ */ new Map();
68
143
  for (const tambon of tambons) {
69
144
  if (tambon.deleted_at) continue;
70
145
  const amphure = ampMap.get(tambon.amphure_id);
@@ -94,70 +169,340 @@ function buildThaiAddressIndex(data, options) {
94
169
  const existing = zipIndex.get(record.zipCode);
95
170
  if (existing) existing.push(idx);
96
171
  else zipIndex.set(record.zipCode, [idx]);
97
- addTrigrams(map, extractTrigrams(record.tambonNameTh), idx);
98
- addTrigrams(map, extractTrigrams(record.tambonNameEn), idx);
172
+ const provList = byProvince.get(province.id);
173
+ if (provList) provList.push(idx);
174
+ else byProvince.set(province.id, [idx]);
175
+ const ampList = byAmphure.get(amphure.id);
176
+ if (ampList) ampList.push(idx);
177
+ else byAmphure.set(amphure.id, [idx]);
178
+ const normTh = normalizeThaiAddressText(record.tambonNameTh);
179
+ normTambon.push(normTh);
180
+ addTrigrams(map, extractTrigramsNormalized(normTh), idx);
181
+ const normEn = normalizeThaiAddressText(record.tambonNameEn);
182
+ normTambonEn.push(normEn);
183
+ addTrigrams(map, extractTrigramsNormalized(normEn), idx);
99
184
  addTrigrams(map, extractTrigrams(record.zipCode), idx);
100
185
  addTrigrams(map, provTrigrams.get(province.id), idx);
101
186
  addTrigrams(map, ampTrigrams.get(amphure.id), idx);
102
187
  }
103
- return { map, records, zipIndex };
188
+ const sortedZipKeys = [...zipIndex.keys()].sort();
189
+ const sortedZipPostings = sortedZipKeys.map((z) => zipIndex.get(z));
190
+ return { map, records, zipIndex, sortedZipKeys, sortedZipPostings, normTambon, normTambonEn, byProvince, byAmphure };
191
+ }
192
+
193
+ // src/core/romanize.ts
194
+ var ALIASES = /* @__PURE__ */ new Map([
195
+ // --- Bangkok khet (districts) — concatenated ("no space") forms ---------
196
+ ["phranakhon", "phra nakhon"],
197
+ ["nongchok", "nong chok"],
198
+ ["bangrak", "bang rak"],
199
+ ["bangkhen", "bang khen"],
200
+ ["bangkapi", "bang kapi"],
201
+ ["pathumwan", "pathum wan"],
202
+ ["patumwan", "pathum wan"],
203
+ ["pomprap", "pom prap sattru phai"],
204
+ ["phrakhanong", "phra khanong"],
205
+ ["minburi", "min buri"],
206
+ ["latkrabang", "lat krabang"],
207
+ ["ladkrabang", "lat krabang"],
208
+ ["yannawa", "yan nawa"],
209
+ ["phayathai", "phaya thai"],
210
+ ["thonburi", "thon buri"],
211
+ ["bangkokyai", "bangkok yai"],
212
+ ["huaikhwang", "huai khwang"],
213
+ ["huaykhwang", "huai khwang"],
214
+ ["huaykwang", "huai khwang"],
215
+ ["khlongsan", "khlong san"],
216
+ ["klongsan", "khlong san"],
217
+ ["talingchan", "taling chan"],
218
+ ["bangkoknoi", "bangkok noi"],
219
+ ["bangkhunthian", "bang khun thian"],
220
+ ["phasicharoen", "phasi charoen"],
221
+ ["nongkhaem", "nong khaem"],
222
+ ["ratburana", "rat burana"],
223
+ ["bangphlat", "bang phlat"],
224
+ ["dindaeng", "din daeng"],
225
+ ["buengkum", "bueng kum"],
226
+ ["bangsue", "bang sue"],
227
+ ["bangkholaem", "bang kho laem"],
228
+ ["khlongtoei", "khlong toei"],
229
+ ["klongtoei", "khlong toei"],
230
+ ["klongtoey", "khlong toei"],
231
+ ["khlongtoey", "khlong toei"],
232
+ ["suanluang", "suan luang"],
233
+ ["chomthong", "chom thong"],
234
+ ["donmueang", "don mueang"],
235
+ ["donmuang", "don mueang"],
236
+ ["latphrao", "lat phrao"],
237
+ ["ladprao", "lat phrao"],
238
+ ["lardprao", "lat phrao"],
239
+ ["lat prao", "lat phrao"],
240
+ ["bangkhae", "bang khae"],
241
+ ["laksi", "lak si"],
242
+ ["saimai", "sai mai"],
243
+ ["khannayao", "khan na yao"],
244
+ ["saphansung", "saphan sung"],
245
+ ["wangthonglang", "wang thonglang"],
246
+ ["khlongsamwa", "khlong sam wa"],
247
+ ["klongsamwa", "khlong sam wa"],
248
+ ["bangna", "bang na"],
249
+ ["thawiwatthana", "thawi watthana"],
250
+ ["thungkhru", "thung khru"],
251
+ ["bangbon", "bang bon"],
252
+ // --- Bangkok area / street nicknames with a common misspelling ----------
253
+ ["sathorn", "sathon"],
254
+ ["wattana", "watthana"],
255
+ ["silom", "si lom"],
256
+ // --- Provinces and other high-traffic areas ------------------------------
257
+ ["krungthep", "bangkok"],
258
+ ["korat", "nakhon ratchasima"],
259
+ ["nakornratchasima", "nakhon ratchasima"],
260
+ ["chiangmai", "chiang mai"],
261
+ ["chiangrai", "chiang rai"],
262
+ ["pathumthani", "pathum thani"],
263
+ ["patumthani", "pathum thani"],
264
+ ["samutprakan", "samut prakan"],
265
+ ["samutsakhon", "samut sakhon"],
266
+ ["samutsongkhram", "samut songkhram"],
267
+ ["nakhonpathom", "nakhon pathom"],
268
+ ["nakhonsawan", "nakhon sawan"],
269
+ ["nakhonsithammarat", "nakhon si thammarat"],
270
+ ["prachuapkhirikhan", "prachuap khiri khan"],
271
+ ["huahin", "hua hin"],
272
+ ["hatyai", "hat yai"],
273
+ ["hadyai", "hat yai"],
274
+ ["had yai", "hat yai"],
275
+ ["lopburi", "loburi"],
276
+ ["lop buri", "loburi"],
277
+ ["buengkan", "buogkan"],
278
+ ["bungkan", "buogkan"],
279
+ ["bung kan", "buogkan"],
280
+ ["udonthani", "udon thani"],
281
+ ["khonkaen", "khon kaen"],
282
+ ["suphanburi", "suphan buri"],
283
+ ["nongkhai", "nong khai"],
284
+ ["buriram", "buri ram"]
285
+ ]);
286
+ var NOISE_WORDS_RE = /\b(?:sub[- ]?district|district|province|changwat|amphoe|amphur)\b/g;
287
+ var WHITESPACE_RE = /\s+/g;
288
+ function cleanup(text) {
289
+ const stripped = text.replace(NOISE_WORDS_RE, " ").replace(WHITESPACE_RE, " ").trim();
290
+ return stripped.length > 0 ? stripped : text;
291
+ }
292
+ function applyRomanizationAliases(normalized) {
293
+ if (!normalized) return normalized;
294
+ const direct = ALIASES.get(normalized);
295
+ if (direct !== void 0) return direct;
296
+ const cleaned = cleanup(normalized);
297
+ if (cleaned === normalized) return normalized;
298
+ const aliasedCleaned = ALIASES.get(cleaned);
299
+ return aliasedCleaned !== void 0 ? aliasedCleaned : cleaned;
104
300
  }
105
301
 
106
302
  // src/core/search.ts
107
303
  var ZIP_CODE_RE = /^\d+$/;
304
+ var HAS_LATIN_RE = /[a-z]/i;
305
+ var TH_COLLATOR = new Intl.Collator("th");
306
+ function compareZip(a, b) {
307
+ if (a < b) return -1;
308
+ if (a > b) return 1;
309
+ return 0;
310
+ }
311
+ function sortZipMatches(matches, exactZip) {
312
+ matches.sort((a, b) => {
313
+ if (a.zipCode === exactZip && b.zipCode !== exactZip) return -1;
314
+ if (b.zipCode === exactZip && a.zipCode !== exactZip) return 1;
315
+ return compareZip(a.zipCode, b.zipCode);
316
+ });
317
+ return matches;
318
+ }
319
+ function normalizeLimit(value) {
320
+ if (Number.isNaN(value) || value === -Infinity) return 0;
321
+ if (value === Infinity) return value;
322
+ return Math.max(0, Math.floor(value));
323
+ }
324
+ function lookupByZipCode(index, zip, options) {
325
+ if (!index || !index.zipIndex || !index.records || typeof zip !== "string") return [];
326
+ const normalized = zip.trim();
327
+ if (!ZIP_CODE_RE.test(normalized) || normalized.length < 2) return [];
328
+ const zipLimit = normalizeLimit(options?.zipLimit ?? Infinity);
329
+ const matches = [];
330
+ const keys = index.sortedZipKeys;
331
+ const postings = index.sortedZipPostings;
332
+ if (keys !== void 0 && postings !== void 0) {
333
+ let lo = 0;
334
+ let hi = keys.length;
335
+ while (lo < hi) {
336
+ const mid = lo + hi >> 1;
337
+ if (keys[mid] < normalized) lo = mid + 1;
338
+ else hi = mid;
339
+ }
340
+ for (let i = lo; i < keys.length && keys[i].startsWith(normalized); i++) {
341
+ const indices = postings[i];
342
+ for (let j = 0; j < indices.length; j++) matches.push(index.records[indices[j]]);
343
+ }
344
+ } else {
345
+ for (const [z, indices] of index.zipIndex) {
346
+ if (z.startsWith(normalized)) {
347
+ for (const idx of indices) matches.push(index.records[idx]);
348
+ }
349
+ }
350
+ sortZipMatches(matches, normalized);
351
+ }
352
+ return matches.slice(0, zipLimit).map((record) => ({ ...record }));
353
+ }
354
+ function rankAgainstName(name, query) {
355
+ if (name === void 0 || name.length === 0) return 0;
356
+ if (name === query) return 3;
357
+ if (name.startsWith(query)) return 2;
358
+ if (name.includes(query)) return 1;
359
+ return 0;
360
+ }
361
+ function computeMatchRank(index, idx, query) {
362
+ const th = rankAgainstName(index.normTambon?.[idx], query);
363
+ if (th === 3) return 3;
364
+ const en = rankAgainstName(index.normTambonEn?.[idx], query);
365
+ return en > th ? en : th;
366
+ }
367
+ var scratchByIndex = /* @__PURE__ */ new WeakMap();
368
+ function getScratch(index) {
369
+ const n = index.records.length;
370
+ let s = scratchByIndex.get(index);
371
+ if (s === void 0 || s.counts.length < n) {
372
+ s = { counts: new Uint32Array(n), touched: new Int32Array(n) };
373
+ scratchByIndex.set(index, s);
374
+ }
375
+ return s;
376
+ }
108
377
  function searchThaiAddress(index, query, options) {
109
- const limit = options?.limit ?? 10;
378
+ const limit = normalizeLimit(options?.limit ?? 10);
110
379
  const threshold = options?.threshold ?? 0.4;
111
- if (!index || !query) return [];
380
+ if (!index || !index.map || !index.records || typeof query !== "string") return [];
381
+ if (query.length > 1e3) return [];
112
382
  const normalized = normalizeThaiAddressText(query);
113
383
  if (normalized.length === 0) return [];
114
384
  if (normalized.length > 300) return [];
115
385
  if (ZIP_CODE_RE.test(normalized)) {
116
386
  if (normalized.length < 2) return [];
117
- const matches = [];
118
- for (const [zip, indices] of index.zipIndex) {
119
- if (zip.startsWith(normalized)) {
120
- for (const idx of indices) matches.push(index.records[idx]);
121
- }
122
- }
123
- matches.sort((a, b) => {
124
- if (a.zipCode === normalized && b.zipCode !== normalized) return -1;
125
- if (b.zipCode === normalized && a.zipCode !== normalized) return 1;
126
- return a.zipCode.localeCompare(b.zipCode, void 0, { numeric: true });
387
+ return lookupByZipCode(index, normalized, {
388
+ zipLimit: options?.zipLimit ?? Infinity
127
389
  });
128
- return matches.slice(0, limit);
129
390
  }
130
- if (normalized.length < 3 && query.trim().length < 3) return [];
131
- const queryTrigrams = extractTrigramsNormalized(normalized);
132
- const hits = /* @__PURE__ */ new Map();
391
+ if (normalized.length < 3) return [];
392
+ const searchText = options?.romanizationAliases !== false && HAS_LATIN_RE.test(normalized) ? applyRomanizationAliases(normalized) : normalized;
393
+ const queryTrigrams = extractTrigramsNormalized(searchText);
394
+ const { counts, touched } = getScratch(index);
395
+ let touchedLen = 0;
133
396
  for (const trigram of queryTrigrams) {
134
397
  const candidates = index.map.get(trigram);
135
398
  if (!candidates) continue;
136
399
  for (const idx of candidates) {
137
- hits.set(idx, (hits.get(idx) ?? 0) + 1);
400
+ if (counts[idx] === 0) touched[touchedLen++] = idx;
401
+ counts[idx]++;
138
402
  }
139
403
  }
140
404
  const scored = [];
141
- for (const [idx, count] of hits) {
142
- const score = count / queryTrigrams.size;
405
+ const totalTrigrams = queryTrigrams.size;
406
+ for (let i = 0; i < touchedLen; i++) {
407
+ const idx = touched[i];
408
+ const score = counts[idx] / totalTrigrams;
409
+ counts[idx] = 0;
143
410
  if (score >= threshold) {
144
- scored.push({ idx, score });
411
+ scored.push({ idx, score, matchRank: computeMatchRank(index, idx, searchText) });
145
412
  }
146
413
  }
147
414
  scored.sort((a, b) => {
148
415
  if (b.score !== a.score) return b.score - a.score;
416
+ return b.matchRank - a.matchRank;
417
+ });
418
+ const windowSize = Math.max(limit * 4, 50);
419
+ const window = scored.slice(0, windowSize);
420
+ window.sort((a, b) => {
421
+ if (b.score !== a.score) return b.score - a.score;
422
+ if (b.matchRank !== a.matchRank) return b.matchRank - a.matchRank;
149
423
  const ra = index.records[a.idx];
150
424
  const rb = index.records[b.idx];
151
- return ra.provinceNameTh.localeCompare(rb.provinceNameTh, "th") || ra.amphureNameTh.localeCompare(rb.amphureNameTh, "th") || ra.tambonNameTh.localeCompare(rb.tambonNameTh, "th");
425
+ return TH_COLLATOR.compare(ra.provinceNameTh, rb.provinceNameTh) || TH_COLLATOR.compare(ra.amphureNameTh, rb.amphureNameTh) || TH_COLLATOR.compare(ra.tambonNameTh, rb.tambonNameTh);
426
+ });
427
+ return window.slice(0, limit).map(({ idx }) => ({ ...index.records[idx] }));
428
+ }
429
+
430
+ // src/core/enumerate.ts
431
+ var thCollator = new Intl.Collator("th");
432
+ function byNameTh(a, b) {
433
+ return thCollator.compare(a.nameTh, b.nameTh);
434
+ }
435
+ function listProvinces(index) {
436
+ if (!index || !index.records) return [];
437
+ const seen = /* @__PURE__ */ new Map();
438
+ if (index.byProvince) {
439
+ for (const [provinceId, recordIndices] of index.byProvince) {
440
+ if (seen.has(provinceId) || recordIndices.length === 0) continue;
441
+ const r = index.records[recordIndices[0]];
442
+ seen.set(provinceId, { id: provinceId, nameTh: r.provinceNameTh, nameEn: r.provinceNameEn });
443
+ }
444
+ } else {
445
+ for (const r of index.records) {
446
+ if (!seen.has(r.provinceId)) {
447
+ seen.set(r.provinceId, { id: r.provinceId, nameTh: r.provinceNameTh, nameEn: r.provinceNameEn });
448
+ }
449
+ }
450
+ }
451
+ return Array.from(seen.values()).sort(byNameTh);
452
+ }
453
+ function listAmphures(index, provinceId) {
454
+ if (!index || !index.records) return [];
455
+ const seen = /* @__PURE__ */ new Map();
456
+ const addFrom = (r) => {
457
+ if (!seen.has(r.amphureId)) {
458
+ seen.set(r.amphureId, {
459
+ id: r.amphureId,
460
+ nameTh: r.amphureNameTh,
461
+ nameEn: r.amphureNameEn,
462
+ provinceId: r.provinceId
463
+ });
464
+ }
465
+ };
466
+ if (index.byProvince) {
467
+ const recordIndices = index.byProvince.get(provinceId);
468
+ if (!recordIndices) return [];
469
+ for (const idx of recordIndices) addFrom(index.records[idx]);
470
+ } else {
471
+ for (const r of index.records) {
472
+ if (r.provinceId === provinceId) addFrom(r);
473
+ }
474
+ }
475
+ return Array.from(seen.values()).sort(byNameTh);
476
+ }
477
+ function listTambons(index, amphureId) {
478
+ if (!index || !index.records) return [];
479
+ const toSummary = (r) => ({
480
+ id: r.tambonId,
481
+ nameTh: r.tambonNameTh,
482
+ nameEn: r.tambonNameEn,
483
+ amphureId: r.amphureId,
484
+ zipCode: r.zipCode
152
485
  });
153
- return scored.slice(0, limit).map(({ idx }) => index.records[idx]);
486
+ let result;
487
+ if (index.byAmphure) {
488
+ const recordIndices = index.byAmphure.get(amphureId);
489
+ if (!recordIndices) return [];
490
+ result = recordIndices.map((idx) => toSummary(index.records[idx]));
491
+ } else {
492
+ result = index.records.filter((r) => r.amphureId === amphureId).map(toSummary);
493
+ }
494
+ return result.sort(byNameTh);
154
495
  }
155
496
 
156
497
  // src/core/formatter.ts
157
- function formatThaiAddressSuggestion(record) {
498
+ function formatThaiAddressSuggestion(record, options) {
499
+ const labelTh = `${record.tambonNameTh} > ${record.amphureNameTh} > ${record.provinceNameTh} ${record.zipCode}`;
500
+ const labelEn = `${record.tambonNameEn} > ${record.amphureNameEn} > ${record.provinceNameEn} ${record.zipCode}`;
158
501
  return {
159
502
  id: String(record.tambonId),
160
- label: `${record.tambonNameTh} > ${record.amphureNameTh} > ${record.provinceNameTh} ${record.zipCode}`,
503
+ label: options?.locale === "en" ? labelEn : labelTh,
504
+ labelTh,
505
+ labelEn,
161
506
  tambon: record.tambonNameTh,
162
507
  tambonEn: record.tambonNameEn,
163
508
  amphure: record.amphureNameTh,
@@ -186,10 +531,16 @@ function resolveThaiAddress(record) {
186
531
  };
187
532
  }
188
533
 
534
+ exports.applyRomanizationAliases = applyRomanizationAliases;
189
535
  exports.buildThaiAddressIndex = buildThaiAddressIndex;
190
536
  exports.formatThaiAddressSuggestion = formatThaiAddressSuggestion;
537
+ exports.listAmphures = listAmphures;
538
+ exports.listProvinces = listProvinces;
539
+ exports.listTambons = listTambons;
540
+ exports.lookupByZipCode = lookupByZipCode;
191
541
  exports.normalizeThaiAddressText = normalizeThaiAddressText;
192
542
  exports.resolveThaiAddress = resolveThaiAddress;
193
543
  exports.searchThaiAddress = searchThaiAddress;
544
+ exports.validateRawData = validateRawData;
194
545
  //# sourceMappingURL=index.cjs.map
195
546
  //# sourceMappingURL=index.cjs.map